Checkpoint in DBMS — Why 60s Timeout Killed Production I/O
A 60-second checkpoint_timeout spiked disk to 100% and latency from 5ms to 5s.
20+ years shipping production systems from the metal up. Written from production experience, not tutorials.
- ✓Deep production experience
- ✓Understanding of internals and trade-offs
- ✓Experience debugging complex systems
- Checkpoint writes dirty pages from buffer pool to disk and updates WAL with a redo start point
- Reduces crash recovery time from hours to seconds by avoiding full WAL replay
- Sharp checkpoints write all dirty pages synchronously (slow but clean); fuzzy checkpoints spread writes over time (faster but complex)
- In PostgreSQL, checkpoint_timeout and checkpoint_completion_target control frequency and I/O impact
- Production gotcha: too frequent checkpoints cause I/O storms; too infrequent cause long recovery
Imagine you're doing a 500-piece jigsaw puzzle and you stop every hour to take a photo of your progress. If the dog jumps on the table and scatters everything, you restart from the photo — not from the empty table. A database checkpoint is exactly that photo: a confirmed 'safe point' on disk so that after a crash, the database only has to redo work done after the last photo, not replay every single move since it was first installed.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Every production database — Postgres, MySQL InnoDB, Oracle, SQL Server — will crash at some point. Power cuts happen. Kernel panics happen. Someone trips over the wrong cable. The question isn't if your database will die mid-transaction; it's how fast it can get back on its feet with zero data loss. That answer lives almost entirely in one mechanism: the checkpoint.
Without checkpoints, crash recovery means replaying every single log record ever written — potentially years of transactions — before the database can accept a single query. That would make restarts take hours or days. Checkpoints solve this by periodically writing all dirty pages from memory to disk and recording a 'you can start recovery from here' marker in the write-ahead log. That single act turns a potential multi-hour recovery into seconds.
By the end of this article you'll understand exactly what happens inside a checkpoint cycle, the difference between sharp and fuzzy checkpoints and why fuzzy ones exist, how the WAL and buffer pool interact during checkpointing, what the performance trade-offs look like in PostgreSQL's real configuration knobs, and the production gotchas that bite engineers who treat checkpoints as a background detail.
What Is a Checkpoint and Why Does It Exist?
A checkpoint in a DBMS is a synchronization point where the buffer pool's dirty pages (modified but not yet on disk) are flushed to the data files, and the WAL is updated to mark that all changes up to that point are safely stored. This drastically shortens crash recovery: instead of replaying the entire WAL from the beginning, the database can start from the most recent checkpoint. The checkpoint record in the WAL contains the LSN (Log Sequence Number) that acts as the new recovery start point.
Checkpoints are not optional – every ACID-compliant database implements them. Without checkpoints, recovery time would grow linearly with the age of the database. A ten-year-old database would need to replay ten years of transactions after each restart. That simply doesn't work at scale.
- Dirty pages = unsaved progress; checkpoint = save to disk.
- WAL = a journal of every action; checkpoint = a bookmark in the journal saying 'everything before this is safe'.
- Recovery scans the WAL from the checkpoint forward, not from the beginning — that's the speed gain.
- Without checkpoints, recovery time equals database age. With them, it's bounded by the checkpoint interval.
WAL and Checkpoint Interaction: The Recovery Handshake
The Write-Ahead Log (WAL) records every change before it's applied to data files. A checkpoint ensures that all WAL records up to a certain LSN have been fully applied to the data files on disk. After checkpoint, the WAL can be truncated up to that LSN. During recovery, the database reads the last checkpoint location from the WAL and replays all subsequent records. This is called the redo phase. Some databases also have an undo phase to roll back incomplete transactions.
The key insight: the checkpoint record itself is written after all dirty pages are safely on disk. If the checkpoint record is missing, recovery falls back to the previous checkpoint. That's why checkpoint writes are synchronous and include a WAL flush. The checkpoint writes use direct I/O to ensure persistence.
Sharp vs Fuzzy Checkpoints: The Performance Trade-off
Sharp checkpoints flush all dirty pages at one moment. This is simple but causes a massive I/O spike. Fuzzy checkpoints (used by PostgreSQL, MySQL InnoDB) spread the flushing over a configurable window. They start writing dirty pages early in the interval and try to complete before the next checkpoint. The WAL checkpoint record marks the boundary, but data writes are asynchronous.
In PostgreSQL, checkpoint_completion_target controls how much of the interval is used for flushing. A value of 0.9 means 90% of checkpoint_timeout is spent writing dirty pages, with the final 10% reserved for the sync. This avoids a sudden burst but requires a steady I/O capacity.
Sharp checkpoints still exist in some systems (e.g., SQL Server simple recovery model), but modern systems prefer fuzzy for stability.
- Sharp = sprint: all writes at once, I/O spike, fast but painful.
- Fuzzy = steady jog: writes spread over minutes, smooth I/O, easier on disk.
- Completion target = your pace plan: how much of the interval you use.
- Database replay doesn't care about fuzziness — it only looks at the LSN boundary.
Checkpoint Tuning in PostgreSQL: Real Knobs and Constraints
PostgreSQL provides several knobs to control checkpoint behaviour. The most important are:
- checkpoint_timeout: Maximum time between automatic checkpoints (default 5 minutes).
- max_wal_size: Target for total WAL size. When exceeded, a checkpoint is forced.
- min_wal_size: Minimum WAL size to keep for recycling.
- checkpoint_completion_target: Fraction of checkpoint_timeout spent flushing dirty pages.
- checkpoint_flush_after: Number of pages after which to flush writes to OS.
Understanding the interaction: checkpoints can be triggered by time (timed checkpoints) or by WAL size (requested checkpoints). The pg_stat_bgwriter view shows how many of each occurred. A high ratio of requested to timed means the WAL size is often hitting max_wal_size — that can signal write bursts or insufficient max_wal_size.
Common Checkpoint Mistakes and How to Fix Them
Even experienced DBAs misconfigure checkpoints. The most common issues:
- Using default settings in production: Default checkpoint_timeout=5min may be fine for dev but causes I/O spikes in heavy-load production. Always baseline I/O and adjust.
- Ignoring full_page_writes: PostgreSQL writes full page images to WAL during first modification after a checkpoint. Disabling this saves space but risks page corruption on crash. Only disable on replicas with data checksums.
- Setting checkpoint_completion_target >0.95: Leaves no buffer for OS I/O delays. A checkpoint that doesn't complete before the next timeout triggers a forced sync, causing a sharp I/O spike.
- Not monitoring buffers_backend: If backend processes are writing dirty pages themselves, the checkpointer is overwhelmed. Increase checkpoint_timeout or add I/O capacity.
Checkpoint Impact on Recovery Time: Why a 5-Minute Checkpoint Can Mean 30-Minute Recovery
When a junior asks why recovery takes so long after a crash, the answer isn't magic—it's checkpoint frequency. Every dirty page not yet flushed to disk must be replayed from the WAL during recovery. The checkpoint record is the starting line for that replay. If your checkpoint interval is 5 minutes, you risk replaying up to 5 minutes of transactions. In high-throughput systems, that's gigabytes of WAL. Recovery time grows linearly with the distance from the last checkpoint. The real pain hits when you have a burst of writes just before the crash—your last checkpoint can be minutes old, and the system spends that entire time scanning and replaying. The fix isn't simply cranking up checkpoint frequency; that creates its own overhead. You need to match your checkpoint interval to your acceptable recovery time objective (RTO). A rule I've learned the hard way: if you can't afford 10 minutes of recovery, don't let your checkpoint drift past 5 minutes. Measure your WAL generation rate, calculate the redo work, and set that interval with purpose.
Fuzzy Checkpoints in Action: How PostgreSQL Actually Writes Dirty Pages
Sharp checkpoints are the dream—stop the world, write everything, proceed. Real databases use fuzzy checkpoints because they don't block writes. Here's how it works in PostgreSQL: a background process (checkpointer) scans the shared buffer pool, writing all dirty pages to disk. But it doesn't hold a lock on those pages. New writes can happen concurrently, turning a just-flushed page dirty again. The checkpoint becomes 'fuzzy' because it doesn't capture a consistent snapshot of the buffer pool at a single point in time. The WAL ensures consistency: the checkpoint record is written after all prior dirty pages are flushed, but before the next transaction commits. Recovery uses that record to know where to start replay. The performance trade-off is worth it: writes never stall. But you pay in complexity—the checkpointer must coordinate with the WAL writer to ensure no dirty page is written before its WAL record hits disk. I've seen teams disable background writes thinking they'd speed things up. Don't. Let the fuzzy checkpoint do its job, and tune the number of buffers it writes per cycle (checkpoint_completion_target) to spread I/O load smoothly.
Checkpoint in PostgreSQL: Full Page Writes and Checkpoint Distance
In PostgreSQL, checkpoints are tightly coupled with full page writes (FPW) and checkpoint distance. Full page writes ensure crash recovery by writing the entire page image during the first modification after a checkpoint. This prevents torn pages—partial writes that corrupt data. However, FPW increases WAL volume, especially with frequent checkpoints. The checkpoint distance parameter (checkpoint_completion_target) controls how much WAL is generated between checkpoints. A common mistake is setting checkpoint_timeout too low (e.g., 30 seconds) combined with a high checkpoint_completion_target (e.g., 0.9), causing I/O spikes. For example, with a 1GB shared_buffers and checkpoint_completion_target=0.9, the system tries to spread writes over 90% of the timeout interval, but if the timeout is 30s, it must flush all dirty pages in 27s, leading to bursty I/O. Practical tuning: set checkpoint_timeout to 5-15 minutes and checkpoint_completion_target to 0.7-0.8 to smooth I/O. Monitor pg_stat_bgwriter to see checkpoint write rate and buffers allocated. A production insight: on SSDs, reduce checkpoint_completion_target to 0.5 to avoid write amplification from FPW.
Write-Ahead Logging and Recovery: ARIES Algorithm
The ARIES (Algorithm for Recovery and Isolation Exploiting Semantics) algorithm is the foundation of PostgreSQL's WAL-based recovery. It uses three phases: analysis, redo, and undo. During analysis, the system scans the WAL from the last checkpoint to determine dirty pages and active transactions. Redo reapplies changes from the checkpoint to the end of WAL to bring the database to a consistent state. Undo rolls back uncommitted transactions using log records. A key concept is the LSN (Log Sequence Number), which marks the position in WAL. Each data page stores the LSN of the last change; during recovery, only pages with LSN < redo LSN are redone. For example, if a checkpoint's redo LSN is 1000 and a page's LSN is 950, it must be redone. Practical implication: checkpoint frequency affects recovery time because the redo phase must process all WAL since the last checkpoint. A 5-minute checkpoint can mean 30-minute recovery if the WAL volume is high. ARIES also supports partial rollbacks and savepoints. In PostgreSQL, the recovery process is automatic on crash, and you can monitor progress via pg_stat_progress_recovery. Production insight: to minimize recovery time, ensure checkpoints are frequent enough to keep the redo scope small, but not so frequent that they cause I/O storms.
pg_control_checkpoint() to see redo LSN and WAL size; aim for redo WAL size under 1GB to keep recovery under 5 minutes on modern hardware.Fuzzy Checkpoints vs Sharp Checkpoints
Fuzzy checkpoints and sharp checkpoints represent two strategies for writing dirty pages to disk. Sharp checkpoints (used in some databases like early MySQL/InnoDB) flush all dirty pages at once, blocking all writes during the process. This guarantees a consistent state but causes severe I/O spikes and latency. Fuzzy checkpoints (used by PostgreSQL) spread the write of dirty pages over time, allowing concurrent writes. PostgreSQL's checkpoint process writes dirty pages in batches, using a background writer and checkpointer. The key advantage: fuzzy checkpoints avoid the 'checkpoint stall' that kills production I/O. For example, a sharp checkpoint on a 100GB buffer pool could stall writes for minutes, causing timeouts. Fuzzy checkpoints, with checkpoint_completion_target, smooth the I/O over the checkpoint interval. However, fuzzy checkpoints mean the checkpoint record in WAL is written before all dirty pages are flushed, requiring a redo phase during recovery. Practical example: In PostgreSQL, you can observe fuzzy checkpoint behavior via pg_stat_bgwriter: checkpoints_timed vs checkpoints_req. A high number of checkpoints_req indicates the system is forced to checkpoint due to WAL size, often a sign of misconfiguration. Production insight: For OLTP workloads, fuzzy checkpoints are superior; sharp checkpoints are only acceptable for batch processing or when recovery time is not critical.
PostgreSQL Checkpoint Storm Wiped Out Production DB Performance
- Monitor checkpoint-related metrics in pg_stat_bgwriter (buffers_checkpoint, checkpoint_sync_time).
- Never use default checkpoint_timeout in production without baseline I/O capacity testing.
- Fuzzy checkpoints are a lifesaver – they smooth out I/O load but require careful tuning of checkpoint_completion_target.
SELECT pg_current_wal_lsn(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();SELECT * FROM pg_stat_bgwriter;| File | Command / Code | Purpose |
|---|---|---|
| checkpoint_basics.sql | CHECKPOINT; | What Is a Checkpoint and Why Does It Exist? |
| wal_interaction.sql | SELECT pg_last_checkpoint_location() AS checkpoint_lsn; | WAL and Checkpoint Interaction |
| checkpoint_configuration.sql | SHOW checkpoint_timeout; | Sharp vs Fuzzy Checkpoints |
| checkpoint_analyze.sql | SELECT | Checkpoint Tuning in PostgreSQL |
| estimate_recovery_time.py | def get_recovery_estimate(conn): | Checkpoint Impact on Recovery Time |
| monitor_checkpoint.js | const { Client } = require('pg'); | Fuzzy Checkpoints in Action |
| checkpoint_tuning.sql | SHOW checkpoint_timeout; | Checkpoint in PostgreSQL |
| recovery_monitoring.sql | SELECT pg_current_wal_lsn(); | Write-Ahead Logging and Recovery |
| checkpoint_type_comparison.sql | SELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_clean, ma... | Fuzzy Checkpoints vs Sharp Checkpoints |
Key takeaways
Interview Questions on This Topic
What is the purpose of a checkpoint in a DBMS?
Frequently Asked Questions
20+ years shipping production systems from the metal up. Written from production experience, not tutorials.
That's DBMS. Mark it forged?
6 min read · try the examples if you haven't