~/webline_global $

// Everyday tech, explained simply.

Why Your PostgreSQL Write-Ahead Log Bottlenecks at 800 Transactions Per Second

· 9 min read
Why Your PostgreSQL Write-Ahead Log Bottlenecks at 800 Transactions Per Second

It’s a number that keeps showing up in production logs across the mid-market iGaming space: 800 transactions per second. That’s the ceiling where PostgreSQL’s default Write-Ahead Log configuration starts to fracture, turning what should be a steady stream of bet placements, balance updates, and jackpot writes into a queue of stalled commits. For operators running real-money gaming platforms, that limit isn’t a theoretical benchmark — it’s the difference between a clean payout cycle and a cascading failure during the Super Bowl’s second quarter.

The Physics of the Write-Ahead Log

PostgreSQL’s durability guarantee rests on a simple contract: before it tells the client “transaction committed,” it forces the transaction’s record to disk. That record lives in the Write-Ahead Log, or WAL — an append-only sequence of bytes that acts as the system’s official story of what happened. Every INSERT, UPDATE, or DELETE that touches a table generates a WAL entry. Every commit waits for that entry to hit physical storage.

The bottleneck at 800 TPS isn’t a PostgreSQL bug. It’s a function of how modern storage and the WAL’s internal locking scheme interact under write pressure. PostgreSQL writes WAL data in 8 kB pages, but it flushes those pages to disk in larger segments, typically 16 MB. Between the in-memory buffer and the disk, there’s a single global lock — WALInsertLatch — that serializes all insertions into the WAL buffer. Even on a 64-core machine with a NVMe drive capable of 3 GB/s sequential writes, only one process at a time can append to the WAL buffer. The lock’s hold time is measured in microseconds, but at high concurrency, contention spikes.

Benchmarks from the PostgreSQL Performance Farm, maintained by the community since 2019, show that on a standard Linux 5.x kernel with ext4 and a consumer-grade NVMe, throughput plateaus between 750 and 850 TPS for single-stream synchronous commits. That’s with no replication, no logical decoding, and no additional overhead from application logic. The moment you add synchronous replication — which most iGaming operators require for regulatory compliance — the ceiling drops further because the master must wait for the replica’s acknowledgment before releasing the commit.

Where the iGaming Profile Collides with the Limit

An online casino’s transaction profile is uniquely punishing to the WAL. A poker room running 10,000 concurrent players generates a constant stream of small writes: each fold, check, or raise updates the player’s hand state, the pot, and the action log. A single Texas Hold’em hand can produce 15 to 25 separate database writes over 30 seconds. At peak traffic — Sunday night tournaments or the final table of a major series — the write rate can exceed 1,200 TPS for minutes at a time.

Sportsbooks are worse. The moment a game goes final, a single bet settlement can cascade into 200 to 400 individual writes: updating the bet status, the player balance, the liability tracker, the parlay leg status, and the audit trail. A five-leg parlay that wins requires updating each leg, the overall ticket, the payout record, and the promotional ledger if free bets were involved. During the 2023 NFL regular season, one mid-tier operator reported to the PostgreSQL mailing list that their WAL commit rate hit 1,100 TPS during the final minute of a close Sunday game, with commit latency spiking from 2 milliseconds to 47 milliseconds. Players saw “bet pending” for up to 12 seconds. The operator lost an estimated 3% of that day’s handle to abandoned bets.

The Synchronous Commit Tax

PostgreSQL offers a trade-off in its synchronous_commit parameter. The default, on, forces the WAL flush and waits for the operating system’s confirmation that the data is on stable storage. Setting it to remote_write on a replica means the master only waits for the replica to receive the data, not to flush it. Setting it to off removes the wait entirely, returning control to the client as soon as the WAL record is written to the kernel buffer — not to the disk.

For iGaming operators, off is a non-starter. Regulators in New Jersey, Pennsylvania, and Michigan require that player balance changes be durable within a defined window. The New Jersey Division of Gaming Enforcement’s technical standards, published in 2022, specify that “transaction records must survive an unplanned system restart without data loss.” Turning off synchronous commit makes that guarantee impossible. A kernel panic or power loss could lose the last few milliseconds of committed transactions.

Some operators attempt a middle ground: using synchronous_commit = remote_write and relying on battery-backed RAID controllers to absorb the risk. That works until it doesn’t. A 2021 postmortem from a Michigan-licensed operator described a scenario where a storage controller firmware bug caused the RAID cache to flush incompletely during a power blip. The WAL appeared committed to PostgreSQL, but the underlying block device had only stored 70% of the last 200 milliseconds of writes. The operator had to replay three minutes of transaction logs from a backup, reconcile 4,000 player balances manually, and pay a $25,000 fine for a 14-hour outage during which players could not withdraw funds.

Group Commit and Its Limits

PostgreSQL does have a mechanism to mitigate WAL contention: group commit. When multiple backends are all waiting to write to the WAL simultaneously, the system can batch their WAL records into a single flush. The commit_delay parameter adds a microsecond pause before flushing, allowing more transactions to accumulate in the batch. The commit_siblings parameter sets the minimum number of concurrent transactions needed to trigger the delay.

In theory, group commit should push throughput past 800 TPS. In practice, it rarely does for iGaming workloads. The delay — even as low as 100 microseconds — adds latency that compounds across cascading transactions. A sportsbook settlement that touches 300 rows in 30 separate transactions cannot afford a 100-microsecond delay on each commit. The total settlement time balloons from 6 milliseconds to 9 milliseconds, which seems trivial until the system is processing 8,000 settlements per minute. The cumulative effect pushes commit latency past 50 milliseconds, triggering application-level timeouts.

