jonnify·redis /redis-patterns/production-operations/persistence-pooling-failover

R8.02 · Production & Operations — module hub

Persistence, pooling & failover.

Operating the bus as a record of truth in production is three guarantees, not one: it must survive a restart, serve under load, and ride a failover. R7.01 made the bus the record of truth; this module keeps it one.

Applies R7.01 redis-as-primary-database — reference the pattern, focus on the operations. Grounding: infra/valkey/conf/valkey.conf (the persistence policy, verbatim) · EchoMQ.Pool (echo/apps/echo_mq/lib/echo_mq/pool.ex) · EchoMQ.Connector (echo/apps/echo_wire/lib/echo_mq/connector.ex). The engine is Valkey 9.

One record of truth, three production guarantees. Select a guarantee to light the real surface that delivers it; the readout names the move.
record of truththe JOB HASH (R7.01) survive restart valkey.confAOF everysec · save "" serve load EchoMQ.PoolN pipelines, 1 thread ride failover EchoMQ.Connectorbackoff · fail-not-replay
readout · Select a guarantee. The record survives a restart through the engine's persistence policy, serves under load through the connector pool, and rides a failover through the connector's reconnect discipline.

Static verdicts — restart → appendfsync everysec bounds loss to ~1s · load → EchoMQ.Pool hides RTT over one command thread · failover → EchoMQ.Connector reconnects on a jittered backoff and never replays an in-flight write.

§1

The record into production

applies R7.01 · primary-database

R7.01 made the case that the bus is the record of truth: the JOB HASH carries state, attempts, and payload, and Postgres holds only what must be transactional. This module takes that record into production and answers the operational question R7.01 left open — how does it stay one.

A record of truth is not a data model; it is a data model plus three operational guarantees. It must survive a restart so the data outlives the process, serve under load so one server feeds many callers without a head-of-line stall, and ride a failover so a dropped wire recovers cleanly. Each guarantee lives on a different real surface, and this module reads each one. The pattern is R7.01; the subject here is the operations.

the pattern

redis-as-primary-database in production: the record of truth must survive a restart, serve under load, and ride a failover — three guarantees that turn a data model into a durable, available record.

its EchoMQ application

valkey.conf (appendonly yes + appendfsync everysec + save "", the durability dial) · EchoMQ.Pool (N pipelined connectors hiding RTT over a single-threaded server) · EchoMQ.Connector (capped-jittered-backoff reconnect, in-flight fail-not-replay).

A record of truth is a data model plus three operational guarantees. The persistence policy keeps the record across a restart, the pool keeps it served under load, and the reconnect discipline keeps it reachable across a failover.

§2

Survive a restart — the persistence policy

valkey.conf · persistence block

The first guarantee is durability: the data outlives the process. The real valkey.conf makes the append-only log the single source of durability. appendonly yes records every keyspace-changing write to a log on disk; appendfsync everysec flushes that log on a background thread once a second, bounding crash loss to about one second of writes; save "" turns snapshotting off so it never competes for a second fork; aof-use-rdb-preamble yes keeps the rewrite fast. propagation-error-behavior panic fails a write rather than acknowledge data that may not survive, and maxmemory-policy noeviction rejects writes loudly under memory pressure instead of dropping a job — the R7.01 tie.

Dive 1 takes this block apart line by line. It is distinct from R8.01’s persistence-safe-settings, which is the kernel layer (the vm.overcommit_memory sysctl for the save fork); here it is the Valkey persistence policy itself. The two are halves of one posture — cross-reference, not duplicate.

infra/valkey/conf/valkey.conf · AOF is the single source of durabilityfrozen
# ---- Persistence: AOF is the single source of durability ----
appendonly yes
appendfsync everysec             ← ~1 s loss bound, fsync on a background thread
aof-use-rdb-preamble yes         ← compact preamble: a fast rewrite
save ""                          ← RDB off: one fork source, not two
dir /data
propagation-error-behavior panic   ← fail the write rather than ack lost data

Notes on ValkeyWith appendonly on and appendfsync everysec, the append-only file is replayed at startup to rebuild the in-memory dataset, and the loss bound after a crash is roughly one second of writes — the balance Valkey recommends, durability close to always at a fraction of its fsync cost — valkey.io/topics/persistence.

§3

Serve under load — the pool

EchoMQ.Pool · pool.ex

The second guarantee is throughput under concurrency. EchoMQ.Pool is a fixed pool of pipelined connectors with lock-free round-robin dispatch: next/1 advances an :atomics counter and a caller hits the next member; the default size: 4. The sharp production insight is the one most pool sizing gets wrong. Valkey runs commands single-threaded — the real valkey.conf sets io-threads 1 and its comment reads “command execution is single-threaded.” So a pool does not buy N× server parallelism — it hides round-trip latency by keeping the one command thread fed via pipelining.

