Skip to content

[RFC]: Store-to-TENT QoS Integration Boundary for Foreground Transfers and Background Offload #2946

Description

@catyans

Changes proposed

Summary

This RFC discusses how Mooncake Store traffic should participate in TENT QoS so that latency-sensitive transfers are protected from background offload traffic.

A representative serving workload has two priority classes:

  • HIGH: foreground point-to-point KV handoff, such as Prefill-to-Decode (P2D), and foreground remote-prefix fetch.
  • LOW: background writes to DRAM, local SSD, remote memory, or remote storage.

The main contention is on NIC TX and accelerator PCIe outbound bandwidth. A background Store write should not delay a foreground KV handoff that gates time-to-first-token. This RFC compares three integration boundaries:

  1. Option A — route all storage data movement through TENT.
  2. Option B — use layered QoS and unified admission while retaining existing executors.
  3. Option C — route only the network portion of remote Store traffic through TENT.

The proposed rollout is C first, then B if PCIe-side interference remains measurable. Option A should remain a longer-term choice only if TENT is intentionally expanded into a general data-movement runtime.

This RFC is an integration proposal. It does not redefine the intent schema, QoS contract, budget scheduler, execution plan, or local admission queue proposed in #2865, #2856, #2866, #2863, and #2132. Instead, it asks how Mooncake Store should consume those mechanisms and where the ownership boundary should be.

Motivation

Consider the following traffic classes:

Traffic Typical resources Priority Reason
Foreground P2D KV handoff accelerator PCIe OUT + NIC TX HIGH decode cannot start until the KV state arrives
Foreground remote-prefix fetch NIC RX + accelerator PCIe IN, depending on placement HIGH a hit avoids repeated prefill computation
DRAM offload accelerator PCIe OUT + memory bandwidth LOW background cache management
Local SSD write accelerator PCIe OUT + memory bandwidth + NVMe LOW background persistence/eviction
Remote Store write accelerator PCIe OUT, depending on GDR/staging, + NIC TX LOW background persistence/write-through

Foreground prefix fetch often uses the opposite direction from background writes, so it should remain concurrent when the resources do not conflict. The scheduler must therefore reason about resources and direction; a single global HIGH/LOW gate would unnecessarily serialize independent RX/IN and TX/OUT work.

The required behavior is cooperative preemption:

foreground HIGH arrives
  -> stop admitting new conflicting LOW slices
  -> allow already-submitted DMA/WQE/I/O to complete
  -> run HIGH
  -> resume LOW using otherwise-idle capacity

Already-posted WQEs and already-submitted DMA or NVMe operations generally cannot be revoked. Therefore, the protection bound depends on both slice size and the global LOW in-flight window, not on queue ordering alone.

Current code shape

The observations below use commit 9c6125f.

Store traffic does not have one execution path

Store Put dispatches disk writes to PutToLocalFile, while memory and NoF replicas go through TransferSubmitter (client_service.cpp).

TransferSubmitter then selects local memcpy, Transfer Engine, SPDK NoF, or file-read execution depending on replica type and locality (transfer_task.cpp). These executors do not share one priority scheduler.

The file-put path performs synchronous accelerator-to-host copying into a pinned buffer before submitting StorageBackend::StoreObject to the write pool (client_service.cpp). Deferring this copy requires an explicit buffer-lifetime contract; queueing a raw pointer after the caller may reuse it would be unsafe.

Store priority is not propagated to TENT

Remote memory replica transfers construct the legacy Transport::TransferRequest (transfer_task.cpp). The legacy request has no intent or QoS priority field (transport.h), and the adapter does not populate those fields when creating tent::Request (transfer_engine.cpp). The effective result is the TENT default priority rather than Store-aware classification.

TENT already contains useful QoS primitives

TENT RDMA slicing preserves request priority and passes it into device selection and worker dispatch (rdma_transport.cpp). RDMA workers maintain three priority queues and select HIGH before MEDIUM before LOW (workers.cpp, workers.cpp).

