Key Access Path Debugging#
This guide explains how to trace a single key through Mooncake Store RS: where a
put writes the object, how a get resolves and reads it, which route layer is
involved, and which storage node finally owns the data.
What You Can See Today#
Mooncake Store RS already exposes two useful observability surfaces:
Tracing logs show request spans, route lookup stages, local/remote read and write decisions, control-plane failures, and transport fallbacks.
query_route()returns the authoritative object route for one key, including replica owners, segment names, offsets, lengths, priorities, and route version.
Use both together:
Use logs to answer: which client issued the request, did it go local or remote, and did route/control-plane access fail?
Use
query_route()to answer: where is this key stored right now?
Metrics are useful for aggregate health and throughput, but they are not per-key traces.
Enable Tracing#
Standalone Store Client#
Start the daemon with a targeted trace filter:
mooncake-store-rs-client ... \
--trace-filter 'info,mooncake_store_client::client=debug,mooncake_store_client::route_directory=debug,mooncake_store_client::control_plane=debug,mooncake_store_py::dummy_service=debug,hyper=warn,h2=warn,tower=warn,tonic=warn' \
--metrics-addr 0.0.0.0:19101
The hyper, h2, tower, and tonic targets are intentionally kept at
warn. Setting the whole process to debug usually produces too much HTTP/2
and gRPC protocol noise.
Python Real Client / SGLang Real Client#
For Python real clients, including SGLang when it loads Mooncake through the Python package, use environment variables:
export MC_STORE_RS_TRACE=1
export MC_STORE_RS_TRACE_FILTER='info,mooncake_store_client::client=debug,mooncake_store_client::route_directory=debug,mooncake_store_client::control_plane=debug,hyper=warn,h2=warn,tower=warn,tonic=warn'
export MC_STORE_RS_TRACE_FILE=/tmp/mooncake-real-client-debug.log
export MC_STORE_RS_METRICS_ADDR='0.0.0.0:19101'
python -m sglang.launch_server ...
MC_STORE_RS_TRACE=1 initializes Rust tracing before setup(...).
MC_STORE_RS_TRACE_FILTER is passed directly to tracing_subscriber.
MC_STORE_RS_TRACE_FILE appends the Rust real-client trace to a dedicated file.
MC_STORE_RS_METRICS_ADDR starts the in-process /metrics endpoint after
real-client setup.
Find One Key in Logs#
Use the unscoped key and the scoped route key when grepping:
KEY='YOUR_KEY'
TENANT='default'
grep -E \
"key=\"${KEY}\"|key=${KEY}|${TENANT}::${KEY}|route authority returned object route|resolved object routes|writing reserved replicas|batch get completed|remote direct|get fallback|route .*failed|marked route authority" \
sglang.log
The scoped route key format is:
<tenant>::<key>
For the default tenant and key foo, the route key is:
default::foo
Put Path#
A normal single-key put emits a span like:
store.put{runtime=rw-node:1 tenant="default" key="YOUR_KEY" bytes=4096}: ...
Important fields:
runtimeis the client runtime that issued the request.tenantis the logical tenant.keyis the user key.bytesis the object payload size.
During the data write stage, look for:
writing reserved replicas runtime=rw-node:1 replicas=1 local_writes=0 remote_writes=1 value_bytes=4096
Interpretation:
replicasis the number of reserved replica targets for this object.local_writesis the number of replicas written into the caller’s local storage memory.remote_writesis the number of replicas written through the transport layer into remote storage memory.value_bytesis the object size written to each replica.
This line tells you whether the put data path was local or remote. To see the
exact storage owner, segment, and offset, use query_route().
For batched puts, the same flow is split into stage metrics and logs such as:
batch_put_stage_rankbatch_put_stage_reservebatch_put_stage_load_routesbatch_put_stage_writebatch_put_stage_route_cas
Get Path#
A normal single-key get emits a span like:
store.get{runtime=rw-node:1 tenant="default" key="YOUR_KEY"}:store.batch_get{runtime=rw-node:1 items=1}: ...
When route resolution succeeds, look for:
resolved object routes runtime=rw-node:1 items=1
If the selected readable replica is local, the get finishes with:
batch get completed via local copies only runtime=rw-node:1 total_items=1 local_items=1 local_bytes=4096
If the selected readable replica is remote, the get finishes with:
batch get completed across local and remote paths runtime=rw-node:1 total_items=1 local_items=0 local_bytes=0 remote_items=1 remote_bytes=4096 remote_batch_chunks=1 remote_direct_fallbacks=0
Important fields:
local_itemsis the number of objects read from local memory.remote_itemsis the number of objects read from remote storage owners.remote_bytesis the remote data volume.remote_batch_chunksis the number of batched remote transfer chunks.remote_direct_fallbacksis the number of direct remote fallback reads.
If batch scratch planning cannot serve the read path, the client may fall back to direct remote get:
executed remote direct get fallback runtime=rw-node:1 tenant="default" key="YOUR_KEY" bytes_out=4096
Query Where a Key Is Stored#
Use query_route() to inspect the storage placement for one key:
import json
from mooncake.store import MooncakeDistributedStore
store = MooncakeDistributedStore()
store.setup({
"local_hostname": "127.0.0.1:19999",
# transport_metadata_url omitted: defaults to P2PHANDSHAKE in the dict-form path.
"global_segment_size": 0,
"local_buffer_size": 16 * 1024 * 1024,
"protocol": "tcp",
"rdma_devices": "",
"metadata_url": "redis://127.0.0.1:6379/0",
"labels": {
"storage": "false",
"route": "false",
},
"routed_writes": False,
"route_control": "embedded_wrh",
})
route = store.query_route("YOUR_KEY", tenant="default")
print(json.dumps(route, indent=2, ensure_ascii=False))
store.close()
The inspector client uses storage=false and route=false so it does not join
the storage pool or route-authority pool.
Example output:
{
"key": "default::YOUR_KEY",
"version": 1,
"state": "Active",
"replicas": [
{
"owner": "storage-node-a:1",
"segment_name": "storage-node-a-segment",
"offset": 140733193388032,
"segment_offset": 1048576,
"length": 4096,
"priority": 0,
"tier": "Dram"
}
]
}
Field meanings:
keyis the scoped route key.versionis the route version.stateshould beActivefor readable objects.replicas[*].owneris the storage owner / replica owner, not the route authority owner.replicas[*].segment_nameis the storage segment containing the object.replicas[*].segment_offsetis the offset inside that segment.replicas[*].offsetis the optional transport-visible absolute address recorded for the replica. Routes written before this field existed omit it; readers then remap fromsegment_offset.replicas[*].lengthis the object length in bytes.replicas[*].priorityis the replica priority; lower priority is preferred.replicas[*].tieris the storage tier.
For reads, the client selects a readable replica from this route. In normal
cases, priority 0 is the first choice if that owner is still readable.
Metrics for Correlation#
Expose metrics:
curl -s http://127.0.0.1:19101/metrics | grep -E \
'route_lookup_many|control_route_batch_get|put_stage|batch_get|segment_used|replica_distribution|transport_bytes'
Useful metric families:
mooncake_store_request_total{tenant="...",operation="route_lookup_many",...}tracks route lookup results.mooncake_store_request_total{tenant="...",operation="control_route_batch_get",...}tracks control-plane route reads.mooncake_store_request_total{tenant="...",operation="put_stage_reserve",...}tracks reservation stage results.mooncake_store_request_total{tenant="...",operation="put_stage_write",...}tracks data write stage results.mooncake_store_request_total{tenant="...",operation="put_stage_route_cas",...}tracks route publish CAS stage results.mooncake_store_segment_used_bytes{tenant="...",runtime=...,segment=...}shows segment usage.mooncake_store_replica_distribution{tenant="...",runtime=...,tier="dram"}shows replica distribution.mooncake_store_transport_bytes_total{tenant="...",direction="read"|"write",peer_kind="storage"}shows storage transport traffic.
Metrics are process-level aggregate signals. They cannot reconstruct one key’s full path by themselves.
Recommended Debug Procedure#
Enable the targeted trace filter on the process that issues
put/get.If using a daemon/dummy path, enable tracing on the daemon too.
Run one
putand onegetfor a unique key prefix.Grep logs by both raw key and scoped key.
Use
query_route()to dump storage placement.Use
/metricsonly to confirm aggregate route, control-plane, data-plane, and storage usage counters changed as expected.
Current Limitation#
The system can show the request span, route-authority hit, local/remote data path, failures, and final storage placement. The only remaining split is that logs and route inspection answer different questions:
Logs show the request path and route-authority lookup path.
query_route()shows the final storage placement for the object replicas.