Profiled on an SSD, eight page batches, comparing write_pages_with durable true against durable false:
- commit with sync: 2.982 ms
- same commit without the sync: 0.241 ms
- the sync alone: 2.741 ms, about 92 percent
That explains why every attempt to shave per group CPU or syscalls failed to move throughput, including coalescing the three writes per record (#63) and the various pacing and locking experiments on #46. There is almost nothing else in the commit to win back.
Also ruled out: the four MiB zero fill ahead was suspected of leaving a dirty backlog that every later sync pays for. Sweeping it says otherwise, smaller is worse. 4 MiB gives 2.86 ms, 256 KiB gives 5.98 ms, 64 KiB gives 3.26 ms. Leave it alone.
With the sync fixed and dominant, write throughput is group size divided by sync time, so the only remaining lever is fitting more commits behind each sync. Our commit worker is strictly serial: it collects a batch, appends its records, syncs, replies, and only then starts collecting again. Nothing is appended to the log while a sync is in flight, so a group can never be larger than the number of clients that happened to be waiting when it started.
Postgres does not have that limit. Its backends write their own log records while another backend is flushing, and one flush covers everyone who arrived during it, so its effective group grows with flush latency instead of being capped by it.
The change is to let appending continue during a sync: a writer that appends records and tracks a durable LSN, with commits waiting for a sync at or past their own LSN rather than owning the sync themselves. That is a real restructuring of group_commit.rs and should be measured against the 1cpu profile first, which is only 14 percent behind.
Profiled on an SSD, eight page batches, comparing write_pages_with durable true against durable false:
That explains why every attempt to shave per group CPU or syscalls failed to move throughput, including coalescing the three writes per record (#63) and the various pacing and locking experiments on #46. There is almost nothing else in the commit to win back.
Also ruled out: the four MiB zero fill ahead was suspected of leaving a dirty backlog that every later sync pays for. Sweeping it says otherwise, smaller is worse. 4 MiB gives 2.86 ms, 256 KiB gives 5.98 ms, 64 KiB gives 3.26 ms. Leave it alone.
With the sync fixed and dominant, write throughput is group size divided by sync time, so the only remaining lever is fitting more commits behind each sync. Our commit worker is strictly serial: it collects a batch, appends its records, syncs, replies, and only then starts collecting again. Nothing is appended to the log while a sync is in flight, so a group can never be larger than the number of clients that happened to be waiting when it started.
Postgres does not have that limit. Its backends write their own log records while another backend is flushing, and one flush covers everyone who arrived during it, so its effective group grows with flush latency instead of being capped by it.
The change is to let appending continue during a sync: a writer that appends records and tracks a durable LSN, with commits waiting for a sync at or past their own LSN rather than owning the sync themselves. That is a real restructuring of group_commit.rs and should be measured against the 1cpu profile first, which is only 14 percent behind.