Configuration
This page covers configuration knobs that go beyond the basic
pool stanza. Refer to the inline rustdoc on
dynomite::conf::ConfPool for the exhaustive field list; the
pages here describe the operator-facing surface in more detail.
Endpoints and Unix sockets
The listen: (client plane), dyn_listen: (peer plane), and
stats_listen: (HTTP stats) directives take a host:port
address (IPv4 or IPv6). A value that begins with / is treated
as a Unix domain socket path instead:
dyn_o_mite:
listen: /var/run/dynomite/client.sock
dyn_listen: 127.0.0.1:8101
stats_listen: 127.0.0.1:22222
The servers: entries (the backend datastore) accept the same
shape: host:port:weight [name] or, for a Unix-socket backend,
/path/to/socket:weight [name]. The port is reported as 0 for
Unix-socket entries.
servers:
- /var/run/valkey/valkey.sock:1 valkey-local
Backend authentication
redis_requirepass(string, default unset): when set, the password is sent asAUTH <pw>on every backend connection immediately after the TCP handshake. It mirrors the Valkey / Redisrequirepassserver option. Memcache backends are not authenticated (AUTHis RESP-specific; memcache binary SASL is not implemented), so the knob is ignored for a memcachedata_store.
Dyniak store
noxu_path(path): filesystem directory the in-process Noxu DB environment opens at. Required whendata_store: dyniakis selected and ignored otherwise. The directory must be writable; an existing environment is reused, otherwise one is created. Adyniakpool serving the Riak surface needs a binary built with--features riak.
Bucket types
A bucket type is a named bundle of routing properties that
applies to every key whose on-the-wire form starts with the
bucket prefix. Operators use bucket types to give different key
classes different SLAs from inside a single pool: cache-style
keys can sit on DC_ONE while transactional keys ride
DC_EACH_SAFE_QUORUM, and the dispatcher swaps in the right
settings on a per-request basis without needing more pools, more
listeners, or client-side routing.
The wire convention is intentionally simple. The bucket name is
the byte sequence before the first / in the key; everything
else (including the slash itself) is the user-visible key body.
A key with no / has no bucket, and the dispatcher falls back
to the pool defaults (or to default_bucket_type, when one is
named).
dyn_o_mite:
listen: 127.0.0.1:8102
dyn_listen: 127.0.0.1:8101
tokens: '101134286'
servers:
- 127.0.0.1:22122:1
data_store: 0
read_consistency: DC_ONE
write_consistency: DC_ONE
bucket_types:
- name: sessions
read_consistency: DC_QUORUM
write_consistency: DC_EACH_SAFE_QUORUM
n_val: 3
- name: cache
read_consistency: DC_ONE
write_consistency: DC_ONE
n_val: 1
default_bucket_type: cache
With this stanza:
GET sessions/abc123reads withDC_QUORUM; writes useDC_EACH_SAFE_QUORUMand may fan out across DCs.GET cache/u/9000usesDC_ONEreads and writes.GET plain-key(no slash) falls through todefault_bucket_type: cacheand inherits itsDC_ONErouting.- Removing
default_bucket_typemakes the slashless and unknown-prefix cases inherit the pool-level defaults.
n_val caps the number of replicas a single request fans out
to. 0 means "no cap" (the default; the consistency level
alone decides fan-out). A positive n_val truncates the plan
to its first n_val targets, where rack-local replicas are
already ordered first.
Validation enforces unique bucket-type names, valid consistency
strings, and that default_bucket_type (when set) names an
entry in bucket_types. Invalid stanzas are caught by
dynomited --test-conf before the daemon starts.
Hinted handoff
Hinted handoff is the data-availability tier that lets writes
succeed even when one of their target peers is unreachable. The
dispatcher records the on-the-wire request bytes plus the
intended peer index in a node-local hint store; a background
drainer periodically replays the hints once the peer returns to
the gossip-defined Normal state.
The feature is off by default. Enable it per-pool in YAML:
dyn_o_mite:
listen: 127.0.0.1:8102
dyn_listen: 127.0.0.1:8101
tokens: '101134286'
servers:
- 127.0.0.1:22122:1
data_store: 0
read_consistency: DC_QUORUM
write_consistency: DC_QUORUM
enable_hinted_handoff: true
hint_ttl_seconds: 86400
hint_store_max_bytes: 67108864
hint_drain_interval_ms: 30000
hint_dir: /var/lib/dynomite/hints
Knobs:
enable_hinted_handoff(bool, defaultfalse): master switch. Whenfalsethe dispatcher's behaviour is unchanged and a Down or unreachable target is silently skipped.hint_ttl_seconds(uint, default86400/ 24 hours): per-hint expiry. Hints older than this are dropped during the next drainer sweep so the store stays bounded.hint_store_max_bytes(uint, default67108864/ 64 MiB): upper bound on the cumulative payload bytes of pending hints. Once the store is full the dispatcher falls back to its non-handoff error path (DynomiteNoQuorumAchieved) and the next drainer sweep reclaims space when peers come back online.hint_drain_interval_ms(uint, default30000): cadence of the drainer sweep. Every tick (a) drops expired hints and (b) replays the queued hints for every peer that has transitioned toNormal.hint_dir(path, default unset): when set, the hint store is durable. It keeps one append-only segment file per peer under this directory and replays them at startup, so hints queued for a temporarily down peer survive a coordinator restart. When unset the hint store is RAM-only and queued hints are lost on restart. Ignored whenenable_hinted_handoffisfalse.
Operational notes:
- Hinted handoff applies only to writes. Reads against a Down peer remain a no-data-to-hint situation and continue to follow the legacy "skip the target and fall through to the consistency check" path.
- The hint store is RAM-only by default. Set
hint_dirto make it durable: the store then writes one append-only segment file per peer under that directory and replays them at startup, so pending hints survive a node restart. Withouthint_dir, a node restart drops every pending hint, so sizehint_store_max_bytesand the drainer cadence conservatively. - The synthesised reply that the dispatcher feeds the
coalescer on a hinted target's behalf is
+OK\r\n. This is the correct shape forSET-style writes (the dominant case) but may show up as a "divergent target" for request types whose reply is an integer or a multibulk (e.g.DEL). The hint is still recorded and replayed; the surviving real replies determine the coalesced answer the client sees, and read-repair (which only fires onGET) is not affected. - The
hint_drain_interval_msvalue should be small enough to notice peer recoveries promptly but large enough that the drainer does not dominate the per-tick scheduling cost when many peers come back at once. The default 30 seconds matches the gossip period.
Search index persistence
The optional RediSearch FT.* surface (crates/dynomite-search)
is wired into dynomited behind the search Cargo feature.
By default the FT.* index registry is purely in-memory: index
definitions, indexed documents, text fields, and suggestion
dictionaries are lost on a process restart and the client must
recreate them.
search_index_dir(path, default unset): when set, the search registry snapshots its full state (index schemas, indexed documents, per-TEXT-field contents, and FT.SUG* suggestion dictionaries) to a CBOR file under this directory and reloads it on restart. A clean checkout, a process kill, or a chaos restart no longer drops the FT.* surface: the snapshot survives on disk and the next startup reloads every index without the client re-issuingFT.CREATEor re-feeding data.
Operational notes:
- The snapshot is written atomically (write to a sibling
*.tmp, flush + fsync, rename over the live file), so a crash mid-write never leaves a half-written snapshot; the prior good snapshot survives. A stray*.tmpfrom an interrupted write is ignored on load and overwritten on the next save. - Snapshots are taken periodically (every five seconds) and once more on clean shutdown. A crash between snapshots loses at most the un-snapshotted delta; the FT.* workload tolerates re-creation, so the recreate-on-miss path becomes a fallback rather than the primary recovery once persistence is on.
- When
search_index_diris unset the registry never touches disk; behaviour is identical to releases before this knob existed.
Riak mode
The optional Riak protocol surface (crates/dyniak) is
wired into dynomited behind the riak Cargo feature. When
the binary is built with --features riak, the riak: block
of the pool body controls the PBC listener, the HTTP gateway,
and the optional active-anti-entropy (AAE) scheduler. The
block is parsed and validated even under the default build so
YAML files authored against the Riak-enabled binary still
validate without the feature flag; under the default build the
fields are inert at run time.
my_pool:
...
riak:
pbc_listen: 127.0.0.1:8087
http_listen: 127.0.0.1:8098
quic_listen: 127.0.0.1:8089
aae_enabled: true
aae_full_sweep_interval_seconds: 86400
aae_segment_interval_seconds: 60
tls_cert: /etc/dynomited/riak.crt
tls_key: /etc/dynomited/riak.key
tls_ca: /etc/dynomited/ca.pem
wasm_modules:
- id: wordcount
path: /etc/dynomited/wasm/wordcount.wasm
Every field is optional. Setting only pbc_listen enables the
PBC listener with no HTTP gateway; setting only http_listen
runs HTTP without PBC. The two listeners share a single
backing Datastore so request accounting accumulates in one
place.
| Key | Type | Meaning |
|---|---|---|
pbc_listen | host:port | Riak PBC listener bind address (TCP). |
http_listen | host:port | Riak HTTP gateway bind address. |
quic_listen | host:port | Riak PBC listener bind address over QUIC (a UDP socket; same framing as pbc_listen). Requires tls_cert + tls_key and a binary built with the quic feature; rejected otherwise. |
aae_enabled | bool | Spawn the AAE scheduler. Default false. |
aae_full_sweep_interval_seconds | u64 | Cadence over which one full sweep across every peer pair completes. Default 86400. |
aae_segment_interval_seconds | u64 | Cadence of one (peer, time-bucket) exchange tick. Default 60. Must be <= aae_full_sweep_interval_seconds. |
tls_cert | path | PEM certificate for the Riak listeners. When tls_cert + tls_key are both set the listeners terminate TLS; both absent runs plaintext. Setting one without the other is rejected. |
tls_key | path | PEM private key matching tls_cert. |
tls_ca | path | Optional PEM CA bundle. When set, inbound clients must present a cert signed by a CA in the bundle (mutual TLS). |
wasm_modules | list | Wasm map/reduce modules registered with the MapReduce executor at startup. Each entry is {id, path} where path points at a .wasm or .wat file. Loaded only when the binary is built with the wasm feature; otherwise parsed and validated but a Phase::WasmModule submission returns a WasmNotImplemented error. Every id must be unique and every path must exist at validation time. |
The CLI offers three matching overrides for the same knobs:
--riak-pbc-listen=HOST:PORT, --riak-http-listen=HOST:PORT,
and --riak-aae-enabled. They are visible in
dynomited --help only when the binary was built with
--features riak. See Riak mode for
operator-facing details.
Distribution modes
The pool's distribution: directive selects the algorithm that
maps a hashed key to one of the rack's peers. Two modes are
first-class and supported indefinitely:
distribution: | Behaviour |
|---|---|
vnode | Per-rack continuum keyed by per-peer tokens: lists. The historical default. |
random_slicing | Small, gap-free (name, size) partition table over the 64-bit hash space. New in this slice. |
random_slicing is the recommended mode for new deployments
that do not need byte-identical compatibility with an existing
operator-managed token plan. The technique is described in
detail in docs/design/random-slicing-integration.md; the
operator-facing pitch is "you cannot accidentally configure a
hole in the ring", which is the failure mode that bit chaos
pass-3 (3-of-4 hosts running with a 4-host token plan, 25% of
the ring orphaned).
The legacy aliases ketama, modula, and random are accepted
for backward compatibility with the C reference's vocabulary.
They emit a tracing::warn! line at config-load time and
collapse to vnode at runtime.
Default per binary build
- Default builds keep
vnodeas the default. Existing YAML files validate and run unchanged. --features riakbuilds that configure a Riak listener (riak.pbc_listenorriak.http_listen) flip the default torandom_slicing. Riak-shaped deployments inherit Riak's full-coverage partition invariant by default. Operators can still setdistribution: vnodeexplicitly to override.
Migration: shadow mode
A --distribution-shadow=<vnode|random_slicing> CLI flag (and
the matching distribution_shadow: YAML key) lets an operator
run both modes simultaneously: routing follows the live
distribution:, but the dispatcher also computes the would-be
peer for the shadow mode and bumps the
distribution_shadow_disagreement_total Prometheus counter when
they disagree. Use this to validate a migration before flipping
the YAML.
The dyn-admin distribution-dump --node <host:stats-port>
subcommand pretty-prints the configured live and shadow modes
plus the cumulative disagreement counter, so an operator can
verify each node's view from one place.
dyn_o_mite:
listen: 127.0.0.1:8102
dyn_listen: 127.0.0.1:8101
tokens: '101134286'
servers:
- 127.0.0.1:6379:1
data_store: 0
distribution: random_slicing # gap-free partition table
distribution_shadow: vnode # validate the new mode
# against the old one