However:

  • SharedSlotManager provides cross-process slot visibility/gating, not a cross-process priority queue or global LOW in-flight budget.
  • The optional local admission queue is process-local and currently owner/FIFO or deadline oriented rather than Store-resource aware ([RFC]: TENT Local Transfer Admission Queue #2132).
  • LOW priority promotion must be configured carefully; promoting old background work into HIGH can defeat strict foreground protection.
  • Device selection fallback is availability-oriented, not a hard priority reservation.
  • Rail monitoring provides health and rerouting, not business-priority arbitration.

Hardware QoS is a QP-pool decision

SL/traffic class is associated with configured QP pools and selected by policy. A practical design uses distinct HIGH and LOW named QP pools. It should not claim per-WQE hardware preemption. End-to-end network differentiation also requires matching switch DCB/PFC/ETS configuration.

Goals

  1. Protect foreground handoff P95/P99 latency under background Store load.
  2. Preserve concurrency for transfers that do not contend on the same directional resource.
  3. Prevent LOW traffic in one process from blocking HIGH traffic in another process sharing a NIC or PCIe root complex.
  4. Allow LOW traffic to consume unused bandwidth and guarantee minimum progress.
  5. Expose unmanaged paths explicitly instead of claiming QoS coverage for black-box backends.
  6. Keep a default-off, reversible rollout path.

Non-goals

Common semantic contract

All three options need the same semantic plumbing:

enum class TransferIntent {
  FOREGROUND_HANDOFF,
  FOREGROUND_PREFIX_FETCH,
  DRAM_OFFLOAD,
  LOCAL_STORE_WRITE,
  REMOTE_STORE_WRITE,
  CHECKPOINT,
};

struct StoreQosContext {
  TransferIntent intent;
  PriorityClass priority;
  uint64_t bytes;
  ResourceCharge resources;
  TraceId trace_id;
};

The concrete names and ABI should reuse #2865 and #2856 rather than introduce a second public schema. The important integration requirement is end-to-end propagation:

Store operation
  -> legacy/public request compatibility layer
  -> tent::Request
  -> policy and QP-pool selection
  -> admission and worker queues
  -> metrics/completion

Unknown legacy callers should be visible as UNKNOWN in metrics. A compatibility default may be necessary during rollout, but silently treating every Store request as HIGH should not be the steady state.

Resource accounting should distinguish at least:

NIC_TX, NIC_RX,
ACCEL_PCIE_OUT, ACCEL_PCIE_IN,
HOST_MEMORY_BW,
NVME_READ, NVME_WRITE

A non-GDR remote write is a staged pipeline rather than one resource charge:

accelerator -> pinned host  [ACCEL_PCIE_OUT, HOST_MEMORY_BW]
pinned host -> remote       [HOST_MEMORY_BW, NIC_TX]

Option A: route all storage data movement through TENT

Design

Expand TENT into a general data-movement runtime. P2D, remote memory, remote SSD, local SSD, accelerator/host copies, and prefix fetches are represented as logical transfer DAGs scheduled by one resource-aware runtime.

P2D / prefix / DRAM / local SSD / remote Store
                         |
                         v
             TENT resource-aware scheduler
                    /       |       \
                RDMA      Copy      File/NoF
              executor  executor    executor

A logical request would contain ordered stages, stable buffer ownership, resource charges, priority/intent, completion, and cancellation metadata. The Execution Plan work in #2863 is the natural representation boundary.

Scheduling rules:

  • maintain runnable queues per resource and priority;
  • stop new conflicting LOW slice submission while HIGH is pending;
  • permit non-conflicting directional work to continue;
  • enforce global and per-process LOW bytes/slices/outstanding limits;
  • guarantee LOW progress with a minimum budget, not by blindly promoting it to HIGH;
  • reserve all resources required by the next composite stage atomically or in a fixed order.

Required implementation work

  • Add Copy, file/NVMe, SPDK/NoF, and backend adapters/executors with priority-equivalent scheduling.
  • Convert synchronous file-put D2H into a lifetime-safe asynchronous contract. Possible models are retaining the source allocation, copying to runtime-owned staging memory, or exposing an asynchronous completion contract.
  • Extend local and cross-process admission to all declared resources.
  • Ensure an executor failure cannot block unrelated foreground TENT work.

Benefits

  • One scheduling domain can model NIC, PCIe, memory, and NVMe contention.
  • Consistent fairness, observability, and cross-process accounting.
  • Strong foundation for deadlines, tenant budgets, and composite transfer plans.

Risks

  • Largest scope and failure-domain expansion.
  • TENT becomes responsible for very different completion, retry, and cancellation models.
  • Buffer allocator lifetime changes may be invasive.
  • A central scheduler/shared lock can become a new bottleneck.
  • Merely wrapping black-box backend calls in a TENT API would produce apparent, not effective, QoS.

Option B: layered QoS with unified admission and existing executors

Design

Keep TENT, memcpy workers, file workers, SPDK/NoF, and storage backends as execution owners. Extend the Admission Queue from #2132, or a shared layer above it, so every executor must acquire a resource-scoped permit before submitting its next slice.

L0  application: intent, priority, handoff deadline
                         |
L1  node admission: resource budgets and HIGH-pending state
              /          |           \
L2 executors: TENT     memcpy      file/NoF/backend
              |
L3 network: HIGH/LOW named QP pools + switch policy
              |
L4 feedback: handoff P99 and utilization adjust LOW budget

Example permit:

struct Permit {
  ResourceMask resources;
  uint64_t max_bytes;
  TimePoint expires_at;
  uint64_t policy_generation;
  bool single_use;
};

Rules:

  • HIGH sets high_pending[resource]; new conflicting LOW permits stop.
  • One LOW permit authorizes at most one bounded slice.
  • Permits expire so unused grants do not strand capacity.
  • HIGH may borrow idle LOW capacity; LOW cannot consume the HIGH reservation.
  • Separate resources preserve useful RX/TX and IN/OUT concurrency.
  • Composite stages declare and acquire their actual resource vectors.

Cross-process shared state should include:

high_pending[resource]
waiters[resource][priority]
inflight_bytes[resource][priority]
token_bucket[resource][priority]
owner_leases[process]
heartbeat[process]
policy_generation

The uncontended path should remain an atomic fast path. Heartbeats/generations reclaim leaked permits after process failure.

Buffer-lifetime boundary

This option still cannot defer the synchronous file-put D2H safely without source retention or owned staging memory. The path should remain explicitly unmanaged until that contract exists. Network and stable-buffer paths can be integrated first.

Benefits

  • Unified scheduling authority while retaining mature executors.
  • Can cover both NIC and PCIe contention incrementally.
  • Reuses existing TENT priority queues, policies, admission work, and QP pools.
  • Smaller failure domain than Option A.

Risks

  • All executors must honor the permit contract; bypasses need auditing.
  • Priority configuration can diverge across layers and cause inversion.
  • Resource declarations may not match a backend's real pipeline.
  • Cross-process leases and multi-resource acquisition remain non-trivial.

Option C: route only remote Store network traffic through TENT

Design

Address the clearest conflict first: foreground handoff and background remote Store writes sharing NIC TX.

foreground handoff HIGH ----\
                              > TENT priority/admission -> RDMA/NIC
remote Store write LOW ------/

DRAM / local SSD / opaque backend -> existing path, reported as unmanaged

“Remote Store” must first be classified:

  1. Remote memory replica already using Transfer Engine/TENT. Propagate priority/intent through the legacy adapter and limit LOW in-flight work.
  2. Remote SSD backend exposing a network-transfer stage. Add a TENT transport adapter for the data plane while leaving metadata/control RPCs in the backend.
  3. Remote SSD network hidden inside StorageBackend. TENT cannot directly schedule it. The backend must expose bounded slices, completion, stable buffers, and endpoint/resource metadata; otherwise only coarse external rate limiting is possible.

Minimum changes for case 1:

  • add backward-compatible intent/priority/trace propagation to the legacy request adapter;
  • mark background remote writes LOW and foreground fetch/handoff HIGH;
  • bound global_low_inflight_bytes, per-process LOW bytes, per-QP outstanding WRs, and LOW slice size;
  • use distinct HIGH/LOW named QP pools;
  • add cross-process high_pending for processes sharing the selected NIC;
  • configure LOW aging so it cannot silently become foreground HIGH.

The cooperative yield bound is approximately:

yield time <= global conflicting LOW in-flight bytes / effective bandwidth
              + scheduler and completion jitter

Reducing slice size without limiting the total number of posted LOW WQEs does not provide this bound.

Coverage boundary

Option C protects the TENT-managed network portion. It does not guarantee that:

  • a synchronous D2H stage before TENT cannot interfere on PCIe;
  • local DRAM/SSD copies cannot interfere;
  • an opaque backend's internal network traffic follows TENT priority;
  • traffic classes receive end-to-end treatment without switch configuration.

These limitations must be visible in metrics and documentation.

Benefits

  • Smallest and fastest change.
  • Directly reuses TENT's existing priority slicing and worker queues.
  • Easy to disable and benchmark independently.

Risks

  • Partial QoS can be mistaken for end-to-end isolation.
  • PCIe interference may remain, especially without GDR.
  • Opaque remote storage backends may not be integrable without a new contract.

Comparison

Dimension A: all through TENT B: layered admission C: remote Store only
TENT role general data-movement runtime network executor plus shared QoS control network executor
Scope largest incremental smallest
NIC TX protection complete complete complete for managed remote Store
PCIe OUT protection potentially complete complete after copy paths join only TENT-visible portion
Local DRAM/SSD TENT executors existing executors with permits unmanaged
Opaque backends must become executors must expose admission/slicing may remain unsupported
Buffer-lifetime change broad and required incremental avoidable for stable/GDR paths
Failure domain largest medium smallest
Recommended role possible long-term redesign target architecture first validation phase

Proposed rollout

Phase 0: semantics and observability

Phase 1: Option C

  • Integrate remote-memory Store writes with TENT LOW priority.
  • Keep foreground handoff/fetch HIGH.
  • Add process-local LOW slice/outstanding limits.
  • Add node-wide high_pending and global LOW in-flight accounting.
  • Validate separate named QP pools and switch configuration where available.

Phase 2: evaluate the remaining interference

  • Run foreground handoff with remote Store, D2H, and local SSD interference independently.
  • If PCIe-side background traffic still degrades foreground P99, proceed to Option B.

Phase 3: Option B

  • Extend admission to accelerator copy and file/NoF executors.
  • Add a buffer lease or owned-staging contract for deferred file-put D2H.
  • Make black-box backends expose bounded asynchronous stages or leave them explicitly unmanaged.

Phase 4: reconsider Option A

Only proceed if maintainers want TENT to own general data movement and the executor/lifetime refactor is justified by broader use cases.

Metrics and validation

Required metrics

  • application-visible foreground handoff P50/P95/P99;
  • end-to-end prefix-fetch latency and TTFT where available;
  • queue/permit wait by intent, priority, and resource;
  • HIGH-pending duration and LOW throttle time;
  • in-flight bytes and outstanding slices by resource/priority/process;
  • TENT submit-to-completion duration and worker queue wait;
  • NIC TX/RX, accelerator PCIe IN/OUT, and NVMe queue/bandwidth counters;
  • QP-pool/traffic-class byte counters;
  • priority promotion count by intent;
  • unmanaged bytes by path and resource.

Validation matrix

  1. Foreground handoff only.
  2. Store only.
  3. Foreground handoff + remote write-through.
  4. Foreground handoff + write-back burst.
  5. Foreground handoff + D2H/DRAM offload.
  6. Foreground handoff + local SSD write.
  7. Foreground prefix fetch + TX-heavy background traffic.
  8. Cross-process: process A runs LOW Store, process B submits HIGH handoff.
  9. Single-rail/multi-rail and rail failure/recovery.
  10. GDR and staged/non-GDR paths where supported.

Proposed initial acceptance targets, subject to benchmark agreement:

  • foreground handoff P99 regression under saturated LOW traffic: at most 5% versus foreground-only baseline;
  • no new conflicting LOW submission within 100 microseconds after HIGH becomes pending;
  • LOW-only throughput at least 95% of QoS-disabled throughput;
  • no cross-process LOW-over-HIGH inversion;
  • no unnecessary serialization of non-conflicting RX/IN and TX/OUT work;
  • bounded LOW progress without promotion into foreground HIGH;
  • default-off configuration and behavior-preserving rollback.

Relationship to existing RFCs

Open questions

  1. Should TENT remain a network transport runtime, or eventually own all storage data movement?
  2. Do maintainers agree with Option C as the first integration phase and Option B as the target architecture?
  3. Should the legacy Store transfer request gain explicit intent/priority fields, or should Store move directly to the newer request/options API?
  4. Should cross-process Store QoS extend the existing shared-slot state, or use the distributed admission mechanism proposed elsewhere?
  5. Which LOW intents may age within LOW, and which, if any, may be promoted to another priority class?
  6. Is HIGH/LOW named-QP-pool selection the preferred hardware boundary?
  7. For deferred file-put D2H, should Mooncake retain the source allocation, use runtime-owned staging memory, or expose asynchronous completion to callers?
  8. Should the primary acceptance metric be the application-visible handoff P99, with CQ/API/kernel timings treated as diagnostic breakdowns?

Before submitting a new issue...

  • Searched existing issues for TENT QoS, transfer intent, admission, resource budgets, traffic classes, and execution plans.
  • Read the TENT roadmap and the related RFCs listed above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions