Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

ADR-003: Redis as State Store + NATS as Message Bus

  • Status: Accepted
  • Date: 2026-01-10

Context

Cordum needs:

  1. Durable state — job metadata, workflow runs, worker tracking, config, sessions
  2. Pub/sub messaging — job dispatch, heartbeats, event streaming, workflow triggers
  3. Crash recovery — services must resume from last-known state after restart

A single system (e.g. PostgreSQL for both) would simplify operations but create coupling between state queries and message flow.

Decision

Split responsibilities:

  • Redis — primary state store for all mutable data (jobs, runs, config, sessions, context pointers)
  • NATS JetStream — message bus for event delivery, job dispatch, and streaming

Why Redis for State (Not PostgreSQL)

ConcernRedisPostgreSQL
LatencySub-ms reads1-5 ms typical
SchemaSchemaless (JSON, sorted sets)Requires migrations
Ops complexitySingle binary, minimal configWAL, vacuuming, extensions
ScalingRedis Cluster or SentinelRead replicas, connection pooling
Job state TTLNative key expiryRequires cron/cleanup jobs

Redis fits the access pattern: high-frequency reads/writes of small JSON objects with TTL-based lifecycle. Job metadata, worker heartbeats, and config lookups benefit from sub-millisecond latency.

Why NATS JetStream for Messaging

ConcernNATS JetStreamRedis Pub/Sub
DurabilityFile-backed streamsFire-and-forget
Consumer groupsBuilt-inRequires Streams API
ReplayConfigurable retentionNo replay
Back-pressureFlow controlNone

NATS JetStream provides durable, replayable message delivery needed for job dispatch (at-least-once), workflow step triggers, and event streaming.

Key source files:

  • core/infra/store/job_store.go — Redis job state
  • core/controlplane/scheduler/engine.go — NATS pub/sub for dispatch
  • config/nats.conf — JetStream configuration

Persistence Configuration

Redis: AOF + RDB snapshots (configurable via redis-server --appendonly yes) NATS: JetStream file store with configurable sync_interval for durability vs throughput

Consequences

Positive:

  • Clear separation of concerns (state vs messaging)
  • Each system optimized for its workload
  • Independent scaling and failure domains
  • Sub-millisecond state lookups on scheduler hot path

Tradeoffs:

  • Two infrastructure dependencies to operate
  • No cross-system transactions (eventual consistency between state and events)
  • Redis persistence requires operator attention (AOF rewrite, memory limits)