Skip to content

[Store]Add async batch get interface - #1861

Closed
zhangzuo21 wants to merge 2 commits into
kvcache-ai:mainfrom
zhangzuo21:feat/native-async-interface
Closed

zhangzuo21 wants to merge 2 commits into
kvcache-ai:mainfrom
zhangzuo21:feat/native-async-interface

Conversation

@zhangzuo21

Copy link
Copy Markdown
Collaborator

Description

Module

  • Transfer Engine (mooncake-transfer-engine)
  • [✅ ] Mooncake Store (mooncake-store)
  • Mooncake EP (mooncake-ep)
  • Integration (mooncake-integration)
  • P2P Store (mooncake-p2p-store)
  • Python Wheel (mooncake-wheel)
  • PyTorch Backend (mooncake-pg)
  • Mooncake RL (mooncake-rl)
  • CI/CD
  • Docs
  • Other

Type of Change

  • Bug fix
  • [✅ ] New feature
  • Refactor
  • Breaking change
  • Documentation update
  • Other

How Has This Been Tested?

Checklist

  • [✅ ] I have performed a self-review of my own code.
  • [✅ ] I have formatted my own code using ./scripts/code_format.sh before submitting.
  • I have updated the documentation.
  • [✅ ] I have added tests to prove my changes are effective.

Motivation

When sglang's HiCache prefetch IO thread fetches KV cache pages from Mooncake, it processes operations serially — one batch_get must fully complete before the next begins. This underutilizes RDMA/network bandwidth when multiple prefetch operations are queued.

This PR adds a two-phase async batch_get interface that allows callers to submit multiple concurrent get operations and wait for completions in any order, enabling parallel RDMA transfers across different prefetch requests.

Changes

Two-phase split of BatchGet (Client layer)

  • Client::BatchGetSubmit(): Performs metadata query, replica selection, hot cache redirect, and submits RDMA transfers via TransferSubmitter::submit(). Returns a BatchGetState holding the in-flight TransferFutures.
  • Client::BatchGetComplete(): Waits for all TransferFutures, handles hot cache admission, lease expiry checks, and metrics.
  • No changes to the existing synchronous BatchGet(). The new methods are additive.

AsyncGetContext (RealClient layer)

  • Completion queue model for managing multiple concurrent async get operations.
  • submit(keys, buffers, sizes) → token: Calls batch_get_submit (synchronous but fast — metadata RPC + RDMA submit), stores state in an active slot. Returns INVALID_TOKEN only when max concurrency is reached. All key-level errors are deferred to wait_any.
  • wait_any() → (token, results): Blocks on a condition variable until any operation completes. Results contain per-key success (0) or failure (-1).
  • Internal completion thread polls TransferFuture::isReady() on active slots, moves ready slots out of the lock before calling BatchGetComplete, then enqueues results.
  • CAS-based concurrency control in submit() to prevent exceeding max_concurrency.
  • Safe destruction: waits for all in-flight RDMA transfers to complete before releasing resources.

Python bindings (pybind11)

  • AsyncGetContext class exposed with submit(), wait_any(), in_flight property.
  • All blocking calls release the GIL.
  • MooncakeDistributedStore.create_async_context(max_concurrency) factory method.

Limitations

  • Only memory replicas (RDMA/Memcpy) are supported in the async path. SSD offload replicas are reported as errors in the results. The existing synchronous batch_get_into continues to handle SSD.

Tests

  • C++ unit tests (10 cases): Two-phase API correctness, sync/async data consistency, AsyncGetContext submit/wait_any, max concurrency enforcement, non-existent key error handling, safe destruction with in-flight transfers.
  • Python unit tests (7 cases): pybind11 binding verification, submit/wait_any workflow, concurrent operations, max concurrency, error propagation via wait_any, destruction safety.

Files changed

