jonnify·redis /redis-patterns/caching/write-behind/durability

R1.03.2 · Write-behind — dive 2

The durability window.

Between a write and its flush, the cache holds the only copy. That window is the price of the throughput — the flush interval sets its width, and durable storage sets whether a crash in it loses anything.

Grounding: echo_store/journal.ex (the SQLite WAL, replay/2, compact/1) · bcs.4.md (the measured price) — verbatim. Engine: Valkey.

Two flushes, one window. Select a zone of the timeline to read what it holds and what a crash there costs.
time → flush next flush flushed in the database at risk cache only
readout · Select a zone. Left of a flush is durable in the database; the window between flushes exists only in the cache until the next flush.
§1

The unflushed window

write-behind.md.txt · The Durability Trade-off

The trade is the inverse of write-through. Write-through writes the database on the request, so the source is always current and a cache loss costs nothing. Write-behind defers that write, so between a write and the next flush the database is behind and the cache holds the only copy of those changes. If the cache is lost in that window — a crash, a failover, an eviction — the unflushed writes are gone.

The window’s width is the flush interval. With a one-second interval and a steady stream, roughly one second of writes is at risk at any moment; with a five-second interval, five seconds is. Narrowing the interval narrows the window but shrinks the batches, which is exactly the throughput the pattern was bought for. The choice is a dial, not a default.

durability window
The span from a write landing in the cache to its flush. In it, the cache holds the only copy of that write.
at-risk writes
Everything written but not yet flushed. A crash in the window loses exactly these.
flush interval
The dial. Shorter narrows the window and the loss; longer batches more and risks more.
RPO
Recovery point objective — how much recent data a system may lose on a crash. Write-behind’s RPO is bounded by the window.

A second lever shrinks the window without touching the interval: flush on shutdown. A clean stop drains the buffer first, so a planned restart loses nothing — only an unclean crash hits the window.

§2

Persistence bounds the loss

write-behind.md.txt · Mitigating Data Loss

The flush interval bounds how many writes the window holds. Durable storage bounds whether a crash actually loses them. The cache can run purely in memory, snapshot to disk on a schedule (RDB), or append every write to a log (AOF). Each mode changes what survives a process restart, and so changes the real risk of the unflushed window. Pick a persistence mode to classify what a crash loses for the same buffered writes.

Pick a persistence mode. The readout classifies what a crash loses over a fixed buffered batch of 20 writes.
readout · Select a mode. The flush interval sizes the window; persistence sets whether a crash in it actually loses anything.

Two dials bound the risk: the flush interval sizes the window, and durable storage sets whether a crash in it actually loses anything.

§3

The window on EchoStore — the lane that remembers

journal.ex:103 · :125-126 · bcs.4.md

EchoStore does not put an append log on the cache. The bus stays volatile by decision; durability for the job lane’s obligations lives in a per-group SQLite WAL standing beside the bus (journal.ex:125-126 — PRAGMA journal_mode=WAL, synchronous=NORMAL). The outbox closes the window by construction: the intent is recorded before the bus hears it, and replay/2 re-enqueues every intent not yet covered by the applied memory after a bus restart. The bus already deduplicates a re-enqueued job id and newer-wins makes a re-applied version harmless, so replay is at-least-once and harmless by comparison.

The committed record prices the memory exactly. The writer’s edge costs 143 us per record-and-mark pair; the remembered-lane median is 524 us against the bare lane’s 148; and after a staged bus loss, replay re-enqueued exactly thirty uncovered intents in seq order.

the application · the remembered lane, measured

What the outbox costs, and what it buys

The price is committed, figure by figure, against the bare lane — from the BCS manuscript record:

bcs.4.md · EchoStore.Journal · the remembered lanefrozen
record-and-mark pair          143 us         ← the writer's edge
remembered-lane median        524 us         ← against the bare lane's 148
replay after a bus loss       exactly 30 uncovered intents re-enqueued in seq order
the trade                     3.5 times the latency buys an outbox, a last word
                              per name, and a replay that survives the bus
the trade-off

Deferring the source write opens a window where the cache holds the only copy. The flush interval sizes the window; durable storage bounds whether a crash in it loses anything.

on EchoStore

The intent is recorded in a SQLite WAL before the bus hears it, so replay/2 rebuilds the lane after a bus restart — 143 us per record-and-mark, 524 us end to end, the outbox the cost buys.

Notes on Valkeythe journal’s SQLite runs at synchronous=NORMAL, where the checkpoint is the only sync barrier; the trade is that a machine power loss may trim the unsynced WAL tail — every process and bus crash this part stages is fully covered. The plain-cache analogue is SET … PX with persistence configured (valkey.io/commands/set).

Write-behind is honest only when the window is named and bounded: a known interval, a known persistence story, and a change that can afford the loss.

§4

Recap — name the window, bound it

the window → the coalescing

The throughput of write-behind is paid for by a window where the cache holds the only copy of the unflushed writes. The flush interval sets the window’s width and the count at risk; durable storage — in-memory, RDB, AOF, or EchoStore’s outbox plus replay — sets whether a crash in the window loses them. Buffer only changes that can afford the loss, and the trade is sound. The next dive shows how to make a burst of writes to one key cost a single flush.

Next in R1.03: R1.03.3 · Coalescing writes — many updates to one key between flushes write the database once.

§5

References

sources · related

Sources

Related in this course