Skip to content

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 (none) 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 (none) 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_logs and narad_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