MySQL9 min read

InnoDB Doublewrite Buffer: What It Costs and What It Protects

Doublewrite exists for one narrow disaster: a torn page that redo cannot repair. This is an honest look at how it works, what it really costs in I/O, how to measure that overhead yourself, and when disabling it is actually defensible.

The doublewrite buffer is the InnoDB feature people most love to turn off. The reasoning is always the same: "it writes everything twice, so disabling it should give us back a bunch of write throughput." Half of that sentence is true. The other half is a bet that a crash will never land in the middle of a 16 KB page write, and I have seen that bet lost exactly once, on a machine that had been fine for two years. The corrupted page was in a primary key index. The restore took nine hours.

This is an honest accounting: what torn pages are, why the redo log alone cannot fix them, what doublewrite actually costs (less than the name implies, more than zero), how to measure that cost on your own workload, and the narrow set of situations where switching it off is a sound engineering decision rather than a superstition about free performance.

Torn pages: the failure redo cannot repair

InnoDB's default page is 16 KB. The atomicity your storage stack actually guarantees is usually smaller: 4 KB sectors on most modern drives, sometimes 512 bytes, occasionally something else through a hypervisor. So a single page write is, physically, several device writes. Lose power partway through, and the page on disk is a chimera: the first 4 KB from the new version, the rest from the old. Checksums catch it at next read; that is the famous "InnoDB: page corruption" startup message.

Here is the part that surprises people: the redo log cannot fix this. InnoDB redo is physiological, meaning most records describe a change to apply to a page, not the full page contents. Applying "insert this record at this offset" requires a self-consistent starting image, and a torn page is not one; it is two half-pages superglued together. PostgreSQL solves the identical problem by writing full page images into WAL after each checkpoint (full_page_writes); InnoDB solves it by staging pages in a doublewrite area instead, which keeps redo small at the cost of a second write path. Different tax structure, same tax.

How doublewrite actually works

Before flushing a batch of dirty pages from the buffer pool to their final tablespace locations, InnoDB first writes the whole batch sequentially into the doublewrite area and syncs it. Only then are the pages written to their real homes. On crash recovery, any page that fails its checksum can be recovered from its doublewrite copy, and then redo is applied on top. The invariant: at every instant, at least one intact copy of every in-flight page exists somewhere durable.

Since MySQL 8.0.20, the doublewrite area lives in dedicated .dw files rather than inside the system tablespace, which was a bigger deal than the release notes made it sound: it removed contention on ibdata1 and added real knobs. innodb_doublewrite_dir lets you place the files on a separate, faster device; innodb_doublewrite_files and innodb_doublewrite_pages control the file count and per-thread batch depth. On busy write workloads, putting the .dw files on low-latency storage is a cheap, legitimate optimization that keeps the protection and shaves the cost.

The flushing pipeline this rides on, page cleaners draining dirty pages under checkpoint and LRU pressure, is the same machinery discussed from the memory side in InnoDB buffer pool vs PostgreSQL shared_buffers; doublewrite sits in the middle of it, adding one sequential stop to every page's journey to disk.

What it really costs

Byte-for-byte, doublewrite does double the page write volume. But throughput is not priced in bytes; it is priced in I/O patterns. The doublewrite copies are large sequential writes batched across many pages, while the final writes are the same scattered random writes you were paying for anyway. On drives where sequential bandwidth is abundant relative to random IOPS, which describes most SSDs and virtually all cloud block storage, the marginal cost is far below the 2x the name suggests. The overhead concentrates in three places: extra bandwidth consumed (which matters when your cloud volume has a throughput cap), extra fsyncs on the doublewrite files, and added latency in the flush path when the doublewrite area itself becomes a bottleneck at very high page-flush rates.

Anyone who quotes you a universal percentage is making it up. The honest answer is that the overhead ranges from unmeasurable to painful depending on flush rate, storage, and page size, which is why the only number worth trusting is one you measured on your own workload.

Measuring the overhead honestly

Two status counters describe doublewrite activity directly, and their ratio tells you how well batching is working:

SELECT variable_name, variable_value
FROM performance_schema.global_status
WHERE variable_name IN ('Innodb_dblwr_pages_written',
                        'Innodb_dblwr_writes',
                        'Innodb_data_written',
                        'Innodb_data_writes');

Sample deltas over a busy interval. Innodb_dblwr_pages_written divided by Innodb_dblwr_writes is pages per doublewrite batch: high values (tens of pages per write) mean the sequential batching is doing its job; values collapsing toward 1 mean single-page flushes are dominating, often LRU-driven, and the doublewrite tax rises accordingly. Doublewrite bytes as a share of total writes is directly computable too:

SELECT round(
         (SELECT variable_value FROM performance_schema.global_status
           WHERE variable_name = 'Innodb_dblwr_pages_written') * 16384 /
         (SELECT variable_value FROM performance_schema.global_status
           WHERE variable_name = 'Innodb_data_written') * 100, 1)
       AS dblwr_pct_of_data_written;

(Adjust 16384 if you run a nondefault page size; counters are since-startup, so compute over deltas for anything precise.) For the full picture, run your real workload with innodb_doublewrite ON and OFF on an identical staging box and compare p99 commit latency, page flush rate, and device utilization. If you cannot measure a difference under your production write rate, you have your answer, and it costs you nothing to keep the protection.

When disabling is actually defensible

The defensible case is simple to state: doublewrite is redundant when the storage stack already guarantees that a 16 KB page write is atomic. Copy-on-write filesystems are the canonical example. ZFS never overwrites a block in place; a page write either commits completely or the old block remains, so torn pages cannot occur, and running doublewrite on ZFS (typically with recordsize matched to the InnoDB page size) is paying for insurance the filesystem already provides. The same logic applies to Btrfs with CoW enabled. Some storage layers make explicit atomic-write guarantees for 16 KB units as well; recent MySQL releases even auto-disable doublewrite where the platform advertises this. The rule I hold to: disable it only when a specific, documented mechanism provides the atomicity, named in writing in your runbook, not "our SSDs are probably fine." ext4 or XFS on generic block storage provides no such guarantee.

Since 8.0.30 there is also a middle setting: innodb_doublewrite = DETECT_ONLY writes only metadata, skipping page content, so recovery can detect torn pages but not repair them. I find it most useful as a migration or evaluation stance, cheaper than full protection, but be clear-eyed that detection without recovery still means a restore when it fires. Note that ON, OFF, and the detect modes are startup decisions in spirit; you can move between ON and the detect modes at runtime, but enabling from OFF requires a restart, so this is a decision to make deliberately, not during an incident.

MonPG, and where MySQL support stands

Plainly: MonPG is a PostgreSQL monitoring platform, and it does not monitor MySQL yet. MySQL support is being built now, and the doublewrite counters above are in its planned metric set, dblwr batch efficiency trended over time, doublewrite share of write bandwidth, and single-page-flush pressure surfaced so the "should we move the .dw files or fix LRU flushing" conversation starts from evidence. Progress lives on the MySQL monitoring (coming soon) page, and until it ships we will not claim otherwise. If your fleet includes PostgreSQL, where the same tradeoff wears the name full_page_writes and shows up as post-checkpoint WAL bursts, you can start there today.