NVMe Storage for Databases: How Much IOPS and Capacity Do You Need?

Updated on Nov 13, 2026
Kimberly N
9 MINS READ
Table of Contents
NVMe Storage and IOPS for a Database Server

Most teams pick disks by size alone, then wonder why the database is slow. The right answer comes from measuring your own workload and turning those numbers into IOPS, latency, and capacity targets. This guide shows how to do database server IOPS sizing for PostgreSQL and MySQL.

NVMe usually gives lower latency and more parallel I/O than SATA SSD or HDD, but many queries are limited by CPU, RAM, locks, or bad indexes. So you must measure, then buy.

What IOPS Really Means

IOPS means I/O operations per second. One operation is one read or one write request to the disk. The number alone is not enough, because it depends on four things:

  1. Block size: PostgreSQL uses 8 KB pages by default, and MySQL InnoDB uses 16 KB pages by default.
  2. Read/write mix: writes cost more than reads, because they must be made safe on disk.
  3. Queue depth: how many requests are waiting at the same time.
  4. Latency: how long each request takes.

A disk that shows 500,000 IOPS in a vendor sheet may show far less with small synced writes at queue depth 1. That is why database server IOPS sizing must use your own block size, mix, and queue depth.

Step 1: Measure Your Live Workload

Start on your current server. Run these checks during the busiest hour. Collect data for at least one full day, or a week if you can. This is the first step of database server IOPS sizing.

Install the tools on Ubuntu or Debian with the commands below:

Bash
sudo apt updatesudo apt install sysstat fio -y

Save disk stats every 5 seconds for one hour:

Bash
iostat -x -d 5 720 > /tmp/iostat-peak.log

Check these columns for your data disk. Names may change a little between sysstat versions:

  • r/s and w/s: Read and write IOPS.
  • rkB/s and wkB/s: Throughput.
  • r_await and w_await: Average latency in ms.
  • aqu-sz: Average queue depth.
  • %util: Busy time. On NVMe, 100% does not mean the disk is full, because NVMe handles many requests at the same time.

Use the peak numbers, not the daily average. If you size for the average, the disk will be too slow at busy times.

Check Database Counters

For PostgreSQL, check transactions, block reads, and WAL volume. The pg_stat_wal view exists in PostgreSQL 14 and newer:

SQL
SELECT now(), xact_commit, blks_read, blks_hit FROM pg_stat_database WHERE datname = current_database();SELECT now(), wal_records, wal_bytes FROM pg_stat_wal;

For MySQL, check InnoDB data and log activity:

SQL
SHOW GLOBAL STATUS WHERE Variable_name IN('Innodb_data_reads','Innodb_data_writes','Innodb_data_fsyncs','Innodb_os_log_fsyncs','Com_commit');

Run each query two times, 60 seconds apart. Subtract the first number from the second, then divide by 60 to get the rate per second.

Note that PostgreSQL blks_read can include reads that came from the OS cache. For real disk activity, trust iostat.

Step 2: Find Read/Write Ratio and Queue Depth

Use the peak numbers from your iostat log to find the read and write share:

read share  = r/s / (r/s + w/s)write share = w/s / (r/s + w/s)

Common read/write splits include; your values may differ:

  • Reporting or search apps: mostly reads, like 90/10.
  • Shop or SaaS apps: mixed, like 70/30.
  • Logging or queue apps: mostly writes, like 30/70.

Queue depth is the aqu-sz column. A low value, like 1 to 4, means your app sends only a few requests. In that case, a very fast disk will not help much. A high value, together with rising await, means the disk is the bottleneck.

This matters for database server IOPS sizing: Test at your real queue depth, not at the highest depth a benchmark can reach.

Step 3: Measure fsync Latency

Before a database says committed, it must save the commit log to disk. PostgreSQL does this with the write-ahead log (WAL). MySQL InnoDB does it with the redo log. Each save waits for the disk, so fsync speed limits how many commits per second one connection can make.

PostgreSQL has a tool to test this, which is pg_test_fsync. Put the test file on the same file system as pg_wal. Also, the results may not reflect real speed if your server is not limited by its WAL.

Bash
/usr/lib/postgresql/16/bin/pg_test_fsync -f /var/lib/postgresql/fsync.test -s 10rm /var/lib/postgresql/fsync.test

Also, you can test it with fio. This writes one 8 KB block and syncs it each time:

Bash
fio --name=fsync-test --filename=/var/lib/postgresql/fio-fsync.test \    --size=1G --rw=write --bs=8k --ioengine=sync \    --fdatasync=1 --runtime=60 --time_based

Check the sync latency percentiles. Consumer NVMe drives without power-loss protection can be slow. Enterprise drives with it are usually faster and steadier, but test before you trust that.

Do not turn off fsync or lower innodb_flush_log_at_trx_commit for better numbers. A crash can lose committed data.

Step 4: Test the Disk With fio

Now test the new disk with fio, using the numbers you measured. This makes database server IOPS sizing real, because it shows what the disk can do at your block size. Never run fio on a device that holds data. Also, avoid running it on a live production disk at peak time.

This example uses a 70/30 read/write mix with 8 KB blocks (PostgreSQL). For MySQL, use --bs=16k:

