Storage Engine¶
Every partition is a directory owned by exactly one node. The engine underneath is a compressed, CRC-checked, segmented log with a visibility watermark and a retention reaper.
On disk¶
topics/orders/p00003/
├── 00000000000000000000.log ← sealed segment (starts at offset 0)
├── 00000000000000450832.log ← active segment (starts at offset 450832)
├── hwm ← 8-byte high-watermark
└── consumer.offset ← 8-byte committed consumer frontier
flowchart LR
subgraph segment file
F1["frame: header + CRC<br/>zstd(records 0..k)"]
F2["frame: header + CRC<br/>zstd(records k+1..m)"]
F3[...]
end
F1 --- F2 --- F3
- Segments are capped at 64 MiB; an append that would overflow seals the segment and rolls a new one named by its first offset. Sealed segments are immutable — the unit of retention deletion.
- Frames are the write unit: all records drained in one flush become one frame — length-prefixed records, compressed together (zstd by default), CRC over the stored bytes. Frame size therefore tracks batch size: trickle traffic gives per-record frames (~40% compression on JSON-ish payloads); busy traffic gives multi-hundred-record frames (~95%+, since similar records compress against each other).
- Records carry the keyed envelope:
[version][key][commit-time][payload]. Commit time is assigned under the partition lock, so it is monotonic per partition — the property the delay gate relies on.
Write path: buffer → flush → sync¶
Appends go into an in-memory buffer; a flusher goroutine drains it into frames and writes them out; fsync policy is configurable (per-write or batched). The commit path bypasses the leniency: Log.Sync() forces drain + fsync synchronously, then re-reads and CRC-verifies the new frames, and only then advances the high-watermark. Buffered data lost in a crash was, by construction, never acked to anyone.
The high-watermark and the hidden tail¶
The HWM is the exclusive bound of what consumers may see. It advances in memory at commit and is persisted (single-sector atomic write + fsync) at a bounded interval. Recovery trusts the persisted file, clamped to the recovered tail — which creates a deliberate artifact:
flowchart LR
A["offsets 0 .. H-1<br/>visible"] --> B["offsets H .. T-1<br/>hidden tail after crash"]
B --> C["offset T = next append"]
Records above the persisted HWM after a crash — fsynced but never re-exposed — stay hidden on purpose: for produce-path records, the ingress WAL re-commits them at fresh offsets (its checkpoint never passed them), so exposing the hidden copy would double-deliver. New commits append past the hidden tail and advance the HWM over it, at which point the duplicates become visible — duplicates, never loss, and only around crashes.
Recovery¶
Opening a log scans segments for the valid frame extent:
- A torn tail in the active segment (crash mid-write) is truncated — those bytes were never acked.
- Mid-file corruption under valid later frames is not truncated (that would destroy acked data and regress offsets); it fails loudly at open, and unreadable single records are skipped at consume time with an explicit counter — recorded loss, never silent.
Retention¶
A per-partition reaper sweeps every minute and deletes sealed segments whose last write is older than the topic's retention_ms. Granularity is the segment: data lives until its whole 64 MiB segment ages out, so real retention oscillates between retention and retention + one segment's fill time. Deletions export bytes/messages counters (reason="age").
The consumer frontier (consumer.offset, atomic temp-file rename, ~100ms cadence) is recovered lazily when a partition's queue state is first touched — from the file on disk, deliberately not from a boot-time metastore scan, so a stale replica at startup can't misplace consumption progress.
The frame format, byte by byte¶
From storage/format.go, and yes, the magic is 0xCAFE:
offset 0 magic 2B 0xCA 0xFE
offset 2 flags 1B bits 0-2: codec (0=none, 1=zstd)
offset 3 recordCount 4B big-endian int32
offset 7 baseOffset 8B big-endian int64 — first record's offset
offset 15 uncompressed 4B payload size before codec
offset 19 compressed 4B payload size on disk
offset 23 crc32c 4B Castagnoli over header[2:23] + payload
offset 27 payload [len:4BE][record bytes] × recordCount, codec-encoded
The CRC deliberately excludes the magic so the recovery scanner can hunt for 0xCAFE cheaply when resynchronizing past a torn region. A 256 MiB maxFrameBytes bound is enforced on both write and read, so a corrupt header can never talk recovery into allocating the moon.
Inside each record sits the keyed envelope (storage/keyed_record.go):
[version:1B = 0x02][keyLen:uvarint][key][committedAtUnixMs:8B BE][payload]
The commit timestamp is assigned under the partition produce lock — that's the per-partition monotonicity the delay gate stands on.
The flusher pipeline, with its actual knobs¶
flowchart LR
A["Append/AppendBatch<br/>(in-memory buffer)"] --> D["drain: flush_bytes 1MiB<br/>flush_records 1000<br/>flush_interval 100ms"]
D --> F["one frame per drain<br/>(codec applied here)"]
F --> W[write to active segment]
W --> S["fsync: batched mode syncs on<br/>sync_interval 1s / sync_bytes 8MiB /<br/>segment roll / Close / explicit Sync()"]
Two things make this safe rather than sloppy:
- The commit path doesn't negotiate.
Log.Sync()(called bycommitDurableon every produce commit) synchronously drains, writes, and fsyncs — the lazy timers above only govern data nobody has been promised yet. - Reads are self-verifying. Every frame decode re-checks the CRC;
VerifyDurablere-reads the just-written frames before the high-watermark moves. Decoded frames and frame positions are cached (frameCache,navCache), both invalidated under the write lock when retention deletes a segment.
Long-poll wiring, since everyone asks¶
An idle consumer isn't polling: it parks on the partition log's broadcast channel (Log.NotifyC). The channel is closed to broadcast — commit, lease expiry, and nack all notifyAll(), every parked waiter wakes, re-checks, and either grabs a message or parks on the fresh channel. Zero timers, zero missed wakeups (a waiter that raced the close sees the already-closed channel immediately).