Store-RS NoF Operations#
This page covers NoF build prerequisites, runtime configuration, and multi-node validation. For design and request-path behavior, see the NoF design.
Source layout#
client/cold_tier/
layout/
physical_key.rs
value_chunk.rs
owner.rs
replica_policy.rs
nof/
backing.rs
backend.rs
object.rs
physical.rs
physical_backend.rs
managed.rs
managed_backend.rs
runtime.rs
extent_store/
executor.rs
nof_spdk_shim.c
kvcs/
capi.rs
executor.rs
physical_layout.rs
KVCS configuration, C API calls, and its private Low-Level layout live under nof/kvcs/.
physical_backend.rs passes complete objects to a key-addressed provider and never publishes a
route. managed_backend.rs is the thin bridge between the existing Cold Tier state machine and a
locator-returning executor. It owns locator encoding, batch bounds, flush-before-publication, and
rollback; ExtentStore alone owns extent alignment and record layout.
Install KVCS SDK and EFC#
The supported SDK version is 0.4.0. Official entry points:
The complete-node installer installs EFC and the SDK under /opt/kvcs-sdk/latest:
curl -fL \
https://kvcachestore.oss-accelerate.aliyuncs.com/scripts/install-kvcs.sh \
-o install-kvcs.sh
less install-kvcs.sh
sudo bash install-kvcs.sh
test -S /var/run/kvcs/efc-grpc.sock
The application and EFC must run as the same operating-system user.
SDK-only archives:
Architecture |
Download |
SHA-256 |
|---|---|---|
x86_64 |
|
|
aarch64 |
|
KVCS_VERSION=0.4.0
KVCS_ARCH="$(uname -m)" # x86_64 or aarch64
curl -fL \
"https://kvcachestore.oss-accelerate.aliyuncs.com/sdk/kvcs-sdk-${KVCS_VERSION}-${KVCS_ARCH}.tar.gz" \
-o "kvcs-sdk-${KVCS_VERSION}-${KVCS_ARCH}.tar.gz"
tar xzf "kvcs-sdk-${KVCS_VERSION}-${KVCS_ARCH}.tar.gz"
sudo mkdir -p /opt/kvcs-sdk
sudo mv "kvcs-sdk-${KVCS_VERSION}" "/opt/kvcs-sdk/${KVCS_VERSION}"
sudo ln -sfnT "/opt/kvcs-sdk/${KVCS_VERSION}" /opt/kvcs-sdk/latest
EFC packages for manual provisioning:
The installer also supports a deploy-tarball package type, but a direct public 0.4.0 deploy-tarball URL is not currently published. Use the installer or the RPM/DEB links above instead of guessing a tarball path.
Build dependencies#
The default feature set has no KVCS dependency. kvcs-capi links the public shared C API and uses
the installed SDK’s generated rust/src/ffi.rs; Mooncake does not build the vendor C++ sources,
run bindgen, or add the vendor Rust wrapper to the Cargo graph. SDK 0.4.0 omits
kvcs_ll_client_destroy from that generated Rust file, so Mooncake declares that one function
from the installed public C header until the SDK binding includes it.
The kvcs-capi feature supports Linux GNU targets on x86_64 and aarch64 only.
Build |
Linked library |
Runtime directory |
|---|---|---|
production |
|
|
official mock |
|
|
KVCS_SDK_ROOT defaults to /opt/kvcs-sdk/latest. The build checks the package version and target
architecture, requires the C header and generated Rust file, checks their expected package shape,
and requires the selected shared library. It does not perform an independent ABI conformance
check. No RPATH is embedded, so the loader must resolve the complete shared-library SONAME chain.
Production build:
export KVCS_SDK_ROOT=/opt/kvcs-sdk/latest
export KVCS_SDK_USE_MOCK=0
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo build -p mooncake-store-client --features kvcs-capi --offline
export LD_LIBRARY_PATH="$KVCS_SDK_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
Mock build:
export KVCS_SDK_ROOT=/opt/kvcs-sdk/latest
export KVCS_SDK_USE_MOCK=1
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo build -p mooncake-store-client --features kvcs-capi --offline
export LD_LIBRARY_PATH="$KVCS_SDK_ROOT/mock/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
KVCS_SDK_USE_MOCK is a build-time switch; changing only LD_LIBRARY_PATH cannot turn a
production-linked binary into a mock build.
SPDK build dependency#
The managed ExtentStore executor itself is pure Rust. Enable nof-spdk only when the NVMe-oF SPDK
transport is required. Install SPDK with the
spdk_nvme, spdk_env_dpdk, and spdk_syslibs pkg-config files, then point the build at either
an installed prefix or an SPDK build tree:
export MOONCAKE_SPDK_PREFIX=<spdk-prefix>
export PKG_CONFIG_PATH="$MOONCAKE_SPDK_PREFIX/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
cargo build -p mooncake-store-client --features nof-spdk --offline
Use the actual directory containing spdk_nvme.pc if the installation places pkg-config files
under install/lib/pkgconfig, build/lib/pkgconfig, or build/lib64/pkgconfig. Without
nof-spdk, mooncake-store-client does not compile or link nof_spdk_shim.c. The transport shim
is the only C layer; allocation, record codec, checksum, buffer pooling, replica policy, and route
lifecycle reuse existing Rust components.
Runtime configuration#
Variable |
Mode |
Meaning |
|---|---|---|
|
both |
set to |
|
both |
|
|
both |
EFC Unix socket; Low-Level defaults to |
|
Standard |
comma-separated Redis endpoints, for example |
|
Standard |
optional Redis password |
|
Low-Level |
KVCS mountpoint index; defaults to 0 |
|
both |
SDK single-value limit; defaults to 4 MiB, maximum 4 GiB |
# Standard
export MC_STORE_RS_ENABLE_COLD_TIER=1
export MOONCAKE_KVCS_MODE=standard
export MOONCAKE_KVCS_EFC_SOCKET=/var/run/kvcs/efc-grpc.sock
export MOONCAKE_KVCS_REDIS_ENDPOINTS=tcp://redis.example:6379
# Low-Level
export MOONCAKE_KVCS_MODE=low-level
export MOONCAKE_KVCS_MOUNTPOINT_INDEX=0
export MOONCAKE_KVCS_MAX_VALUE_SIZE=3221225472
Static target registration#
KVCS 0.4.0 does not enumerate Low-Level mountpoints or expose a mountpoint change stream. The
deployment inventory is therefore the target-list authority. The same inventory must generate the
server-side EFC mountpoints and the Mooncake application configuration. It supplies a stable
target_id and the corresponding local mountpoint_index; an index is not a stable target ID.
The application bootstrap that constructs each StoreClient reads that static inventory and calls
nof_targets once. Every client sharing a route namespace registers the same target IDs and data
plane so that every client can query and read every target directly. Registration is followed by
the common target-owner assignment: local disks keep the registering node as owner, while NoF
targets are assigned to one eligible active runtime. The owner manages target control-plane
operations and health; it does not proxy I/O or authorize ordinary reads.
Create one shared SDK client per EFC socket and bind its configured mountpoints:
use std::sync::Arc;
use mooncake_store_client::{
KvcsLowLevelClient, NofBackend, NofTargetConfig, StoreClientBuilder,
};
// Supplied by the deployment configuration that also generates EFC mountpoints.
let configured_targets = [("nof-disk-a", 0), ("nof-disk-b", 1)];
let kvcs = KvcsLowLevelClient::new(
"/var/run/kvcs/efc-grpc.sock".to_string(),
3 * 1024 * 1024 * 1024,
)?;
let mut targets = Vec::with_capacity(configured_targets.len());
for (target_id, mountpoint_index) in configured_targets {
let executor = Arc::new(kvcs.executor(mountpoint_index));
targets.push(NofTargetConfig::new(
target_id,
NofBackend::new(executor)?,
)?);
}
let client = StoreClientBuilder::new(metadata, "storage-0")
.nof_targets(targets)
.nof_replica_count(2)
.build(expires_at_ms)?;
Register a managed ExtentStore target by wrapping an exclusive region of an SPDK NVMe-oF namespace:
let executor = Arc::new(ExtentStoreExecutor::new(
block_device,
ExtentStoreExecutorConfig::new(0, 16 * 1024 * 1024 * 1024),
)?);
let target = NofTargetConfig::new("nof-disk-0", NofBackend::new(executor)?)?;
let client = StoreClientBuilder::new(metadata, "storage-0")
.nof_target(target)
.nof_replica_count(1)
.build(expires_at_ms)?;
KvcsCapiExecutor::new() is the environment-variable convenience constructor for a single target.
with_low_level_config remains a single-target convenience API. Standard mode uses
with_standard_config; the provider owns its internal placement, so it is registered as one
logical target.
The target list is immutable for the lifetime of a StoreClient. Temporary target availability is
dynamic: the existing owner heartbeat excludes a target after three consecutive failures. One
successful probe restores a short-circuited target; after long-failure route downline, manifest
recovery must also succeed before the target is admitted. Permanent additions or removals require a
coordinated configuration rollout and client restart. A new target must not accept writes until
every client has registered it, and a removed target must be drained by the provider before it
disappears from the inventory. Mooncake does not parse private EFC
configuration, invent target IDs, or implement an SDK-independent discovery protocol.
A target ID, its provider endpoint, mountpoint, mode, and maximum value size are immutable configuration. The ownership fingerprint identifies target IDs, data-plane shape, and replica count; it cannot verify that two processes mapped a target ID to the same provider endpoint or mountpoint. Deployment configuration must enforce that mapping.
Standard sharded writes return an SDK error when any shard fails, after which Mooncake may retry the object write. Mooncake does not maintain a second shard manifest or independently clean up a partially accepted write. Correct retry and incomplete-manifest behavior therefore depends on the KVCS Standard API’s idempotency and manifest contract.
Four-node managed NoF validation#
The SPDK shim is compiled with the Rust/C bridge and links the external SPDK/DPDK libraries
dynamically. Build on a system whose libc is compatible with every initiator. Every initiator must
already have an ABI-compatible RDMA-enabled SPDK runtime at NOF_SPDK_LIB_DIR; the runner keeps
that directory in LD_LIBRARY_PATH and rejects unresolved symbols before starting a client.
The reproducible commands are:
# Run from the Mooncake Store-RS repository root on the build host.
export MOONCAKE_SPDK_PREFIX=<spdk-prefix>
# The reserved initiator and NoF hosts are CPU-only.
export MOONCAKE_ENABLE_CUDA=0
mooncake-store-rs/scripts/e2e/build-nof-multi-client.sh
# Optional: run the SPDK-backed NoF unit-test subset with the same environment.
NOF_RUN_UNIT_TESTS=1 mooncake-store-rs/scripts/e2e/build-nof-multi-client.sh
The multi-client build defaults MOONCAKE_ENABLE_CUDA to 0 because the
reserved validation hosts have no CUDA device. Override it explicitly to 1
only when running the binary on a GPU-capable initiator. This avoids compiling
CUDA pointer/copy paths that fail during CPU-only validation even when CUDA headers are present on
the build host.
build-nof-multi-client.sh validates spdk_nvme.pc, spdk_env_dpdk.pc,
spdk_syslibs.pc, the shared SPDK libraries, Rust formatting, and the final binary’s dynamic
library resolution. It also requires a clean tree and verifies that HEAD did not change during
the build; the emitted build manifest binds that commit to the binary SHA256. The test artifact is
target/debug/nof_multi_client (or the release path when NOF_BUILD_PROFILE=release). The bastion
runner passes the same profile and expected commit through to staging; set NOF_RUN_UNIT_TESTS=1
to run the NoF unit subset before any target reset.
RDMA targets require an RDMA-capable NIC on both endpoints and an SPDK build configured with
--with-rdma; NOF_SPDK_LIB_DIR must select that RDMA-enabled runtime on each initiator.
run-nof-multi-client.sh repeats ldd -r on the actual initiator after adding its SPDK runtime
directory, and writes hostname, kernel, interface inventory, binary hash, and runtime-library
status to run-manifest.txt. A run is not started when a shared library or symbol is unresolved.
The multi-node command runs on the bastion. It builds on NOF_BUILD_HOST, optionally resets the
provisioned NoF image on every configured target, stages the exact binary and runner with rsync,
verifies the build commit and binary SHA256 on each initiator, and runs the configured client
subset on each initiator. Initiators do not need to SSH to target public addresses, so target
cleanup, when enabled, is deliberately performed from the bastion:
# On the bastion, from a checked-out Mooncake Store-RS repository.
NOF_BUILD_HOST='<build-ssh-host>' \
NOF_BUILD_ROOT='<repo-path-on-build-host>' \
MOONCAKE_SPDK_PREFIX='<spdk-prefix-on-build-host-and-initiators>' \
NOF_SPDK_LIB_DIR='<spdk-shared-library-directory-on-initiators>' \
NOF_TARGETS='<target-ssh-host-a>|<target-traddr-a>|<target-id-a>|<target-subnqn-a>|<target-port-a>|tcp,<target-ssh-host-b>|<target-traddr-b>|<target-id-b>|<target-subnqn-b>|<target-port-b>|rdma' \
NOF_CLIENT_IDS='<client-id-a>,<client-id-b>' \
NOF_INITIATORS='<initiator-ssh-host-a>|<initiator-bind-ip-a>|<client-id-a>;<initiator-ssh-host-b>|<initiator-bind-ip-b>|<client-id-b>' \
NOF_REDIS_URL='redis://<redis-host>:<redis-port>/<redis-db>' \
NOF_REPLICA_COUNT='<replica-count>' \
mooncake-store-rs/scripts/e2e/run-nof-multi-client-from-bastion.sh
The multi-node script intentionally has no target, initiator, Redis, build-host, or client-list defaults. A real reservation must pass the topology explicitly:
NOF_TARGETSis a comma-separated target list. Each entry ispublic_host|traddr|target_id|subnqn|port[|transport].transportacceptstcporrdmaand defaults totcpwhen omitted. Both transports use the same SPDK block-device and ExtentStore implementation. The number of NoF targets is the number of entries.NOF_CLIENT_IDSis the global client list used by the test binary for barriers and read verification.NOF_INITIATORSis a semicolon-separated initiator list. Each entry isssh_host|bind_ip|local_client_ids; the number of initiator machines is the number of entries.local_client_idsis the comma-separated subset started on that initiator.MOONCAKE_SPDK_PREFIXis required on the build host.NOF_SPDK_LIB_DIRis required explicitly and must name the ABI-compatible SPDK shared-library directory present on every initiator.NOF_BARRIER_REDIS_URLcontrols the cross-initiator barrier. It defaults toNOF_REDIS_URL.NOF_HOST_NQNis required by the per-initiator runner. The bastion runner derives one from the initiator client list when it is omitted from an initiator entry.NOF_RESET_TARGETS=1enables target image reset from the bastion. When it is enabled,NOF_NVMF_SERVICEandNOF_TARGET_IMAGEmust also be set explicitly. This convenience mode supports at most one configured target per SSH host; provision targets separately when one host exports multiple images or uses different services.
Every reset stops NOF_NVMF_SERVICE, removes and recreates NOF_TARGET_IMAGE at
NOF_TEST_IMAGE_BYTES, and restarts the service; it never touches the system NVMe disk unless the
operator explicitly points NOF_TARGET_IMAGE there. Run logs and the manifest are left under
NOF_INITIATOR_DIR/logs/<run-tag>/ on each initiator.
For a manually staged run, use mooncake-store-rs/scripts/e2e/run-nof-multi-client.sh with
NOF_RESET_TARGETS=0, NOF_SKIP_TARGET_SSH_CHECK=1, the local NOF_BIND_IP, and the same
Redis/SPDK settings after performing the reset from the bastion. NOF_LOCAL_CLIENT_IDS can be
used to start only the client subset that belongs on the current machine while keeping
NOF_CLIENT_IDS as the global client set. Do not use scp.
The runner sets MC_STORE_RS_ENABLE_COLD_TIER=1, creates per-run registration, write/offload,
and read-start barriers, and starts the client IDs from NOF_CLIENT_IDS with
NOF_REPLICA_COUNT. Each client writes a disjoint object set and calls the synchronous
debug_evict_all API. A client joins the offload barrier only after eviction reports completion,
zero remaining hot replicas, and no replica dropped without cold backing. After every configured
client reaches that barrier, all clients issue batch_get_into reads concurrently and verify the
complete object set. Read-only recovery runs wait separately for recovered target manifests because
there are no hot replicas for debug_evict_all to process. The test binary uses
NoopTransport for the local hot segment, so it does not claim to validate remote hot-memory
transfers; the cross-client assertion is the managed NoF read path. A passing run must contain the
configured write/offload and read verification counts in each client log. The target list is
configured by NOF_TARGETS, client IDs by NOF_CLIENT_IDS, local client subset by
NOF_LOCAL_CLIENT_IDS, initiator machines by NOF_INITIATORS, and the image size by
NOF_TEST_IMAGE_BYTES; the reset operation removes and recreates only NOF_TARGET_IMAGE.
The same runner exposes focused lifecycle checks without embedding a topology:
NOF_EXPECT_NOF_COPIESverifies the number of materialized target copies per route and requires those copies to use distinct target IDs.NOF_EXPECT_MAX_TARGET_COPY_SKEWverifies that total materialized copies are distributed across configured targets within the supplied maximum count difference.NOF_WATERMARK_HIGH_BYTESandNOF_WATERMARK_LOW_BYTES, combined withNOF_POST_OFFLOAD_WAIT_SECONDS, verify owner-driven high-to-low watermark cleanup.NOF_EXPECT_POST_WAIT_MISSING_ROUTES=trueverifies that watermark reclaim removes logical routes when it evicts their final payload copy.
Watermark validation is intentionally layered. Existing Cold Tier CLOCK unit tests verify hot/cold victim ordering, the managed-NoF tracker unit test verifies that only locally owned target copies enter that same CLOCK, and the multi-machine test verifies that selected records are physically released until the low watermark and are not resurrected on restart. The multi-machine test does not implement a second LRU oracle.
NOF_DELETE_AND_REWRITE=truedeletes the first object set and writes a second set, allowing a deliberately smallNOF_DEVICE_BYTESto verify extent release and reuse; the test also requires an old and rewritten route to share a target/locator pair.NOF_HANDOFF_DEPARTING_CLIENT=<client-id>makes one of exactly two clients exit normally after offload; the survivor then deletes, rewrites, offloads, and reads the complete object set.NOF_HANDOFF_ABRUPT_EXIT=truekills that departing client withSIGKILL, without drain; the runner accepts exit status 137 only for that client. The survivor continues renewing its own lease while it waits for the departed owner’s lease to expire, then exercises takeover. SetNOF_LEASE_TTL_MSto a short test lease and setNOF_HANDOFF_WAIT_SECONDSgreater than the lease plus two seconds. The per-client timeout must also exceed the handoff wait.NOF_READ_ONLY=trueskips writes and validates routes rebuilt from existing target manifests.NOF_EXPECT_MISSING_ROUTES=truecombines with read-only mode to verify that tombstoned/reclaimed records are not resurrected after a fresh startup scan, then writes and reads a probe object to prove that the recovered target is usable.NOF_EXPECT_ABSENT_TARGETSverifies that long-failed target copies have been removed after an externally orchestrated target outage.NOF_EXIT_AFTER_POST_WAIT=trueends a successful fault-downline stage before the final read. It is intended for a following read-only invocation, with the same keyspace and a new barrier run ID, that restarts the target and verifies manifest recovery from its original records.NOF_REONLINE_WAIT_SECONDSadds a second live-client window after downline. Restart the failed target after all clients reach thereonline-readybarrier; the same client processes then require every pre-fault target/key association and replica count to recover before reading.NOF_BARRIER_RUN_IDnamespaces synchronization keys independently ofNOF_KEYSPACE; use a new value for every invocation, including restart checks that intentionally reuse the same objects.
Validation#
Default build and tests do not require KVCS:
cargo fmt --all -- --check
MOONCAKE_SKIP_NATIVE_BUILD=1 cargo test -p mooncake-store-core --offline
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --lib --offline -- --test-threads=1
Managed ExtentStore validation does not require KVCS or SPDK:
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --lib \
client::cold_tier::nof::extent_store:: --offline -- --test-threads=1
Official mock validation:
export KVCS_SDK_ROOT=/opt/kvcs-sdk/latest
export KVCS_SDK_USE_MOCK=1
export LD_LIBRARY_PATH="$KVCS_SDK_ROOT/mock/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
nof:: --offline -- --test-threads=1
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
client::cold_tier::nof::kvcs::executor::standard::tests::live_standard_round_trip_smoke \
--offline -- --exact --ignored --test-threads=1
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
client::cold_tier::nof::kvcs::executor::low_level::tests::live_low_level_round_trip_smoke \
--offline -- --exact --ignored --test-threads=1
MOONCAKE_KVCS_MAX_VALUE_SIZE=8 MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
client::cold_tier::nof::kvcs::executor::low_level::tests::live_low_level_round_trip_smoke \
--offline -- --exact --ignored --test-threads=1
MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo clippy -p mooncake-store-client --all-targets --features kvcs-capi \
--offline -- -D warnings
Live Standard validation requires EFC plus Redis; live Low-Level validation requires EFC plus a configured mountpoint:
export KVCS_SDK_ROOT=/opt/kvcs-sdk/latest
export KVCS_SDK_USE_MOCK=0
export LD_LIBRARY_PATH="$KVCS_SDK_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export MOONCAKE_KVCS_EFC_SOCKET=/var/run/kvcs/efc-grpc.sock
export MOONCAKE_KVCS_REDIS_ENDPOINTS=tcp://127.0.0.1:6379
MOONCAKE_KVCS_MODE=standard MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
client::cold_tier::nof::kvcs::executor::standard::tests::live_standard_round_trip_smoke \
--offline -- --exact --ignored --nocapture --test-threads=1
MOONCAKE_KVCS_MODE=low-level MOONCAKE_SKIP_NATIVE_BUILD=1 \
cargo test -p mooncake-store-client --features kvcs-capi --lib \
client::cold_tier::nof::kvcs::executor::low_level::tests::live_low_level_round_trip_smoke \
--offline -- --exact --ignored --nocapture --test-threads=1
The official mock validates ABI marshalling, error handling, and executor behavior. It does not exercise real I/O, shared memory, Redis, timeouts, failover, disk faults, capacity, watermarks, or performance. It also omits the full shard details needed for a Standard sharded-object read, so that path requires live EFC and Redis.
Compatibility#
Managed NoF routes are stored in ObjectRoute.nof_backing using protobuf field 15; field 14
remains reserved. The metadata capability gate rejects managed NoF routes on backends that do not
advertise NoF backing support. Existing routes without nof_backing continue to decode normally;
a cluster must enable the backing-route capability before it writes managed NoF placement metadata.