Bash
fio --name=db-mixed --filename=/data/fio-test.bin --size=20G \    --rw=randrw --rwmixread=70 --bs=8k \    --ioengine=libaio --direct=1 \    --iodepth=8 --numjobs=4 \    --ramp_time=15 --runtime=120 --time_based \    --group_reporting --output=db-mixed.txt
  • --direct=1 skips the page cache, so you test the disk, not RAM.
  • --iodepth works only with async engines like libaio. Check the fio output to confirm the real depth.
  • Total queue depth is about iodepth x numjobs (here, 32).

Run the test at total depths like 4, 16, and 64. Write down the IOPS and the 99th percentile latency (p99) for each run. Pick the run where p99 is still within your goal, like 2 ms. That IOPS number is your usable capacity, and it is usually lower than the peak number.

Step 5: Check Write Amplification

Write amplification means the disk writes more data than the database sends, and it affects drive life. It comes from several layers:

  • PostgreSQL: It writes to the WAL first and to the data pages later. After a checkpoint, the first change to a page can write a full page image into the WAL.
  • MySQL InnoDB: It uses a redo log and a doublewrite buffer. Data is written twice, but in one big sequential chunk with a single fsync() call. So it does not double the I/O operations.
  • The SSD: It also moves and rewrites flash blocks inside, which is called garbage collection.

To see the total effect, compare what the database wrote with what the drive wrote. Check the NVMe write counter before and after a busy day:

Bash
sudo apt install nvme-cli -ysudo nvme smart-log /dev/nvme0 | grep -i -E "data_units_written|percentage_used"

One data unit is 512,000 bytes. So bytes written equals data_units_written x 512,000. Subtract the two readings to get one day of writes. This counts only what the server sent to the drive, not the drive's own internal writes.

Then, compare it with your drive's endurance rating, DWPD (drive writes per day) or TBW (terabytes written). For example, 1 TB of writes per day on a 3.84 TB drive is about 0.26 DWPD.

Step 6: Turn Measurements Into Disk Needs

Here is an example of database server IOPS sizing. The numbers are made up, so use your own.

  • Peak load: 4,000 IOPS, 70% reads, and 30% writes
  • Block size: 8 KB
  • p99 latency goal: 2 ms
  • Data size: 800 GB
  • WAL or binlog space: 100 GB
  • Temp space: 100 GB
  • Growth: 40% per year, planned for 2 years

First, find the IOPS you need after growth and extra space:

growth      = 1.4 x 1.4 = 1.96future peak = 4,000 x 1.96 = 7,840 IOPSheadroom    = x 2 (run at about 50% at peak)target      = 15,680, round up to 16,000 IOPS at p99 under 2 ms

Throughput check: 16,000 IOPS x 8 KB is about 128 MB/s. Any modern NVMe drive can handle that. The real limits are IOPS and latency.

Next, find how much storage you need after growth, extra files, and free space:

data in 2 years = 800 x 1.96  = 1,568 GBadd WAL + temp  = 1,568 + 200 = 1,768 GBkeep 30% free   = 1,768 / 0.70 = about 2,526 GB usable

RAID and Backup Space

RAID and backups change how much disk space you need. RAID decides how much of your disk space you can actually use. Backups need their own space, too. Plan for both before you pick your drives.

Layout Usable space Notes
RAID 1 (2 disks) 1 disk Simple, good for databases, 2x write to disks
RAID 10 (4 disks) 2 disks More IOPS and space, common for busy databases
RAID 5 (3+ disks) N-1 disks Extra parity writes, so small random writes cost more

In our example, RAID 1 with two 3.84 TB NVMe drives gives about 3.84 TB of usable space. That covers the 2,526 GB target with extra room. Test the IOPS on the final RAID setup, not on a single disk.

RAID is not a backup. It protects you from a disk failure, not from a bad DELETE or ransomware. Keep backups on another disk or another server. If you keep a local backup, add its size to your plan. The exact size depends on your data.

Database Server IOPS Sizing: Pick the Right NVMe Setup

Now you can compare your target with the server specs. For production database server IOPS sizing, a dedicated server gives you NVMe drives that no one else uses, so your fio results stay steady over time.

Check the NVMe dedicated servers on PerLod and match their drive sizes to your IOPS and capacity numbers.

Before you buy, keep these in mind:

  • More IOPS will not fix slow queries. Check EXPLAIN plans and indexes first.
  • More RAM can cut disk reads, because hot data stays in cache.
  • One server has no automatic failover. Plan replicas.
  • Measure again every quarter. Growth is rarely steady.

To practice the fio steps before you move to bare metal, run a small test on an NVMe VPS with root access. VPS storage can be shared or limited, so use the results to learn the method, not to size your final server.

Conclusion

Good database server IOPS sizing starts with your own numbers. Measure your peak IOPS, read/write mix, queue depth, and fsync latency. Test the new disk with fio, then add room for growth, RAID, free space, and backups.

NVMe is a great choice for most databases, but the test results should make the decision, not the label on the disk.

If you are not sure a VPS is enough for your database, you can check this guide on VPS vs Dedicated Server for Databases to see which one fits your workload.

It depends on your peak. First measure with iostat -x, then multiply by your growth and add about 2x headroom.

No. NVMe usually offers lower latency and greater parallelism, but CPU, RAM, and query design can be the real limits.

Lower and steadier is better. There is no single right number, so compare p99 with your commit latency goal.

Use 8k for PostgreSQL and 16k for MySQL InnoDB, since those are the default page sizes.