mooncake-store-rs-bench#
mooncake-store-rs-bench is a standalone benchmark and verification tool for the Mooncake store. It is built by mooncake-store-rs-cli alongside mooncake-store-rs-client; the separate admin command is built by mooncake-store-rs-admin.
Commands#
mooncake-store-rs-bench [global options] <COMMAND>
Global options#
Flag |
Default |
Description |
|---|---|---|
|
required |
Store-RS metadata URL. Accepts |
|
|
Transfer Engine metadata input. Accepts |
|
|
Metadata keyspace prefix |
|
|
Data-plane transport backend |
|
|
Transport protocol |
|
|
Transport bind hostname |
|
0 |
Per-client storage allocation for bench RW clients |
|
16 MiB |
Per-client scratch allocation |
|
|
Default tenant scope |
|
|
|
|
off |
Prometheus metrics bind address |
|
|
Global RNG seed for deterministic data |
|
|
Route control mode used by bench runtimes |
|
|
WRH route-authority fanout |
|
|
Replication factor for writes |
The default bench topology is scratch-only for RW clients
(MC_BENCH_STORAGE_BYTES=0). Start separate storage=true daemons with
matching metadata, transport, and --keyspace settings before running the
benchmark. This is the same storage/RW role split used by
scripts/tests/client/test-client-rw-cli.sh. When --keyspace is omitted in
scratch-only mode, bench joins the same default metadata namespace as
mooncake-store-rs-client: mc/store-rs/v2. When --storage-bytes > 0 and
--keyspace is omitted, bench generates an isolated
mc/store-rs/bench/<unique> keyspace for that run.
Startup safety gate for classic_te compatibility deployments:
bench refuses to start if active
storage=truedaemons in the same keyspace mix metadata-backed logical segment names withP2PHANDSHAKErpc-address segment namesbench also refuses to start if its effective transport metadata mode disagrees with the active storage daemons already visible in that keyspace
fix the cluster by restarting every participating storage daemon and bench process with one transport metadata mode before benchmarking
Tracing behavior#
mooncake-store-rs-bench initializes its own tracing subscriber.
default sink:
stderreffective filter:
--trace-filter, thenMC_STORE_RS_TRACE_FILTER, thenRUST_LOG, theninfodedicated trace file:
MC_BENCH_TRACE_FILE=/path/to/bench.logMC_STORE_RS_TRACE_FILEremains the real-client / standalone-client trace-file surface and does not redirect bench outputANSI colors are enabled only when
stderris a TTY and no bench trace file is selectedspan close events are disabled, so per-request
close time.busy=...subscriber noise does not flood benchmark output
bench — throughput and latency#
mooncake-store-rs-bench --metadata-url redis://127.0.0.1:6379/0 bench \
--mode mixed \
--write-interface batch-put-from \
--read-interface batch-get-into \
--concurrency 8 \
--value-size 65536 \
--batch-size 16 \
--duration 60 \
--read-ratio 70 \
--output-format text
Flag |
Default |
Description |
|---|---|---|
|
|
Operation mix |
|
|
Write-side store API used for measured write traffic; also read from |
|
|
Read-side store API used for measured read traffic; also read from |
|
|
Concurrent worker threads |
|
|
Payload size per object |
|
|
Objects per |
|
|
Total operations per worker |
|
off |
Run for fixed wall time instead of iteration count |
|
|
Warmup operations excluded from stats |
|
|
Read fraction in mixed mode |
|
|
Periodic progress interval |
|
|
Distinct key count |
|
|
Writer |
|
|
Reader |
|
|
Report body format emitted through tracing |
bench separates traffic shape from concrete client APIs:
--modecontrols whether the workload is write-only, read-only, or mixed--write-interfaceselectsput,batch_put, orbatch_put_fromfor measured writes--read-interfaceselectsget,batch_get, orbatch_get_intofor measured readsdefaults benchmark
batch_put_from+batch_get_intoMC_BENCH_INTERFACEScan set both env defaults at once, for exampleMC_BENCH_INTERFACES=put,getbatched operations advance their key cursor by the batch width, so adjacent batch calls do not repeatedly rewrite or reread the same object window
mixed mode splits each worker’s key shard into a stable read half and a write half; this keeps the benchmark from turning ordinary read traffic into immediate read-after-overwrite route churn
prefill, warmup, and measured phases heartbeat per-worker writer/reader clients so long scratch-only benchmark setup does not let route authorities expire before measured reads start
writer/reader runtime stable IDs include a per-process run ID, so rerunning the same Kubernetes Job before old leases expire does not collide with the previous run’s still-active bench clients
measured reads retry bounded route-readiness misses, such as a freshly written object route not yet being readable; the wait is included in read latency and persistent misses still count as read errors
Output at the end of a run:
=== Benchmark Results ===
Mode: mixed | Write: batch_put_from | Read: batch_get_into | Duration: 30.12s | Workers: 8 | Value size: 65536 B | Batch size: 16
Metric batch_put_from batch_get_into
------ --- ---
Total ops 3840 8960
QPS 127.5 297.4
Throughput 7.7 GiB/s 18.0 GiB/s
Latency p50 1.23ms 0.84ms
Latency p90 2.45ms 1.56ms
Latency p99 5.12ms 3.78ms
Latency p999 12.4ms 8.92ms
Latency max 18.7ms 11.3ms
Errors 0 0
--output-format changes the report body only. The final text, JSON, or CSV
payload is still emitted as INFO tracing events, so the normal tracing prefix
remains around the report in stderr or MC_BENCH_TRACE_FILE.
Counters always count API calls, not individual objects:
putandgetcount one object per callbatch_put,batch_put_from,batch_get, andbatch_get_intocount one batch call per operationthroughput still includes the bytes of every object returned by that call
verify — correctness checks#
mooncake-store-rs-bench --metadata-url redis://127.0.0.1:6379/0 verify \
--write-interface batch-put-from \
--read-interface batch-get-into \
--verify-overwrite \
--verify-delete \
--verify-multi-tenant
Flag |
Default |
Description |
|---|---|---|
|
|
Write-side store API used for verify checks that issue writes; also read from |
|
|
Read-side store API used for verify checks that issue ordinary reads; also read from |
|
|
Payload size |
|
|
Keys exercised in multi-key tests |
|
|
Objects per |
|
off |
Enable overwrite correctness test (64 rounds) |
|
off |
Enable delete + reclaim test |
|
off |
Enable tenant isolation test |
Each check prints PASS or FAIL with a diagnostic. The process exits non-zero if any check fails.
verify defaults to batch_put_from + batch_get_into. MC_BENCH_INTERFACES=put,get
and the explicit --write-interface / --read-interface flags are both
supported here too.
Default checks run without flags:
single-round-trip— configured write interface, then configured read interface, byte-compareget-into-buffer— configured write interface, thenget_into, size and byte-comparebatch-put-get— multi-key write/read using the configured interfacesis-exist— configured write interface, thenis_existmulti-key-write/multi-key-read— N-key write/read using the configured interfaces with a heartbeat between phases
soak — long-duration stability#
mooncake-store-rs-bench --metadata-url redis://127.0.0.1:6379/0 soak \
--duration 3600 \
--write-interface batch-put-from \
--read-interface batch-get-into \
--fault redis-jitter:5:50 \
--fault metadata-drop:2 \
--verify-reads
Flag |
Default |
Description |
|---|---|---|
|
|
Total run time |
|
|
Worker count |
|
|
Write-side store API used for soak writes; also read from |
|
|
Read-side store API used for soak reads; also read from |
|
|
Payload size |
|
|
Objects per |
|
|
Progress report cadence |
|
none |
Fault injection spec (repeatable) |
|
|
Verify every read against expected payload |
|
|
Compatibility flag accepted by the CLI; the current soak loop still heartbeats every 64 iterations |
|
|
Read fraction |
|
|
Distinct key count |
soak defaults to batch_put_from + batch_get_into. Override either side with the same
CLI flags or env vars used by bench when you want single-key traffic.
MC_BENCH_INTERFACES=write=put,read=get is also accepted.
Fault spec format:
Spec |
Effect |
|---|---|
|
Sleep a deterministic random interval before the next store operation |
|
Inject a synthetic pre-op failure for N% of operations |
|
Sleep a deterministic random interval before the next store operation |
|
Inject a synthetic pre-op failure for N% of operations |
Multiple --fault flags are applied in order before each operation.
Progress is reported on a single line per interval:
[60s] batch_put_from: 142 qps p50=1.1ms p99=4.8ms | batch_get_into: 331 qps p50=0.7ms p99=3.2ms | errors: 0
Architecture#
Module layout#
crates/mooncake-store-rs-cli/src/bin/mooncake-store-rs-bench/
main.rs entry point: parse args, init bench-local tracing, install ctrlc, dispatch
cli.rs clap structs: GlobalArgs, BenchArgs, VerifyArgs, SoakArgs, FaultSpec
datagen.rs deterministic LCG payload generation; key naming helpers
latency.rs LatencyRecorder: raw-sample p50/p90/p99/p999 percentile tracker
reporter.rs text/json/csv report-body formatting via tracing; interval progress printer
setup.rs BenchCluster: metadata backend, compat runtime construction, startup snapshots
bench.rs concurrent worker threads (std::thread::scope)
verify.rs sequential correctness checks
soak.rs long-running loop with per-iteration fault injection and read verification
fault.rs FaultInjector: deterministic jitter and drop decisions from LCG
Data generation#
All payloads are deterministic. The seed string is {global_seed}-{key}-{generation} and the LCG is taken from mooncake-store-e2e. The same seed string always produces the same bytes regardless of when or where it is called, so read verification does not require storing the written values.
Latency tracking#
LatencyRecorder stores raw per-operation microsecond samples in a Vec. Percentiles are computed by lazy sort on first access. Workers accumulate into a thread-local recorder and the main thread merges all recorders at the end. This approach gives exact percentiles without pre-defined histogram buckets.
Fault injection#
FaultInjector uses the same LCG to produce deterministic random decisions per operation, seeded by a global operation counter. Each worker calls pre_op() before issuing a store request. Depending on the configured specs, pre_op() may sleep (jitter or transport-delay) or return an error string (metadata-drop or transport-error). Returned errors increment the error counter and skip the actual store call.
Client construction#
BenchCluster wraps one or more StoreClient instances for writer and reader
roles. It uses the same CompatRuntimeArgs / CompatSetupArgs path as
mooncake-store-rs-client, so transport backend selection, RDMA env passthrough,
and compatibility defaults stay aligned with the normal runtime.
Each client gets its own selected transport factory (classic-te by default,
optional tent) with a unique segment name, registered against the same
RedisMetadataBackend.
Keyspace behavior is conditional:
scratch-only mode (
storage_bytes=0) reuses the configured keyspace, ormc/store-rs/v2when--keyspaceis omittedstorage-owning mode (
storage_bytes>0) auto-generatesmc/store-rs/bench/<unique>when--keyspaceis omitted
Build#
Configure the root CMake project as described in the Store-RS source-checkout quickstart, then build the build_store_rs_cli target. The unified root wheel installs the client, admin, and benchmark commands for direct use from its Python environment.
Next reading#
Rust API for store client usage
Configuration for Redis and transport options
mooncake-store-rs/crates/mooncake-store-e2e/src/main.rsfor the upstream correctness test suite