The Conventional Wisdom Around InnoDB Recovery Buffer Pool Sizing
The InnoDB buffer pool is the single most impactful memory allocation in any MySQL configuration. It caches data pages, index pages, change buffer data, the adaptive hash index, and other internal structures. For OLTP workloads with hot datasets that fit largely in memory, a large buffer pool means fewer physical reads and faster response times. MySQL documentation consistently recommends settinginnodb_buffer_pool_size to 50–80% of available RAM on dedicated database servers. For a 64GB machine, this translates to a buffer pool somewhere in the 32–48GB range, though practical allocations often start around 20GB and grow from there as workload demands increase.
The assumption is that this sizing benefits every aspect of InnoDB's operation—including crash recovery. That assumption deserves scrutiny.
When InnoDB Recovery Buffer Pool Size Becomes a Performance Problem
InnoDB crash recovery is the process by which the storage engine restores consistency after an unclean shutdown. It reads the redo log, identifies pages that were modified but not fully written to disk, and replays the changes to bring the tablespace files back to a consistent state. The recovery time depends on several factors: the size and activity level of the redo log, the number of dirty pages, and the underlying disk throughput. In a documented real-world case, a MySQL instance with an InnoDB recovery buffer pool of 20GB+ on a 64GB server exhibited an unusual recovery pattern. The redo log was a reasonably sized 256MB, and yet recovery was progressing glacially—taking several hours and slowing progressively as time passed. Disk I/O was minimal. CPU, however, was pegged at 100% on a single core. Profiling withoprofile revealed the culprit. Nearly 95.26% of CPU time was being spent in a single function:
samples % app name symbol name 1345161 95.2609 mysqld buf_flush_insert_sorted_into_flush_list 37849 2.6804 no-vmlinux (no symbols) 8865 0.6278 mysql (no symbols) 7423 0.5257 libc-2.4.so (no symbols) 2623 0.1858 mysqld buf_calc_page_new_checksumThe function
buf_flush_insert_sorted_into_flush_list was monopolizing the CPU. This is the core of the problem.
Understanding the Flush List and Its InnoDB Recovery Buffer Pool Bottleneck
InnoDB uses a structure called the flush list to track dirty pages that need to be written to disk. Pages are inserted into this list in a sorted order—specifically sorted by the Log Sequence Number (LSN) at which they were last modified. This sorted structure is what allows InnoDB to flush pages efficiently during normal operation: it can merge adjacent pages, write them in LSN order, and minimize the number of disk operations needed. The problem is how InnoDB inserts pages into this sorted list. The implementation uses a simple sorted linked list. Inserting a new page requires scanning the list to find the correct position—an O(n) operation. Under normal operation with a modest number of dirty pages, this overhead is negligible. During crash recovery, however, the behavior changes dramatically. When recovering from a crash, InnoDB reads through the entire redo log and replays modifications to pages. With a large buffer pool (20GB or more), InnoDB can hold an enormous number of pages in memory simultaneously. Each modified page must be inserted into the flush list, and with tens or hundreds of thousands of pages already in the list, each insertion requires scanning through a large portion of it. The result is quadratic time complexity—the more pages in memory, the slower each subsequent insertion becomes, causing recovery to slow progressively rather than maintain a consistent pace. As the recovery process noted: "Innodb is smart about a way it flushes pages, it tries to merge sequential pages together and generally flush things in order. It is not however overly smart inserting pages to the flush list – keeping simple sorted linked list, so being forced to scan good portion of it finding place to insert the page." This O(n) insertion cost, compounded across tens of thousands of pages, turns a routine recovery operation into a multi-hour ordeal.The Fix: Reducing Buffer Pool Size for Recovery
The immediate workaround for the InnoDB recovery buffer pool bottleneck is straightforward: restart MySQL with a significantly smaller buffer pool size. By limiting the number of pages that InnoDB can hold in memory during recovery, you reduce the length of the flush list at any given time, which in turn reduces the cost of each insertion. Recovery completes in a fraction of the original time. To apply this temporarily, edit the MySQL configuration file:[mysqld] # Temporarily reduced for crash recovery innodb_buffer_pool_size = 512MAfter recovery completes and the server is stable, restore the buffer pool to its production value:
[mysqld] # Restore to production-appropriate size innodb_buffer_pool_size = 20GThen restart MySQL again to bring the server online with its full buffer pool. This two-step restart process adds some overhead, but it is far preferable to waiting many additional hours for recovery to complete on its own.
Confirming the Root Cause with Profiling
If you suspect an InnoDB recovery buffer pool bottleneck on your MySQL instance,oprofile is one of the most direct tools for confirming it. The command sequence below samples system-wide CPU activity over a defined period and generates a report broken down by symbol:
## Start the oprofile daemon and configure sampling opcontrol --no-vmlinux --start ## Let it run for 30–60 seconds during recovery sleep 60 ## Stop sampling and generate a report opcontrol --stop opreport -l /usr/sbin/mysqld | head -30If
buf_flush_insert_sorted_into_flush_list appears at the top of the output consuming the majority of CPU time, you are hitting this exact bottleneck. The fix is the buffer pool reduction described above.
You can also monitor the progress of InnoDB recovery through the MySQL error log. During recovery, InnoDB prints periodic progress updates that look like this:
InnoDB: Starting crash recovery. InnoDB: Reading tablespace information from the .ibd files... InnoDB: Restoring possible half-written data pages from the doublewrite buffer... InnoDB: Starting an apply batch of log records to the database... InnoDB: Progress in percent: 12 13 14 ...If the percentage increments become noticeably slower over time rather than progressing at a steady rate, the flush list insertion bottleneck is likely the cause.
Understanding the InnoDB Recovery Buffer Pool Problem Scope
It is worth noting that this InnoDB recovery buffer pool problem does not manifest uniformly across all large-buffer-pool deployments. The severity depends on several conditions working together:- Redo log activity and dirty page count: Systems with high write throughput generate more dirty pages. More dirty pages in the buffer pool at the time of a crash means more pages to recover, which amplifies the flush list insertion overhead.
- Buffer pool size relative to workload: A 20GB buffer pool that is mostly empty (few dirty pages) will not exhibit this problem as severely as one that is heavily loaded. The bottleneck is proportional to the number of pages that must be inserted into the flush list during recovery, not the buffer pool size in isolation.
- InnoDB version: This was identified and filed as MySQL Bug #29847. Subsequent InnoDB versions improved the implementation of the flush list, but the fundamental data structure remained a sorted linked list for many years. Organizations running older MySQL versions are more likely to encounter the most severe form of this issue.
- Log file size configuration: The
innodb_log_file_sizeparameter controls how much redo log InnoDB retains. Larger log files mean more potential work to replay during recovery. Balancing log file size against recovery time expectations is an important part of InnoDB performance tuning.
InnoDB Recovery Internals: What Actually Happens
To fully appreciate why the flush list creates this specific InnoDB recovery buffer pool bottleneck, it helps to walk through InnoDB's crash recovery sequence in more detail. When MySQL starts after an unclean shutdown, InnoDB enters recovery mode before accepting any connections. The process proceeds through several phases:- Phase 1 — Log scan: InnoDB reads the redo log from the last checkpoint forward. It identifies the LSN (Log Sequence Number) at which each page was last modified and builds a list of pages that need to be recovered.
- Phase 2 — Page application: InnoDB reads each affected page from disk into the buffer pool and applies the redo log records for that page. Each page that is modified is inserted into the flush list so that InnoDB can track which pages are dirty and need to be written back to disk.
- Phase 3 — Flush: After all redo log records have been applied, InnoDB writes the recovered pages back to their tablespace files. It then truncates the redo log and allows the server to accept connections.
Configuration Recommendations for Recovery Resilience
Based on the mechanics of this issue, several configuration and operational practices can reduce exposure to slow InnoDB crash recovery: Keep redo log files appropriately sized: Theinnodb_log_file_size parameter (or innodb_redo_log_capacity in MySQL 8.0.30+) controls the total redo log capacity. Larger logs allow InnoDB to defer checkpoints longer, which improves write performance but increases recovery time after a crash. For most production workloads, a total redo log capacity of 1–4GB represents a reasonable balance.
[mysqld] # MySQL 8.0.30+ unified redo log capacity innodb_redo_log_capacity = 2G # For older MySQL versions # innodb_log_file_size = 512M # innodb_log_files_in_group = 4Monitor checkpoint age: The gap between the current LSN and the last checkpoint LSN indicates how much redo log would need to be replayed in a crash scenario. You can monitor this with:
SELECT
(LSN - LAST_CHECKPOINT_LSN) AS checkpoint_age_bytes,
ROUND((LSN - LAST_CHECKPOINT_LSN) / 1024 / 1024, 2) AS checkpoint_age_mb
FROM information_schema.INNODB_METRICS
WHERE NAME IN ('log_lsn_current', 'log_lsn_last_checkpoint');
Alternatively, the SHOW ENGINE INNODB STATUS command provides checkpoint age information in its LOG section:
SHOW ENGINE INNODB STATUSGLook for the Log sequence number and Last checkpoint at values. The difference between them is the checkpoint age—the amount of redo log that would need to be replayed in a crash. Tune the dirty page flush aggressiveness: InnoDB's background flushing behavior controls how aggressively dirty pages are written to disk during normal operation. More aggressive flushing means fewer dirty pages in the buffer pool at any given time, which reduces recovery work after a crash.
[mysqld] # Flush method optimized for most Linux deployments innodb_flush_method = O_DIRECT # Adaptive flushing helps maintain a steady dirty page ratio innodb_adaptive_flushing = ON # Maximum percentage of the buffer pool that can be dirty innodb_max_dirty_pages_pct = 75 innodb_max_dirty_pages_pct_lwm = 50 # I/O capacity settings — tune to your storage system's capability innodb_io_capacity = 2000 innodb_io_capacity_max = 4000Document your recovery procedure: If you manage servers with large buffer pools and a realistic possibility of unclean shutdowns, document the buffer pool reduction procedure in your runbook. When a crash recovery is taking hours and CPU is pinned on a single core, knowing exactly what to check and how to fix it is the difference between a brief service disruption and an extended outage.
Relationship to InnoDB Performance Tuning Broadly
This buffer pool recovery issue is a specific instance of a broader principle in database engineering: optimizations for steady-state performance can create liabilities in exceptional states like crash recovery, high-pressure flushing, or replica catchup after significant lag. At MinervaDB, we approach MySQL performance tuning and optimization by examining both the steady-state operating behavior and the exceptional-state behavior of database configurations. A buffer pool that accelerates daily operations but adds hours to crash recovery represents an incomplete optimization—one that may not surface until a critical moment. Related considerations that often surface alongside buffer pool sizing include:Doublewrite Buffer
- The doublewrite buffer (
innodb_doublewrite) adds write overhead under normal conditions but is essential for crash safety on systems where the storage does not guarantee atomic 16KB page writes. - Disabling it for performance without understanding the crash safety implications is a common mistake.
- For a deeper look at InnoDB storage internals, the InnoDB Disk Structures documentation provides authoritative detail.
Redo Log Architecture
- The redo log architecture changed significantly in MySQL 8.0.30, with the introduction of
innodb_redo_log_capacityreplacing the olderinnodb_log_file_sizeandinnodb_log_files_in_grouppair. - The new architecture is more flexible and resizes online without requiring a restart, removing one barrier to right-sizing the redo log.
Diagnostic Checklist: InnoDB Recovery Buffer Pool Slow Recovery
If you are experiencing unexpectedly slow InnoDB crash recovery, work through the following steps before assuming a larger problem:- Verify it is actually a CPU bottleneck: Check
vmstatortopduring recovery. If disk I/O is low but one CPU core is at 100%, you are likely hitting the flush list insertion problem rather than a disk throughput issue. - Confirm with profiling: Use
oprofileorperfto identify whetherbuf_flush_insert_sorted_into_flush_listis dominating CPU consumption.## Use perf as an alternative to oprofile on modern Linux perf top -p $(pgrep mysqld) --sort=symbol
- Reduce the InnoDB recovery buffer pool and restart: Set
innodb_buffer_pool_sizeto 512MB or 1GB, restart MySQL, and allow recovery to complete. This is the most reliable immediate fix. - Restore and monitor: After successful recovery, restore the buffer pool to its production size and restart. Monitor dirty page counts and checkpoint age for the next several hours to ensure normal operation.
- Review your InnoDB log configuration: Post-recovery, review your redo log sizing and flushing configuration to reduce the likelihood of a long recovery window in future incidents. Organizations looking for expert guidance on this can engage MySQL DBA support to conduct a configuration audit.
Conclusion: Rethinking InnoDB Recovery Buffer Pool Strategy
The InnoDB buffer pool is one of the most powerful levers available for MySQL performance tuning, and maximizing its size is sound advice for normal operational conditions. However, the flush list's O(n) insertion behavior during crash recovery creates a scenario where a very large buffer pool can actually extend recovery time significantly—a counterintuitive outcome that has caught experienced DBAs off guard. The diagnostic signature is clear: CPU pinned at 100% on a single core, minimal disk I/O, andbuf_flush_insert_sorted_into_flush_list dominating the profile. The fix is equally clear: restart MySQL with a reduced buffer pool, allow recovery to complete quickly, then restore the full configuration.
Understanding this behavior is part of building operational resilience around MySQL. If your team manages large-scale MySQL deployments and needs help identifying configuration risks before they become incidents, get in touch with the MinervaDB team for a performance and availability review.
Running MySQL in production? MinervaDB provides MySQL consulting, 24×7 MySQL support and MySQL remote DBA services as part of its database consulting services.
Running this in production?
MinervaDB provides MySQL Consulting, MySQL Support and MySQL Remote DBA with 24x7 coverage and a 15-minute S1 response. Talk to an engineer.