The backfill runs in chunks of a fixed row count, one transaction per chunk, with a pause between them.
Rejected alternative: the single-pass version, which is recorded in backfill-in-one-pass as a dead end rather than deleted, because it is the approach anyone would reach for first.
What decided it is not throughput — the chunked version is slower in wall-clock time and that is fine. It is that each chunk is independently restartable, so an interrupted backfill resumes rather than starting again, and no transaction is open long enough to block vacuum.
The pause between chunks exists to leave headroom for production traffic rather than for any correctness reason. It was set by watching write latency while the job ran, not calculated.
One thing worth stating because it was nearly missed: the chunking key has to be stable. Chunking by a column that rows can move between means a row can be skipped or done twice, and "done twice" is only harmless if the write is idempotent. It is here. It would not be for a counter.