Why architecture matters here
Durability has a hard floor set by hardware. A commit is a promise: once the client sees success, the data survives a crash. The only way to keep that promise is to make the log record physically present on media that outlives a power loss, which means an fsync (or an O_DSYNC write, or a device flush) that does not return until the storage controller has the bytes in non-volatile cells or battery-backed cache. That call has a latency floor measured in hundreds of microseconds to single-digit milliseconds. Serialize commits behind it and your maximum commit rate is fixed by that number alone — a database on hardware capable of a million random IOPS can be throttled to two thousand commits per second purely because each commit waits for its own flush.
The insight that breaks the ceiling is that one flush can make many records durable. Storage does not charge per logical record; it charges per flush operation and per byte. Ten transactions whose commit records occupy a few kilobytes in the same log page can all be made durable by the same fsync that would have served one. So the architectural move is to decouple appending a commit record (cheap, in-memory, per-transaction) from flushing the log (expensive, shared). Group commit is the coordination layer that lets independent committers rendezvous on a shared flush without any of them knowing about the others in advance.
Three properties fall out of getting this right. Throughput scales with contention: the busier the system, the bigger the groups, so exactly when you need throughput most, the amortization is best — group commit has a self-tuning quality. Ordering is preserved: because records are appended to a single log in LSN order and the flush is monotone, the durable prefix of the log is always a valid, gap-free history, which recovery depends on. Latency stays bounded: a committer never waits longer than one in-flight flush plus its own turn, so tail latency is predictable rather than unbounded.
The cost is that group commit couples independent transactions' fates in the time domain. An early committer's response time now depends on how quickly a group forms and how long the shared flush takes — so a slow disk hurts everyone in the group at once, and a misconfigured wait window can add latency to a workload that would have been better served flushing immediately. Understanding the mechanics is what lets you keep the throughput win without paying an unnecessary latency tax.
The architecture: every piece explained
Top row: the committers. Each transaction that reaches its commit point has already written its data-change records to the in-memory WAL buffer; committing means appending a commit record at a log sequence number (LSN) and then ensuring the log up to that LSN is durable. Crucially, appending is a fast, lock-protected memory operation — many transactions can append in quick succession. What each committer needs before it can acknowledge the client is a guarantee that the flushed prefix of the log has reached or passed its own commit LSN.
Middle row: the commit queue and the group leader. Rather than every committer calling fsync, committers enter a queue. The engine designates one of them — typically the first to arrive when no flush is in progress — as the leader for the current group. The leader records the highest LSN currently in the buffer (the group's flush target), issues a single flush, and on completion wakes every waiter whose commit LSN is at or below the flushed LSN. Followers do no I/O at all; they simply wait to be released. In some designs (MySML/InnoDB's two-phase binlog group commit, PostgreSQL's commit_delay path) the leadership and batching are explicit; in others the same effect emerges from a mutex plus a condition variable guarding the log-flush position.
Bottom-middle row: the WAL buffer and the single fsync(). The buffer stages log records contiguously; the flush pushes the buffer's contents (up to the target LSN) through the OS page cache and forces the device. Because the log is a single append-only stream, one contiguous write covers every record in the group — sequential I/O, which is exactly what storage is fastest at. The flushed LSN advances monotonically; recovery later replays the log up to the last durable LSN, so the batch boundary is invisible after a crash — durability is all-or-nothing at the record level, never partial within a record.
Bottom row: the operational surface. Group commit exposes a handful of dials — the wait window (how long, if at all, a leader lingers to let more committers join), the flush method (fsync vs fdatasync vs O_DSYNC vs direct I/O), and the group-size cap. It also exposes signals: fsync latency, commit-queue depth, average group size, and the durable-LSN lag behind the append LSN. The diagram shows four committers collapsing into one queue, one leader, one buffer, and one flush — the essential shape regardless of which engine implements it.
End-to-end flow
Follow one commit through a busy OLTP database. Transaction T2 finishes its work and calls commit. Under the log mutex it appends its commit record to the WAL buffer at LSN 4,812 and reads the current flush state: a flush is already in progress (leader T?, target LSN 4,790). T2 is past that target, so it cannot ride the in-flight flush; it enqueues and waits. In the same microsecond, T3 and T4 append at 4,813 and 4,815 and enqueue behind T2.
The in-flight flush completes and wakes its group. Now a new leader is chosen — say T2, the head of the waiting queue. T2 reads the current append position: 4,815 (T4's record is the latest). That becomes the group's flush target. T2 issues one fdatasync covering everything up to 4,815. While that flush is outstanding, T5 and T6 arrive, append at 4,817 and 4,820, and queue for the next group. T2's flush is doing sequential I/O over a few kilobytes; it returns in, say, 300 microseconds.
On completion, T2 advances the durable LSN to 4,815 and wakes all waiters whose commit LSN is at or below 4,815 — that is T2, T3, and T4. All three now consider themselves durably committed; they release their locks, and the server sends commit acknowledgments to three clients from a single flush. The amortization is explicit: one fdatasync, three commits. Under heavier load the same flush might have released thirty committers instead of three, and the per-commit sync cost would have fallen tenfold without any configuration change.
Immediately, the queue is non-empty again (T5, T6, and newcomers), so a new leader is elected and the cycle repeats — flushes pipeline back-to-back, each one draining whatever accumulated during the previous flush. Now consider a crash right here: the server loses power after T2's flush but before T5's. On restart, recovery reads the log and finds a durable prefix ending at LSN 4,815. T2, T3, and T4 are replayed and their effects restored; T5 and T6, whose records never reached the durable prefix, simply never happened — their clients never received an acknowledgment, so no promise was broken. The batch boundary that existed at runtime is gone; recovery sees only a clean, gap-free log prefix.