There is a specific incident signature I have learned to recognize on write-heavy MySQL: everything is fine, then a batch job or a traffic burst lands, and suddenly commit latency spikes across the board, all queries at once, not just the heavy ones. CPU is not saturated. No lock chain. Then, minutes later, it clears on its own. That is very often the redo log running out of runway: checkpoint age hit the wall, InnoDB switched into synchronous flushing, and every user thread got conscripted into page cleaning. People call it furious flushing, and it is one of the most alertable, most preventable stalls in the engine.
This guide covers how redo capacity works after MySQL 8.0.30, what checkpoint age actually measures, and how I size and monitor it in production.
The redo log is a ring, and the checkpoint chases the head
InnoDB is a write-ahead engine. Every page change is recorded in the redo log first, sequentially and cheaply; the modified page itself stays dirty in the buffer pool and gets flushed to the tablespace later, at the engine's leisure. That deferral is where InnoDB's write performance comes from, and it is the same dirty-page economics that make buffer pool health so central (I touched on that side in InnoDB buffer pool vs PostgreSQL shared_buffers).
The catch is that the redo log is circular. A redo record can only be overwritten once the dirty pages it protects have been flushed to disk. The checkpoint LSN marks how far that flushing has progressed; the current LSN marks how much redo has been written. The gap between them is the checkpoint age, and it can never be allowed to reach the log's capacity, because that would mean overwriting redo for pages that exist nowhere on durable storage. Crash there and you lose committed data. InnoDB will do anything to prevent it, including stopping your workload.
What 8.0.30 changed: one knob, resizable online
For two decades redo sizing meant innodb_log_file_size times innodb_log_files_in_group, changeable only with a restart. MySQL 8.0.30 replaced that pair with a single dynamic variable, innodb_redo_log_capacity, and moved the files into a dedicated #innodb_redo directory as 32 segments. The old variables are deprecated and ignored when the new one is set.
Two operational facts matter. First, the default is 100 MiB, which is far too small for any serious write workload; a busy OLTP server can generate that much redo in seconds, so treat the default as a placeholder, not a recommendation. Second, you can now resize online with SET GLOBAL and watch progress through Innodb_redo_log_resize_status. Growing is effectively instant. Shrinking has to wait for the checkpoint to advance past the space being released, which on a busy system means the engine aggressively flushes to get there, so a large shrink is itself a flushing event you schedule, not a casual afternoon change.
If innodb_dedicated_server is on, MySQL auto-sizes redo capacity from host memory. It picks sane values for a box that only runs MySQL, but auto is not a substitute for checking the result against your actual write rate.
The pressure ladder: adaptive, then furious
InnoDB manages checkpoint age with escalating force. At low age, background page cleaners flush at a leisurely rate shaped by innodb_io_capacity. As age grows past the adaptive flushing low-water mark (innodb_adaptive_flushing_lwm, 10 percent of capacity by default), the adaptive algorithm starts scaling flush rate up toward innodb_io_capacity_max, trying to keep age comfortable while smoothing I/O. This is the regime you want to live in permanently.
If redo generation outruns even that, and checkpoint age approaches the capacity limit, InnoDB abandons smoothing and enters sync flush mode: user threads are blocked and forced to participate in flushing pages in LSN order until the checkpoint advances. This is the stall. It does not show up as lock waits or slow plans; it shows up as everything pausing at once, with mysqld hammering the disk. On cloud volumes with modest IOPS ceilings the effect is vicious, because the emergency flush rate immediately hits the volume's throttle and the stall stretches from seconds into minutes.
Worth noting for upgraders: MySQL 8.4 raised several I/O-related InnoDB defaults, including innodb_io_capacity, so the background flusher on a stock 8.4 install is considerably more willing to work than a stock 8.0 install. Undersized redo hurts on both.
What furious flushing looks like in the metrics
Since 8.0.30, checkpoint age is trivially computable from global status, no SHOW ENGINE INNODB STATUS parsing required:
SELECT
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_current_lsn') -
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_checkpoint_lsn')
AS checkpoint_age_bytes,
(SELECT variable_value FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_logical_size')
AS redo_logical_bytes,
@@innodb_redo_log_capacity AS capacity_bytes;
Trend checkpoint_age_bytes as a fraction of capacity. A healthy server sawtooths somewhere in the low-to-middle range. A server flirting with disaster rides above 75 to 80 percent during bursts, and a server in sync flush is pinned near the top while flush throughput spikes. The corroborating signals: buffer_flush_sync_total_pages climbing in information_schema.innodb_metrics, Innodb_buffer_pool_wait_free ticking up, Innodb_data_writes exploding, and a cliff in query throughput that matches none of the usual suspects.
This is also a case where statement-level tooling misleads you. Every query gets slower during a sync flush, so the slow query log fills with innocents; the cause is engine state, not any statement in the log (a blind spot cousin to the ones in MySQL slow query log vs pg_stat_statements).
Sizing against write bursts, not averages
The sizing question is: how much redo does your workload generate during its worst sustained burst, and how much runway do you want while flushing catches up? Measure generation rate directly by sampling the current LSN:
SELECT variable_value INTO @lsn1
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_current_lsn';
DO sleep(60);
SELECT variable_value INTO @lsn2
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_redo_log_current_lsn';
SELECT round((@lsn2 - @lsn1) / 1024 / 1024, 1) AS redo_mb_per_min;
Run that during your heaviest window: bulk loads, end-of-day settlement, Monday morning peak, whatever your workload's ugliest hour is. The folklore rule is to size capacity to hold roughly an hour of peak redo, and as folklore goes it is decent, erring generously. My actual rule is a floor: capacity should comfortably absorb your longest realistic burst without checkpoint age crossing about 80 percent. For most OLTP fleets I end up between 2 and 16 GiB. Disk is cheap; stalls are not.
The traditional argument against big redo logs was crash recovery time, since recovery replays redo from the last checkpoint. It still has weight, but 8.0's recovery is substantially better than the 5.x-era fear that spawned the tiny-log habit, and in practice recovery time is bounded by how much age you actually accumulate, not by capacity itself. A big log that normally runs at 30 percent age recovers quickly; it just also survives bursts.
The alert set
Three things, all cheap. Page when checkpoint age exceeds 80 percent of innodb_redo_log_capacity for more than a minute. Warn on redo generation rate approaching what your flushing can retire, which you learn by watching age trend upward during bursts. And alert on any nonzero rate of sync-flush activity via buffer_flush_sync_total_pages, because by the time that counter moves, users have already felt it once, and it should never move twice.
Where MonPG fits, honestly
MonPG is a PostgreSQL monitoring platform, and today it monitors PostgreSQL only. MySQL support is in active development, and redo pressure is on its shortlist of built-in signals: checkpoint age as a percentage of capacity out of the box, LSN generation rate trended so sizing stops being folklore, and sync-flush events surfaced as annotated stalls rather than mystery latency cliffs. If you want to see where that is headed, the MySQL monitoring (coming soon) page is the place to watch, and we will not pretend it is shipped before it is. If your team also runs PostgreSQL, where the analogous story is checkpoints and max_wal_size, you can start there today with the workflow the MySQL side is being built to match.