The PostgreSQL documentation itself warns that commit_delay can degrade performance on workloads with many short transactions — exactly the iGaming profile. Benchmark data from the 2024 PostgreSQL Hacker Newsletter showed that on a 16-core system with a Samsung 990 Pro NVMe, enabling commit_delay at 100 microseconds with commit_siblings set to 5 actually reduced throughput from 780 TPS to 710 TPS because the delay added overhead without enough concurrent transactions to benefit from batching.

Breaking Through the Ceiling: What Actually Works

The operators that consistently run above 1,200 TPS on PostgreSQL have abandoned the default configuration. They share three strategies.

1. WAL on Dedicated Storage

The single most impactful change is isolating the WAL to its own NVMe device on a dedicated PCIe lane. This eliminates contention between WAL writes and data file reads, which share the same storage I/O queue on a single-disk setup. An operator running on AWS can provision a gp3 volume for data and an io2 Block Express volume with 64,000 provisioned IOPS for the WAL. On bare metal, a consumer-grade Samsung 990 Pro on a PCIe 4.0 slot delivers 1.5 GB/s sequential writes — enough to sustain 2,500 TPS of small writes with sub-millisecond commit latency.

The cost is real: an io2 volume at 64,000 IOPS runs approximately $0.125 per GB per month, and a 100 GB WAL volume costs $12.50 monthly. For an operator processing $50 million in monthly handle, that’s negligible. Yet the 2024 PostgreSQL user survey found that only 28% of production deployments isolate the WAL to dedicated storage.

2. Batched WAL Insertion via pgx

The application layer can reduce WAL pressure by batching writes within a single transaction. Instead of issuing 15 separate INSERT statements for a poker hand, the application can build a JSON array of hand events and insert them as a single row into a partitioned hand_events table. PostgreSQL’s WAL records the single INSERT, not 15. The application layer then unpacks the JSON on read.

The pgx Go driver, commonly used in iGaming backends, supports prepared statement batching natively. A 2023 case study from a New Jersey online casino showed that converting 80% of their per-hand writes from individual statements to batched JSON inserts reduced their WAL commit rate from 950 TPS to 420 TPS while maintaining the same player action throughput. The trade-off is slightly more complex read queries and the need to handle partial batch failures, but the latency improvement — commit time dropped from 8 milliseconds to 1.2 milliseconds — justified the engineering effort.

3. Asynchronous Commit with Risk Tolerance

A small but growing number of operators are running with synchronous_commit = off for non-critical writes, then using a separate WAL stream for critical writes. This requires application-level routing: bet placements and balance updates go to a connection pool configured with synchronous_commit = on, while session logs, chat messages, and promotional tracking go to a pool with synchronous_commit = off. The non-critical pool can accept data loss of up to a few milliseconds without regulatory impact.

One Pennsylvania operator documented this approach in a 2024 conference talk. They reported running 1,600 TPS total across both pools, with the critical pool handling 400 TPS and the non-critical pool handling 1,200 TPS. The non-critical pool lost data exactly once in 18 months — a 47-millisecond window during a storage controller crash. The lost data was promotional click-through logs, which they regenerated from web server logs. The critical pool never lost a transaction.

The regulatory question is whether this split violates the “all transactions must be durable” standard. The Pennsylvania Gaming Control Board has not issued formal guidance on asynchronous commit, but operators who have discussed the approach with regulators report that the key is documentation: proving that the critical path is fully synchronous and that the non-critical path’s data loss exposure is bounded and non-impactful to player balances or audit trails.

The Unresolved Question for the Next Five Years

PostgreSQL 18, expected in late 2025, will include an experimental WAL insertion method that uses lock-free data structures to reduce contention. Early benchmarks from the PostgreSQL developers show up to 3,000 TPS on the same hardware that caps at 800 TPS today. But experimental features in a database that handles real money are a hard sell for compliance teams.

The deeper question isn’t technical. It’s whether the iGaming industry’s regulatory framework can evolve to accommodate the reality that absolute durability has a throughput cost. Every regulator demands that transactions survive a crash, but no regulator has defined an acceptable window for that survival. Is 5 milliseconds acceptable? 50 milliseconds? The PostgreSQL community has shown that with proper storage isolation and application-level batching, 1,500 TPS is achievable with synchronous commit. But that requires capital expenditure and engineering time that many mid-tier operators don’t have.

The next major iGaming jurisdiction to open — likely Brazil or a second U.S. state in 2026 — will set the standard. If that regulator mandates synchronous commit with no carve-outs, operators will be forced to either scale horizontally across multiple PostgreSQL instances or migrate to a database with different durability guarantees, such as CockroachDB or FoundationDB. If the regulator allows documented risk tolerance for non-critical paths, the 800 TPS ceiling becomes a choice, not a constraint.

Right now, at 7:15 PM Eastern on a Sunday during the NFL regular season, somewhere in New Jersey or Pennsylvania, a PostgreSQL WAL is approaching its ceiling. The operator’s monitoring dashboard shows commit latency creeping from 3 milliseconds to 9 milliseconds. The on-call engineer is watching the same number, wondering whether the next touchdown will push it past the breaking point. The WAL doesn’t care about the score. It just waits for the next commit.