Configuration Reference¶
Three layers, later wins: defaults → JSON config file (--config) → environment variables → CLI flags. In Kubernetes the chart handles all of it; this page is the full map for when you need to reach in.
Environment variables¶
Every variable, with the compiled-in default when unset. (This table is generated by reading internal/platform/config/env.go, not by wishful thinking.)
HTTP¶
| Variable | Default | Notes |
|---|---|---|
NARAD_HTTP_ADDR |
:7942 |
Client API + node RPC (QUIC shares the port) |
NARAD_HTTP_READ_TIMEOUT |
10s |
|
NARAD_HTTP_WRITE_TIMEOUT |
30s |
Must exceed MAX_CONSUME_WAIT or long-polls die mid-wait |
NARAD_HTTP_IDLE_TIMEOUT |
60s |
|
NARAD_HTTP_SHUTDOWN_GRACE |
10s |
Drain window on SIGTERM |
NARAD_HTTP_MAX_CONSUME_WAIT |
10s |
Server-side ceiling on ?wait= long-polls |
NARAD_HTTP_PPROF_ADDR |
off | e.g. :6060; unauthenticated — keep it cluster-internal |
Cluster¶
| Variable | Default | Notes |
|---|---|---|
NARAD_NODE_ID |
pod/host name | The node's identity; must appear in the peer list |
NARAD_CLUSTER_ADDR |
:7943 |
Raft bind address |
NARAD_CLUSTER_PEERS |
— | id@host:7943,… — the full voter list, identical on every node |
NARAD_CLUSTER_INITIAL_MEMBERS |
empty | IDs allowed to bootstrap; everyone else joins. Empty = legacy "all bootstrap" |
NARAD_CLUSTER_SECRET |
— | Shared secret gating all node-to-node QUIC RPC |
NARAD_CLUSTER_TLS_CERT_FILE / _KEY_FILE / _CA_FILE |
off | Mutual TLS for Raft; all three or nothing |
Storage & data¶
| Variable | Default | Notes |
|---|---|---|
NARAD_DATA_DIR |
data |
Everything lives here: topics/, ingress/, metastore/ |
The interesting storage knobs (codec, fsync mode, flush/sync tuning, segment size) are file-only — see the config file section below.
Topic defaults¶
Applied when a topic-create omits the field; existing topics keep their values.
| Variable | Default |
|---|---|
NARAD_TOPIC_DEFAULT_PARTITIONS |
3 |
NARAD_TOPIC_MAX_PARTITIONS |
108 |
NARAD_TOPIC_DEFAULT_RETENTION_AGE_MS |
604800000 (7 days) |
NARAD_TOPIC_DEFAULT_VISIBILITY_TIMEOUT_MS |
30000 |
NARAD_TOPIC_DEFAULT_MAX_IN_FLIGHT_PER_PARTITION |
1024 |
NARAD_TOPIC_DEFAULT_MAX_ACKED_AHEAD_PER_PARTITION |
1024 |
Fan-out¶
| Variable | Default | Notes |
|---|---|---|
NARAD_FANOUT_MAX_BATCH_RECORDS |
4096 |
Records per cursor batch |
NARAD_FANOUT_MAX_BATCH_BYTES |
4194304 (4 MiB) |
|
NARAD_FANOUT_LINGER_MS |
25 |
How long a partial batch waits to fatten up |
Logging & security¶
| Variable | Default | Notes |
|---|---|---|
NARAD_LOG_LEVEL |
info |
debug is chatty, in a good way |
NARAD_LOG_FORMAT |
json |
or text for humans |
NARAD_SECURITY_ENABLED |
true |
Secure by default; local dev can opt out |
NARAD_ADMIN_PASSWORD |
random, logged once | Seeds the root admin on first cluster start |
The config file (--config narad.json)¶
For everything not worth an env var — mostly the storage engine. The chart renders narad.config values into this file. Full shape with defaults:
Storage accepts exactly four keys — everything else (fsync mode, flush/sync cadence, segment sizing) is an engine internal with production defaults, and the loader rejects any attempt to set it:
{
"storage": {
"data_dir": "data",
"codec": "none", // "none" | "zstd" — yes, OFF by default
"compression_level": "fastest", // zstd: fastest | default | better | best
"idle_log_eviction_ms": 1800000 // close logs untouched this long; 0 disables
},
"http": { "...": "same knobs as the env vars" },
"topic": { "...": "same as env" },
"fanout": { "...": "same as env" },
"log": { "level": "info", "format": "json" },
"security": { "enabled": true }
}
Secrets (NARAD_CLUSTER_SECRET, NARAD_ADMIN_PASSWORD) are deliberately not file-configurable — files end up in git, and git ends up on the internet.
Idle topics cost (almost) nothing¶
An open partition log holds two goroutines, open file descriptors, and
buffers — and topics people create and abandon would hold them forever.
So Narad closes any partition log untouched for idle_log_eviction_ms
(default 30 minutes) and reopens it lazily, invisibly, on the next
produce, consume, or replay. Details that make this safe:
- A topic that was never used opens nothing anywhere — creation is just a metastore entry.
- Metrics polls never keep a log warm (they observe without opening), and a fan-out child that is attached but silent doesn't either — its cursor checks the durable high-watermark file instead of the log. Committed backlog always forces the log open: correctness first.
- Eviction waits until retention has finished deleting aged segments, so closing never strands data the reaper still owes you.
- Watch it:
narad_open_partition_logsandnarad_idle_logs_evicted_total.
The upshot: creating short-lived topics and forgetting to delete them is rude but free. Deleting them is still nicer — metastore entries and the last active segment on disk stay until you do.
The fsync knob, honestly explained¶
"fsync": "batched" (the default) does not weaken the durability contract you care about: a produce is fsynced in the ingress WAL before its 202, and a partition commit is fsynced + CRC-verified before it's acknowledged back or made visible — always, in both modes. The knob only controls how eagerly background flusher batches hit disk between those hard points. per_write syncs every flushed batch; it buys you almost nothing and costs you a lot of IOPS. Leave it.
Tuning cheat sheet¶
| You want | Change |
|---|---|
| Less disk | codec: zstd (seriously, just turn it on) |
| Longer long-polls | max_consume_wait (and raise write_timeout to match) |
| Fatter fan-out batches on slow disks | raise fanout.linger_ms |
| Faster delay-child metadata refresh | you don't; the engine self-paces (30s max wake) |
| More retention granularity | smaller segment_bytes — more files, finer reaping |