File Change
mooncake-store/include/client_service.h BatchGetState struct, BatchGetSubmit/BatchGetComplete declarations
mooncake-store/src/client_service.cpp Two-phase method implementations
mooncake-store/include/real_client.h AsyncGetContext class, RealClient new methods
mooncake-store/src/real_client.cpp AsyncGetContext implementation, batch_get_submit/batch_get_complete wrappers
mooncake-integration/store/store_py.cpp pybind11 bindings for AsyncGetContext
mooncake-store/tests/CMakeLists.txt Register new test target
mooncake-store/tests/async_batch_get_test.cpp C++ unit tests
mooncake-store/tests/test_async_python.py Python unit tests

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a two-phase asynchronous batch get mechanism and an AsyncGetContext class for concurrent operations, including Python bindings. Feedback focuses on improving the performance of result re-indexing from O(N^2) to O(N) and utilizing C++20 features for cleaner code in the completion polling logic.

Comment on lines +3736 to +3745
for (size_t i = 0; i < num_keys; ++i) {
auto it = std::find(batch_to_orig.begin(), batch_to_orig.end(), i);
if (it != batch_to_orig.end()) {
size_t batch_idx = std::distance(batch_to_orig.begin(), it);
state.results[i] = batch_results[batch_idx];
state.lease_timeouts[i] = batch_leases[batch_idx];
} else {
state.results[i] = tl::unexpected(pre_results[i].error());
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This loop for re-indexing results has a potential performance issue. It iterates num_keys times and calls std::find within the loop, leading to a complexity that can approach O(N^2) for large batches, where N is num_keys. This can be inefficient.

A more performant O(N) approach would be to first initialize all results with their pre-check outcomes, and then iterate through the batch_to_orig vector once to update the results for the operations that were actually submitted.

    // Initialize all results with their pre-determined outcomes from pre-checks.
    for (size_t i = 0; i < num_keys; ++i) {
        if (!pre_results[i]) {
            state.results[i] = tl::unexpected(pre_results[i].error());
        }
    }

    // Overwrite with results from the actual batch operation for keys that were submitted.
    for (size_t batch_idx = 0; batch_idx < batch_to_orig.size(); ++batch_idx) {
        size_t orig_idx = batch_to_orig[batch_idx];
        state.results[orig_idx] = batch_results[batch_idx];
        state.lease_timeouts[orig_idx] = batch_leases[batch_idx];
    }

Comment thread mooncake-store/src/real_client.cpp Outdated
Comment on lines +3856 to +3871
for (auto it = active_slots_.begin(); it != active_slots_.end(); ) {
bool all_ready = true;
for (auto& pt : it->state.pending_transfers) {
if (!pt.future.isReady()) {
all_ready = false;
break;
}
}

if (all_ready) {
ready_slots.push_back(Slot{it->token, std::move(it->state)});
it = active_slots_.erase(it);
} else {
++it;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved code clarity and maintainability, consider using the C++20 std::erase_if algorithm here. It simplifies the code for filtering and moving elements from active_slots_ to ready_slots_, making the intent clearer and reducing the risk of manual iterator errors.

            std::erase_if(active_slots_, [&](Slot& slot) {
                bool all_ready = true;
                for (const auto& pt : slot.state.pending_transfers) {
                    if (!pt.future.isReady()) {
                        all_ready = false;
                        break;
                    }
                }

                if (all_ready) {
                    ready_slots.push_back({slot.token, std::move(slot.state)});
                }
                return all_ready;
            });

@00fish0 00fish0 added run-ci and removed run-ci labels Apr 10, 2026
@stmatengss

Copy link
Copy Markdown
Collaborator

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 72.74549% with 136 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
mooncake-store/src/real_client.cpp 60.00% 60 Missing ⚠️
mooncake-store/src/client_service.cpp 51.68% 43 Missing ⚠️
mooncake-integration/store/store_py.cpp 0.00% 33 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

This PR has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Please update the PR or comment to keep it open.

@github-actions

Copy link
Copy Markdown

This PR was closed because it has been inactive for 104 days. Feel free to reopen if you'd like to continue the work.

@github-actions github-actions Bot closed this Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants