0005: Postgres WAL is received by Keeper itself, not pg_receivewal¶
Date: 2026-10-07 · Status: accepted
Context¶
The design names pg_receivewal on a physical slot for the change stream and promises "a kill at any point
repeats at most one segment and never skips one". pg_receivewal reports a segment as flushed to the server (so
the slot advances) as soon as it is on the streamer's local disk. A streamer killed after that but before the
upload to S3 would restart with an empty emptyDir, start from the advanced slot position, and the segment would be
lost: a silent gap.
Decision¶
The streamer speaks the replication protocol itself (github.com/jackc/pglogrepl, START_REPLICATION ... PHYSICAL
on slot keeper_<namespace>_<target>). WAL is buffered in memory one segment at a time. A complete segment is
uploaded (zstd + age), its summary is written, and stream/position.json is advanced. Only then does the standby
status update report the segment end as flushed. The slot therefore retains everything that is not committed in
S3. The growing segment is uploaded as a "partial" snapshot every pitr.partialInterval (default 60 s), so recent
seconds are restorable on quiet databases. Segments touch the work directory only for pg_waldump (summaries) and
are deleted right after. pg_receivewal stays in the image for operators.
Consequences¶
No gaps on kills. The memory per streamer is about one WAL segment (16 MiB). A timeline switch on the source
(promotion) or a recreated database (new system identifier) marks the chain broken and triggers a new full backup.
max_slot_wal_keep_size still bounds what a dead streamer can retain on the server.