free -h shows most of the new memory sitting unused. The cause is very often a single setting: innodb_buffer_pool_size is still 128M, the default value on a fresh install. Adding RAM to the server does not give InnoDB any more of it. Someone has to configure it.
This article walks through the problem as a post-mortem: what the impact looks like, how the investigation usually goes, the point where the cause becomes clear, a temporary fix you can apply without a restart, and the permanent change.
What the impact usually looks like
top shows a noticeable wa (I/O wait) value while CPU usage stays modest. The new server has plenty of free memory, which makes RAM look like the last thing to suspect. From the support side, the complaint usually sounds like "we upgraded the plan and nothing improved". That should prompt a check of the database configuration, not only the hardware.
How the investigation tends to go
SQL
-- Active buffer pool size in MB
SELECT @@innodb_buffer_pool_size / 1024 / 1024 AS buffer_pool_mb;
-- Logical read requests vs. reads that had to go to disk
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- Total InnoDB data + index size in MB
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024) AS innodb_mb
FROM information_schema.tables
WHERE engine = 'InnoDB';Innodb_buffer_pool_read_requests counts page reads InnoDB served, from memory or disk. Innodb_buffer_pool_reads counts the reads that missed the pool and went to disk. Both are cumulative since the server started, so one snapshot tells you little. Take two snapshots a few minutes apart during normal traffic and compare how fast each one grows. If disk reads keep rising steadily on a site that is not doing anything unusual, the pool is too small for the working set.
The third query gives the upper bound. It shows how large the InnoDB data actually is, which tells you how much buffer pool could possibly help.
The running value never changed
innodb_buffer_pool_size is a server setting stored in an option file such as my.cnf. It is not stored in the database. A mysqldump and restore, an rsync of the data directory, or a panel account transfer moves the data and leaves the configuration behind. A fresh MySQL install on the new VPS starts with no value set, so it uses the default. The new server's RAM has no effect on that.
There are a few variations of the same problem:
- The old
my.cnfwas copied over, including a value sized for the old, smaller server. - The line was added under
[client]or[mysql]instead of[mysqld], so the server never reads it. - The value is set correctly in one file, but a file read later sets it again. When a setting appears more than once, the last value read takes effect.
- Someone edited a file that this server does not read at all, which is common when distro packages and vendor packages use different paths.
BASH
# Which option files mysqld reads, and in what order
mysqld --verbose --help 2>/dev/null | grep -A1 'Default options'
# Options the [mysqld] group resolves to from those files
my_print_defaults mysqld | grep -i buffer_pool
# Every place the setting is defined on disk
grep -rn 'innodb_buffer_pool_size' /etc/my.cnf /etc/mysql/ 2>/dev/nullmariadbd and read a [mariadb] group, and control panels sometimes keep their own copy of the config. Treat the commands above as a starting point and adjust them to the server.
Raising it without a restart
SQL
-- Illustrative example: set 4 GB (value in bytes)
SET GLOBAL innodb_buffer_pool_size = 4294967296;
-- Watch the resize progress
SHOW STATUS LIKE 'Innodb_buffer_pool_resize_status';innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances, so the result may differ slightly from the number you entered. Run the first query from the investigation again afterwards to see the real value. Resizing can briefly hold up queries while memory is reallocated, so a quieter period is a better time to do it.
This is only a mitigation. A value set with SET GLOBAL is lost on the next restart, and the server returns to whatever the option files say. MySQL 8.0 offers SET PERSIST, which writes the value to a file in the data directory. That works, but it also means the setting now lives in a place the next person might not think to check. For a setting this important, keeping it in the main config file is usually clearer.
Sizing it properly and making it stick
mysqld. That causes an outage instead of slowness.
Once you have a number, put it under [mysqld] in the file the server actually reads, as identified earlier. If a control panel manages that file, use the panel's method so the change is not overwritten. Restart MySQL during a maintenance window, then verify:
- Run
SELECT @@innodb_buffer_pool_sizeagain and check that it matches the new value. - Run
SHOW GLOBAL STATUS LIKE 'Uptime'to confirm the server really restarted and you are not looking at an old process. - After the pool has had time to warm up, compare the
Innodb_buffer_pool_readsgrowth rate with your earlier snapshots. A restart empties the pool, so the first period will look worse until frequently used pages are loaded again. - Watch
free -hand swap usage over a normal traffic cycle to make sure the new value fits alongside everything else.
innodb_buffer_pool_size and the other tuned values. After the move, choose a new value for the new server's RAM and the services running on it, and confirm it with a query instead of assuming it took effect. Upgrading the hardware only helps if the configuration changes with it.