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 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_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