Group Commit
also called Commit Batching, Log Flush Batching
Making many concurrent transactions durable with a single log flush, so the fixed cost of an fsync is amortised across a batch rather than paid per transaction.
A database that acknowledges a write only after the log is on durable storage has one unavoidable serial cost: the flush. An NVMe device completes it in roughly 50–200 microseconds, a network-attached block volume in roughly 0.5–2 milliseconds. One flush per transaction therefore caps commits at about 10,000 per second locally and about 1,000 per second on network storage, regardless of how many cores the machine has.
Group commit breaks that cap without weakening durability. Transactions that arrive while a flush is in progress are appended to the log buffer and made durable by the next flush, so one fsync acknowledges the whole batch. Fifty transactions per flush turns a 100-microsecond sync into a 2-microsecond per-transaction cost, and the binding constraint moves from the device to log bandwidth and CPU.
Why it matters
It is the reason write throughput depends on client concurrency, which is the least intuitive fact in database capacity planning. Ten serial clients give one commit per round trip and no batch to form; five hundred concurrent clients batch well. The same server, the same storage and the same statements produce an order of magnitude difference in commits per second.
This has direct consequences. A load test with low concurrency reports a pessimistic ceiling and triggers a sharding discussion that the data does not support. A connection pooler raises write throughput without any change to the database, because it increases the number of transactions in flight. And a "slow writes" complaint from a single-threaded importer is not a database problem at all.
Implementation patterns
- Tune the flush delay deliberately. MySQL's binary-log group commit and Postgres's
commit_delayboth let the flusher wait briefly to gather a larger batch, trading a little latency for a lot of throughput under concurrency. - Raise concurrency before raising hardware. Batch inserts into one transaction, pool connections, and parallelise the writer where ordering allows.
- Keep the log on the fastest local device and separate from data files, so the one serial cost is as small as possible.
- Measure flush size, not just flush latency. Transactions per flush is the metric that says whether batching is happening at all.
- For replication, remember the second queue: synchronous replication adds a network round trip to each batch, so the batch size that is optimal locally is often too small once a remote acknowledgement is involved.
Industry example
Every mainstream durable store does a version of this. MySQL's InnoDB has grouped commits since its early releases, and MySQL 5.6 (2013) added binary-log group commit so that replication did not serialise them again. PostgreSQL exposes commit_delay and commit_siblings to control the wait. Distributed logs do the same at a different layer: a Kafka producer's linger.ms is group commit for the network - wait a few milliseconds, send one larger batch, pay one round trip.
Failure scenarios
- A low-concurrency benchmark sets the capacity plan, and the team shards a database that had an order of magnitude of headroom.
- A flush delay tuned for peak hurts the quiet hours, where each transaction waits for a batch that never forms. Latency rises at low load, which is the opposite of what operators expect.
- Asynchronous commit chosen to avoid the sync silently converts a durability guarantee into a window of acknowledged-but-lost transactions, discovered after a power loss.
- Log device contention. Putting the log on the same volume as data makes the one serial cost compete with page writes, and the batch size no longer helps.
Trade-offs
What is gained is throughput proportional to batch size, at no cost to durability. What is paid is latency for the first transaction in each batch - it waits for the flush it triggered plus any deliberate delay - and a capacity model that now depends on a client-side property. There is also an honest operational cost: two more knobs whose correct values differ between a bulk import and a transactional peak.
When not to use it
Do not add a deliberate flush delay to a workload that is latency-sensitive and low-concurrency, such as a leader election or a lock service where each commit is on the critical path of one caller and there is no batch to gather. The rule: tune for batching when many independent writers are concurrent; leave the delay at zero when writes are serial, and solve the throughput problem by making them concurrent instead. Where the data genuinely tolerates loss - telemetry, click events - asynchronous commit or an unlogged table is the cheaper answer than optimising a durability guarantee nobody needs.
Interview question
Q: A service's writes are capped at about 900 per second against a database on network storage, and the team wants to shard. What do you measure first, and what would convince you that sharding is the wrong answer?
What a strong answer covers: measuring transactions per log flush and the number of concurrent writers before anything else; recognising 900 per second as suspiciously close to the unbatched network-storage ceiling; testing with a pooler and higher concurrency to see whether batching forms; separating index write amplification from commit cost; and only then arguing that sharding is justified when writes or working set exceed one machine rather than when the client is serial.
Quick check
Quiz: Why can the same database commit ten times more transactions per second from 500 clients than from 10? Because group commit amortises one fsync over all transactions in flight, and ten serial clients never produce a batch.
Flashcard: Planning numbers for a commit ceiling without batching? Roughly 10,000 per second on local NVMe (50–200 microsecond sync) and roughly 1,000 on network block storage (0.5–2 millisecond sync).