Size for pipeline depth and RTT-hiding, not for cores; over-sizing only adds sockets the single thread serializes anyway. Dive 2 derives that insight. It is the same EchoMQ.Pool R6.05 teaches as the concurrency primitive — here it is sized for production rather than introduced.

echo/apps/echo_mq/lib/echo_mq/pool.ex · lock-free round-robinfrozen
# A fixed pool of pipelined connectors with lock-free round-robin
# dispatch. Each member is already a pipeline, so a small pool
# multiplies throughput without checkout ceremony.
size = Keyword.get(opts, :size, 4)

defp next(name) do
  {size, at} = :persistent_term.get({__MODULE__, name})
  member(name, rem(:atomics.add_get(at, 1, 1), size) + 1)
end

Notes on ValkeyA pipeline sends several commands without waiting for each reply, so the round-trip latency is paid once for the batch rather than per command — which is exactly what a single command thread needs to stay fed — redis.io/docs/…/pipelining.

§4

Ride a failover — the reconnect discipline

EchoMQ.Connector · connector.ex

The third guarantee is reachability across a disruption. When a primary fails over to a replica, a client that keeps writing to the old node gets a Valkey -READONLY reply — the signal that a failover happened. A resilient client must reconnect to the new primary. EchoMQ.Connector grounds the reconnect discipline: supervised reconnect with capped jittered backoff, re-fencing on every reconnect; in-flight callers failed with :disconnected on socket loss — never replayed, because the connector cannot know what is idempotent; and an idle heartbeat that PINGs a quiet wire so dead peers are noticed before the next caller pays for the discovery.

The shipped deploy is a standalone Fly node, so a failover here is a socket drop and a reconnect to the same restarted server; the discipline is the same one a Sentinel- or Cluster-aware client uses to re-resolve a new primary. Dive 3 is honest about the seam: the connector handles failover by socket-drop and reconnect, not by parsing -READONLY — the -READONLY reply is the signal (the pattern), the connector’s reconnect is the real grounding.

Notes on ValkeyA replica refuses writes and answers “You can’t write against a read only replica”; a client that gets -READONLY has been talking to a node that is no longer the primary, and should reconnect to the promoted one — valkey.io/topics/replication.

§5

Putting it together

one posture, three layers

The three surfaces are one posture seen from three layers. A restart hits the persistence policy: the append-only log replays and the record is whole. A load spike hits the pool: the four pipelined connectors keep the single command thread fed without one slow caller stalling the rest. A failover hits the connector: the socket drops, in-flight callers get a clean :disconnected, and the supervised reconnect re-fences and resumes. Because a pool member is an EchoMQ.Connector, the pool inherits the reconnect discipline for free.

the application · codemojex’s in-flight state, protected

Why a job store chooses this engine

codemojex keeps its game and JOB state in Valkey (the bus and the store) while money lives in Postgres — the R7.01 per-datum split. So Valkey’s AOF durability is exactly what carries in-flight game and JOB state across a restart, and the EchoMQ.Pool fronts the bot workers’ notify queue, where an EchoMQ.Consumer drains the emq:{q}: keyspace. The persistence, pool, and reconnect lines, side by side:

the production posture · three surfaces, one recordfrozen
valkey.conf      appendfsync everysec    ← survive a restart (~1 s loss)
EchoMQ.Pool      size: 4, :atomics      ← serve under load (RTT-hiding)
EchoMQ.Connector backoff + fail-not-replay ← ride a failover (at-most-once)

The EchoMQ protocol depth — the lane consumers, the leases, the Lua — is the dedicated /echomq course; this module stays on the operations.

§6

The three dives

restart → load → failover

Each dive takes one guarantee and the real surface that delivers it, in the arc survive → serve → ride: the engine’s persistence policy, the connector pool sized for a single-threaded server, and the connector’s reconnect discipline. Read them in order — durability starts.

Three dives, one record of truth: keep it across a restart, serve it under load, and reach it across a failover — the persistence policy, the pool, and the reconnect discipline working as one posture.

After R8.02 the chapter continues with the case studies — Pinterest, Twitter/X, and Uber — then the capstone hands off to the dedicated EchoMQ course. Return to the Production & Operations chapter to continue, open the EchoMQ Proof pillar for the production evidence, or Codemojex on Fly for where this valkey.conf actually runs.

§7

References

sources · related

Sources

  • Valkey — Persistence — the append-only file, the everysec fsync policy, and the roughly one-second loss bound after a crash.
  • Valkey — Replication — primary/replica roles and the -READONLY reply returned to a client after a failover.
  • Redis — Pipelining — why a pipeline hides round-trip latency on a single command thread, the premise behind pool sizing.
  • Valkey — Sentinel — automatic failover and how a resilient client re-resolves the new primary after a -READONLY reply.

Related in this course