The MariaDB InnoDB buffer pool is usually the largest and most important cache in an InnoDB-focused server. It holds frequently used data and index pages, reduces physical reads, buffers modified pages before flushing, and strongly influences query latency. Making it too small produces avoidable storage I/O. Making it too large can push the operating system, connection buffers, backup jobs, Galera, or container runtime into memory pressure and terminate MariaDB.
This guide replaces the simplistic “set it to 80% of RAM” rule with a measurement-based production method. It calculates a memory budget, estimates the active working set, reads buffer-pool and operating-system signals, changes size in controlled steps, handles version-specific resize behavior, validates restart persistence, and troubleshoots misses, dirty-page pressure, swaps, and out-of-memory events.
Examples use MariaDB 10.11 concepts but call out changes introduced in later 10.11 maintenance releases and newer LTS branches. Always inspect SELECT VERSION() and the exact variable documentation before applying a resize. Run tests on a production-like workload and keep a rollback path.
Understand what the buffer pool stores
InnoDB reads pages from tablespaces into the buffer pool. Queries can then use cached pages without waiting for storage. The pool contains data pages, B-tree index pages, modified or dirty pages waiting to be flushed, and internal structures used to manage cached pages.
The buffer pool is not a result cache. It does not make an unindexed query efficient; it only makes repeated page access less dependent on disk. A full table scan still consumes CPU and may displace useful pages. Correct indexes, query plans, schema design, and storage latency remain important.
InnoDB normally maintains an old sublist and a new sublist. Newly read pages first enter the old portion. Pages that prove useful can move into the new portion. Settings such as innodb_old_blocks_pct and innodb_old_blocks_time influence scan resistance, but defaults should be changed only after evidence shows large scans evict the hot workload.
Reject the universal 80% rule
MariaDB documentation commonly describes up to roughly 80% of memory for a dedicated, primarily InnoDB server. That is a possible upper region, not a safe answer for every machine.
The available buffer-pool budget depends on:
- Physical RAM or the container/cgroup memory limit.
- Operating-system kernel and filesystem needs.
- MariaDB executable, Performance Schema, and internal metadata.
- Aria or MyISAM caches where those engines are used.
- Per-connection and per-operation memory.
- Maximum concurrent queries, not only
max_connections. - Temporary tables, sorts, joins, and replication buffers.
- Galera write sets and certification state.
- Backup compression, encryption, and page-cache effects.
- Monitoring agents, sidecars, security tools, and co-located services.
- Memory fragmentation and allocator behavior.
- Crash recovery and maintenance peaks.
A dedicated 128 GiB database host with predictable connections can devote a larger share to InnoDB than a 4 GiB VM or an 8 GiB Kubernetes pod running sidecars. A server that swaps under ordinary peaks does not have useful cache sizing, regardless of its hit-rate percentage.
Establish the real memory ceiling
On a bare-metal host or VM, inspect physical memory and current pressure:
free -h
grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' /proc/meminfo
vmstat 1 10
Check MariaDB's systemd constraints:
systemctl show mariadb
-p MemoryMax
-p MemoryHigh
-p MemoryCurrent
-p OOMPolicy
On cgroup v2, inspect the service's effective cgroup rather than assuming host RAM is available:
pid=$(systemctl show -p MainPID --value mariadb)
cat "/proc/$pid/cgroup"
systemctl status mariadb --no-pager
For a Kubernetes pod, inspect resource requests and limits:
kubectl -n data get statefulset mariadb -o yaml
kubectl -n data top pod mariadb-0 --containers
kubectl -n data describe pod mariadb-0
Inside the container, cgroup v2 commonly exposes the limit and current usage:
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.events
memory.max may contain max, which means no limit at that cgroup level. Confirm the actual hierarchy and orchestrator specification. Do not size from the Kubernetes node's total RAM when the database container has a smaller limit.
Inventory MariaDB memory consumers
Capture relevant configuration:
SHOW GLOBAL VARIABLES WHERE Variable_name IN (
'innodb_buffer_pool_size',
'innodb_buffer_pool_chunk_size',
'innodb_buffer_pool_size_max',
'innodb_buffer_pool_size_auto_min',
'max_connections',
'max_allowed_packet',
'tmp_table_size',
'max_heap_table_size',
'sort_buffer_size',
'join_buffer_size',
'read_buffer_size',
'read_rnd_buffer_size',
'thread_stack',
'key_buffer_size',
'aria_pagecache_buffer_size'
);
Some variables do not exist on older releases. An unknown-variable result is a version signal, not a reason to create the option manually.
Check connection peaks:
SHOW GLOBAL STATUS WHERE Variable_name IN
('Threads_connected','Threads_running','Max_used_connections','Connections');
Do not calculate memory as max_connections multiplied by every per-session buffer. Many buffers are allocated only when an operation uses them, and one connection can allocate more than one buffer in a complex plan. Conversely, ignoring connection memory entirely is unsafe. Use measured concurrency, query shapes, and high-water observations with a conservative peak allowance.
Review non-InnoDB tables:
SELECT ENGINE,
COUNT(*) AS table_count,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 1) AS total_mib
FROM information_schema.TABLES
WHERE TABLE_SCHEMA NOT IN
('mysql','information_schema','performance_schema','sys')
GROUP BY ENGINE
ORDER BY total_mib DESC;
If substantial Aria or MyISAM workloads exist, their caches and operating characteristics need a separate budget. Do not give almost all RAM to InnoDB by assumption.
Build a memory budget
Use an explicit worksheet:
effective memory ceiling
- OS/kernel/filesystem headroom
- MariaDB non-buffer-pool global memory
- connection and query peak allowance
- replication/Galera allowance
- backup and maintenance allowance
- agents, sidecars, and co-located services
- safety margin
= candidate InnoDB buffer pool
For example, an exclusive 32 GiB VM might begin with a candidate of 20–24 GiB after measurement, while an 8 GiB pod may begin near 4–5 GiB because the process, connection peaks, and sidecars need proportionally more headroom. These are examples, not recommendations for an unseen workload.
Do not count Linux filesystem cache as entirely wasted. MariaDB files, binary logs, libraries, backup streams, and other workloads use it. More importantly, MemAvailable and memory-pressure events show whether the full system has breathing room.
Record the candidate, assumptions, peak data, and rollback threshold in the change ticket. A number without its budget cannot be reviewed later.
Measure the data set and active working set
Estimate allocated InnoDB table and index size:
SELECT ROUND(SUM(DATA_LENGTH) / 1024 / 1024 / 1024, 2) AS data_gib,
ROUND(SUM(INDEX_LENGTH) / 1024 / 1024 / 1024, 2) AS index_gib,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024 / 1024, 2)
AS total_gib
FROM information_schema.TABLES
WHERE ENGINE = 'InnoDB';
Break it down by schema:
SELECT TABLE_SCHEMA,
ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024 / 1024, 2)
AS total_gib
FROM information_schema.TABLES
WHERE ENGINE = 'InnoDB'
AND TABLE_SCHEMA NOT IN
('mysql','information_schema','performance_schema','sys')
GROUP BY TABLE_SCHEMA
ORDER BY total_gib DESC;
Information Schema sizes are estimates and allocated size is not the working set. A 2 TiB historical table may receive few reads, while a 50 GiB current-orders index is hot. Determine working set from workload metrics over normal weekdays, weekends, batch windows, backups, reporting periods, restarts, and seasonal peaks. Sizing from a quiet 15-minute sample produces false confidence.
Read buffer-pool status correctly
Capture core counters:
SHOW GLOBAL STATUS WHERE Variable_name IN (
'Innodb_buffer_pool_pages_total',
'Innodb_buffer_pool_pages_free',
'Innodb_buffer_pool_pages_data',
'Innodb_buffer_pool_pages_dirty',
'Innodb_buffer_pool_bytes_data',
'Innodb_buffer_pool_bytes_dirty',
'Innodb_buffer_pool_read_requests',
'Innodb_buffer_pool_reads',
'Innodb_buffer_pool_wait_free',
'Innodb_pages_read',
'Innodb_pages_written'
);
Interpret them as time-series rates and ratios. Innodb_buffer_pool_read_requests counts logical requests. Innodb_buffer_pool_reads counts requests that required a storage read. Free pages naturally become scarce on a warmed server because a cache is supposed to fill. Dirty pages are modified pages awaiting flush, not automatically a fault. Innodb_buffer_pool_wait_free growth can indicate sessions waited for reusable pages, often involving flushing pressure.
A cumulative hit ratio is:
1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
Do not treat “99.9%” as universally healthy. At billions of requests, the remaining misses may saturate storage. Lifetime counters also hide a new incident. Calculate deltas over a meaningful interval and correlate them with physical read latency.
Capture two samples without resetting global status:
sudo mariadb --batch --raw --skip-column-names
--execute="SHOW GLOBAL STATUS WHERE Variable_name IN
('Innodb_buffer_pool_read_requests','Innodb_buffer_pool_reads',
'Innodb_buffer_pool_wait_free','Innodb_pages_read','Innodb_pages_written')"
> /tmp/innodb-sample-1.tsv
sleep 300
sudo mariadb --batch --raw --skip-column-names
--execute="SHOW GLOBAL STATUS WHERE Variable_name IN
('Innodb_buffer_pool_read_requests','Innodb_buffer_pool_reads',
'Innodb_buffer_pool_wait_free','Innodb_pages_read','Innodb_pages_written')"
> /tmp/innodb-sample-2.tsv
Store production samples in an approved metrics system rather than relying on /tmp. The example illustrates the interval method.
Correlate database and operating-system signals
A buffer-pool problem is not proved by one MariaDB counter. Correlate database metrics with host evidence:
vmstat 1 60
iostat -xz 1 60
pidstat -r -p "$(systemctl show -p MainPID --value mariadb)" 1 60
Look for sustained storage utilization and read latency during cache misses, swap-in and swap-out activity, falling MemAvailable, major page faults, MariaDB resident memory approaching a cgroup limit, and query concurrency coinciding with memory spikes.
Linux swap usage alone does not prove active swapping; inactive pages can remain in swap. vmstat si and so, pressure-stall information, current memory events, and application latency are more useful.
Check OOM evidence:
journalctl -k -g 'oom|Out of memory|Killed process' --no-pager
systemctl show mariadb -p Result -p ExecMainCode -p ExecMainStatus
In Kubernetes:
kubectl -n data describe pod mariadb-0
kubectl -n data get pod mariadb-0
-o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}{"n"}'
An OOMKilled container needs a lower memory footprint, a justified higher limit, or both. Restarting without a budget only repeats the incident.
Decide whether a larger pool will help
Increasing the pool is justified when InnoDB dominates, the active set exceeds the current pool, physical reads affect query latency, queries are reasonably designed, proven headroom exists at peak, and the new size remains safe on every failover node.
A larger pool may not help when CPU is saturated by inefficient queries, lock waits dominate latency, a report performs a one-time scan, redo flushing is the bottleneck, the useful data already fits, the host is swapping, or another storage engine dominates. Fix query plans before buying cache for repeated full scans.
Check version-specific resize behavior
Capture the exact release and available variables:
SELECT VERSION();
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_chunk_size';
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size_max';
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size_auto_min';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_resize_status';
In traditional dynamic resize behavior, changes occur in chunks related to innodb_buffer_pool_chunk_size. MariaDB ignores innodb_buffer_pool_instances from 10.5 and removed it in 10.6, so old advice about many buffer-pool instances is invalid for 10.11.
Starting with MariaDB 10.11.12, 11.4.6, and 11.8.2, behavior changed significantly:
innodb_buffer_pool_chunk_sizeis deprecated and ignored.- Size changes use arbitrary 1 MiB increments up to
innodb_buffer_pool_size_max. innodb_buffer_pool_size_maxprovides the startup ceiling for upward manual resizing.innodb_buffer_pool_size_auto_mincontrols possible shrinking under Linux memory pressure.Innodb_buffer_pool_resize_statusis removed.SET GLOBAL innodb_buffer_pool_sizeblocks until completion or abort.
Patch level therefore matters even inside 10.11. Do not select a runbook from the major.minor number alone.
Resize dynamically in controlled steps
Before change, confirm backup and recovery state, record current size and latency, check replica or Galera health, stop unrelated memory-intensive maintenance, define rollback thresholds, and keep a separate administrative session.
For a release supporting the requested dynamic range:
SET GLOBAL innodb_buffer_pool_size = 24 * 1024 * 1024 * 1024;
On older resize implementations, monitor progress and the error log:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_resize_status';
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
sudo journalctl -u mariadb --since '-15 minutes' --no-pager
On the newer maintenance behavior, the SET GLOBAL call blocks and the old status variable no longer exists. Monitor that SQL session, server log, memory, and workload latency.
Resize in increments appropriate to the host rather than jumping from 8 GiB to 28 GiB. A gradual increase validates headroom after each step. Shrinking also requires care: InnoDB withdraws pages and the workload may immediately produce more physical reads.
Dynamic resize can wait for active transactions or operations, and parts of the workload can pause. Perform it in a low-risk window even though no restart is required.
Persist the approved size
Runtime changes normally disappear at restart. On Ubuntu, use an owned late-loading option file:
[mariadb]
innodb_buffer_pool_size = 24G
On maintenance releases supporting the new ceiling, a deployment that needs later upward resizing can configure:
[mariadb]
innodb_buffer_pool_size = 20G
innodb_buffer_pool_size_max = 24G
innodb_buffer_pool_size_auto_min = 16G
Use these newer variables only on supporting releases. A maximum supplies resize headroom; it does not justify allocating beyond the memory budget, and it does not provide automatic upward growth.
Inspect parsed defaults:
my_print_defaults server mysqld mariadb mariadbd |
grep -E 'innodb-buffer-pool|innodb_buffer_pool'
mariadbd --print-defaults | tr ' ' 'n' |
grep -E 'innodb-buffer-pool|innodb_buffer_pool'
Restart only during a planned window:
sudo systemctl restart mariadb
sudo systemctl --no-pager --full status mariadb
sudo journalctl -u mariadb -n 100 --no-pager
Verify through SQL:
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
A file value is not proof until MariaDB restarts with it and workload behavior is checked.
Monitor after the change
Compare the same windows before and after:
- Physical-read rate and latency.
- Query p50, p95, and p99 latency by operation.
- Throughput and CPU.
- Dirty-page ratio and write latency.
- Buffer-pool waits.
- MariaDB RSS and cgroup memory.
MemAvailable, swap activity, and PSI.- Connection and temporary-table peaks.
- Backup duration and memory.
- Replica lag or Galera flow control.
Allow the cache to warm before declaring no benefit. Conversely, do not wait through clear swap thrashing or rising OOM events. Use the predefined rollback. Measure business operations, not just cache ratio; fewer reads with unchanged latency may mean storage was not the bottleneck.
Warm the buffer pool after restart
MariaDB supports dumping selected page identifiers at shutdown and loading them at startup. Inspect the relevant variables:
SHOW GLOBAL VARIABLES WHERE Variable_name IN
('innodb_buffer_pool_dump_at_shutdown',
'innodb_buffer_pool_load_at_startup',
'innodb_buffer_pool_dump_pct',
'innodb_buffer_pool_filename');
Monitor dump/load status variables present on the release:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%status';
Warm-up metadata guides reloading useful pages; it does not contain table data itself. Loading consumes I/O and can compete with startup traffic. Test failover and restart against representative storage.
Do not warm the cache with unrestricted SELECT * scans. They can evict hot pages, saturate storage, overload replicas, and expose data to an unsafe client.
Protect the cache from disruptive scans
If large reports displace the OLTP set, first add or fix indexes, move analytics to a replica, build summaries, batch work, and schedule it. Only after evidence should you evaluate:
SHOW GLOBAL VARIABLES WHERE Variable_name IN
('innodb_old_blocks_time','innodb_old_blocks_pct');
A nonzero old-blocks time delays promotion of newly read pages and can limit scan pollution. Changing it affects broad cache behavior, so benchmark instead of copying a tuning number.
Troubleshoot common buffer-pool problems
Physical reads remain high after increasing the pool
The working set may still exceed memory, the cache may be warming, or queries may scan large tables. Inspect slow queries and plans, break down schemas, and compare interval rates. Do not increase until OOM.
Free pages are nearly zero
This is normal for a warmed cache. Ask whether misses, wait-free events, storage latency, and query latency are problematic.
MariaDB uses more memory than the configured pool
Connections, query buffers, Performance Schema, Aria/MyISAM, Galera, plugins, allocator overhead, and process code add memory. Rebuild the complete budget.
The host swaps after resizing
Reduce the pool through supported dynamic resize or rollback configuration. Inspect connection and maintenance peaks. Active swapping can destroy latency and precede OOM.
SET GLOBAL rounds to another value
Older logic aligns to chunk size. Newer maintenance releases use 1 MiB increments and a configured maximum. Inspect exact patch level, requested bytes, and actual value.
Increasing size fails on a newer release
innodb_buffer_pool_size_max may equal the startup size. A higher ceiling must be configured at startup. Do not restart with a larger maximum without budget review.
Resize appears stuck
Older implementations can wait for active work and expose status in the resize variable and error log. Check long transactions and logs. Do not kill MariaDB casually.
A container is OOMKilled while the node has free RAM
The cgroup limit, not node RAM, is the ceiling. Include sidecars and query peaks, then reduce the pool or raise a justified limit with scheduling capacity.
Performance worsens after shrinking
More pages are being evicted. Compare misses and storage latency. Restore the previous size when headroom allows and the rollback threshold is crossed.
Plan high availability consistently
Size every failover candidate for its own effective limit. A replica with a small pool may look healthy while applying replication, then suffer severe misses after promotion.
Resize one node at a time where topology supports it. Wait for health and catch-up before continuing. Compare hardware, configuration, workload, Galera or replication memory, and cold-start behavior across nodes. Avoid simultaneous cache cold starts.
Production checklist
- Size from the cgroup or service limit, not blindly from host RAM.
- Budget OS, connections, other engines, HA, backup, and agents.
- Measure the active set across complete business cycles.
- Use interval miss rates rather than lifetime hit rate alone.
- Correlate MariaDB counters with storage latency and memory pressure.
- Fix plans and indexes before using RAM to hide scans.
- Version-gate resize status, chunk behavior, and maximum variables.
- Resize gradually with rollback thresholds.
- Persist the approved value in an owned option file.
- Verify restart persistence and workload behavior.
- Monitor OOM, swap, dirty pages, latency, and HA health.
- Test warm-up and failover on every candidate node.
FAQ
Should the MariaDB InnoDB buffer pool always use 80% of RAM?
No. That may suit a large dedicated InnoDB host, but small servers, containers, busy connections, Galera, backups, and co-located services need more headroom.
Is a nearly full buffer pool a problem?
No. A cache should fill. Focus on interval physical reads, wait-free events, storage latency, query latency, and memory pressure.
Can innodb_buffer_pool_size change without restart?
Yes on supported releases, but mechanics differ by patch level. Persist the final value for restart survival.
Why is MariaDB memory higher than the buffer-pool size?
MariaDB also allocates connection, query, Performance Schema, plugin, storage-engine, allocator, and executable memory.
What happened to innodb_buffer_pool_instances?
MariaDB ignored it from 10.5 and removed it in 10.6. Do not copy old recommendations into 10.11 or newer.
Does a 99.9% hit rate prove good performance?
No. Lifetime ratios hide current spikes, and a small miss fraction at high volume can saturate storage. Use interval rates and latency.
Why can a Kubernetes pod be OOMKilled with free node RAM?
The pod or container cgroup limit is its effective ceiling. MariaDB, sidecars, and peaks can exceed it even with spare node memory.
Should I preload every table after restart?
No. Use supported dump/load features and normal warm-up. Full scans can evict useful pages and overload storage.
Conclusion
Sizing the MariaDB InnoDB buffer pool is a capacity exercise, not a percentage shortcut. Establish the true memory ceiling, subtract credible peak consumers, observe the active set and interval miss rate, and confirm that storage reads affect business-query latency.
Resize in measured steps with version-specific procedures, persist the approved value, and monitor memory pressure as closely as cache performance. The right pool holds useful pages while leaving MariaDB, the operating system, maintenance jobs, and failover nodes enough memory to remain stable at peak load.
Suggested Internal Links
- Understand MariaDB Configuration Files and Precedence
- Tune MariaDB Connections, Threads, and Timeouts
- Tune MariaDB Temporary Tables and Sort Operations
- Use the MariaDB Slow Query Log for Real Diagnosis
- Analyze MariaDB Queries with EXPLAIN and ANALYZE
- Troubleshoot MariaDB High CPU Usage Step by Step