SSD 的性能指標主要包括:IOPS、帶寬和延遲。
IOPS (Input/Output Operations per Second)是指每秒能處理的 I/O 個數。一般 OLTP 的資料庫應用需要關注 IOPS 性能。帶寬是指單位時間內可以成功傳輸的數據量(MB/s)。大量順序讀寫的應用,例如離線計算任務,需要關注帶寬。延遲是指 SSD 處理一個 I/O 需要的時間(us)。一般情況下,在線業務對高延遲比較敏感,離線任務對高延遲比較不敏感。dd 命令dd 是 Linux 下的一個命令,其作用是用指定大小的塊拷貝一個文件,並在拷貝的同時進行指定的轉換。
dd 的幾個主要參數(命令的詳細參數、用法可以參考 Linux man page[1] ):
ibs: 輸入塊大小(input block size)obs: 輸出塊大小(output block size)bs: 輸入和輸出的塊大小(input and output block size)iflag: 輸入 flag(input flag)oflag: 輸出 flag(outout flag)幾個常用的 flag 參數:
dsync:寫入文件塊後,調用 fdatasyncnonblock: 採用非阻塞的方式讀寫文件。在 Linux 下,只有 O_DIRECT 的文件 I/O 支持非阻塞/dev/zero 是 Linux 提供的一個特殊文件,可以無限產生 0x00 字符,從這個文件讀取不產生 I/O,可以用來作為輸入文件測試 SSD 的純寫速度。
/dev/null 也會 Linux 提供的一個特殊文件,提供一種「無底洞」的特性,往這個文件寫入任何數據都不會產生 I/O,可以作為輸出文件測試 SSD 的純讀速度。
用於測試 SSD 的命令:
dd if=/dev/zero of=test.data bs=4k oflag=direct,nonblock count=1000000
dd if=/dev/zero of=test.data bs=4k oflag=direct,nonblock,dsync count=1000000
dd if=/dev/zero of=test.data bs=4k oflag=direct,nonblock,sync count=1000000dd 命令行工具的優點是使用簡單,並且 Linux 作業系統一般都有自帶,不需要額外安裝。缺點也是因為太簡單了:
單進程單線程運行,無法充分發揮 SSD 的並行特性。pg_test_fsyncpg_test_fsync 是 PostgreSQL 提供的一個測試工具,主要作用是讓用戶方便快捷地了解特定系統上最快的 wal_sync_method。pg_test_fsync 的執行結果報告了每個 wal_sync_method 的平均文件同步操作時間。
簡單說,pg_test_fsync 可以用來測試 fsync/fdatasync 的延遲。
pg_test_fsync 可以從 PostgreSQL 的官網[2]下載原始碼進行編譯。比如:
我下載的是 postgresql-13.1.tar.bz2[3]。切換帶原始碼的根目錄下:cd postgresql-13.1切換帶 pg_test_fsync 的代碼目錄下:cd src/bin/pg_test_fsync運行 pg_test_fsync 時遇到一個小問題:
8 * 2kB open_sync writes pg_test_fsync: error: write failed: Invalid argument
原因是使用 O_DIRECT 對寫入的數據有對齊要求:
Under Linux 2.4, transfer sizes, and the alignment of the user buffer and the file offset must all be multiples of the logical block size of the filesystem. Since Linux 2.6.0, alignment to the logical block size of the underlying storage (typically 512 bytes) suffices. The logical block size can be determined using the ioctl(2) BLKSSZGET** **operation or from the shell using the command:blockdev --getss
在我的測試機器上,這個 block size 是 4096 而 pg_test_fsync 這個工具認為是 512:
cat /sys/block/nvme0n1/queue/physical_block_size4096
最簡單的做法是將不符合這個要求的兩行代碼注釋掉:
static void
test_open_syncs(void)
{
printf(_("\nCompare open_sync with different write sizes:\n"));
printf(_("(This is designed to compare the cost of writing 16kB in different write\n"
"open_sync sizes.)\n"));
test_open_sync(_(" 1 * 16kB open_sync write"), 16);
test_open_sync(_(" 2 * 8kB open_sync writes"), 8);
test_open_sync(_(" 4 * 4kB open_sync writes"), 4);
//test_open_sync(_(" 8 * 2kB open_sync writes"), 2);
//test_open_sync(_("16 * 1kB open_sync writes"), 1);
}執行結果示例:
5 seconds per test
O_DIRECT supported on this platform for open_datasync and open_sync.
Compare file sync methods using one 8kB write:
(in wal_sync_method preference order, except fdatasync is Linux's default)
open_datasync 63591.342 ops/sec 16 usecs/op
fdatasync 52872.641 ops/sec 19 usecs/op
fsync 52138.296 ops/sec 19 usecs/op
fsync_writethrough n/a
open_sync 65479.469 ops/sec 15 usecs/op
Compare file sync methods using two 8kB writes:
(in wal_sync_method preference order, except fdatasync is Linux's default)
open_datasync 32794.672 ops/sec 30 usecs/op
fdatasync 38832.670 ops/sec 26 usecs/op
fsync 36112.149 ops/sec 28 usecs/op
fsync_writethrough n/a
open_sync 33255.280 ops/sec 30 usecs/op
Compare open_sync with different write sizes:
(This is designed to compare the cost of writing 16kB in different write
open_sync sizes.)
1 * 16kB open_sync write 50401.319 ops/sec 20 usecs/op
2 * 8kB open_sync writes 32680.378 ops/sec 31 usecs/op
4 * 4kB open_sync writes 19378.457 ops/sec 52 usecs/op
Test if fsync on non-write file descriptor is honored:
(If the times are similar, fsync() can sync data written on a different
descriptor.)
write, fsync, close 47270.649 ops/sec 21 usecs/op
write, close, fsync 45598.608 ops/sec 22 usecs/op
Non-sync'ed 8kB writes:
write 380100.992 ops/sec 3 usecs/op這裡簡單解釋一下執行結果:
open_datasync: open 文件的時候指定 O_DSYNC。fdatasync: write 之後調用 fdatasync。fsync_writethrough: 是 Windows 的,這裡忽略。open_sync: open 文件的時候指定 O_SYNC。8kB 的 sync 延遲不到 20us,16kB 的 sync 延遲大約 30us。O_SYNC 和 O_DSYNC 延遲差別不大;fsync 和 fdatasync 延遲差別不大。O_SYNC/O_DSYNC 的延遲看起來略優於 fsync/fdatasync。直接寫 page cache 不進行 sync 的延遲大概是 3us => 可以看出,sync 非常影響資料庫應用的延遲/性能。fiofio[4] 是一個多線程 I/O 生成工具,可以生成多種 I/O 模式,可以用來測試 SSD 的性能,特別是 IOPS。
fio 是個功能挺複雜的工具,這裡先簡單介紹幾個常用的命令參數:
--name=fio-test # 任務名稱--filename=/dev/sda # 讀寫的文件或者設備--direct=1 # 使用 O_DIRECT 讀寫數據(繞過 page cache)--rw=randread # 隨機讀--rw=randwrite # 隨機寫--rw=randrw # 隨機讀寫混合--rw=read # 順序讀--rw=write # 順序寫--rw=rw # 順序讀寫混合--rwmixwrite=30 # 混合讀寫的模式下,寫佔30%--bs=4k # 單次 I/O 的數據塊大小為 4k--bsrange=512-2048 # 單機 I/O 的數據塊大小範圍--size=5G # 測試文件大小為 5GB--numjobs=30 # 30 個線程--time_based # 基於時間運行,運行 --runtime 時間後結束--runtime=600 # 測試時間為 600 秒,如果不寫則一直將 --size 大小的文件寫完為止--ioengine=libaio # I/O 引擎,Linux 下一般用 libaio--group_reporting # 顯示結果匯總每個進程的信息--lockmem=1G # 只使用 1GB 內存進行測試--iodepth=32 # I/O 隊列深度,可以認為是使用異步 I/O 時同時提交的 I/O 數。這裡直接用幾個實際例子來說明。
fio \
--name=fio-test \
--filename=test.data \
--size=50G \
--bs=4k \
--rw=randwrite \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--time_based \
--runtime=600 \
--group_reportingfio \
--name=fio-test \
--filename=test.data \
--size=50G \
--bs=4k \
--rw=randread \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--time_based \
--runtime=600 \
--group_reportingfio \
--name=fio-test \
--filename=test.data \
--size=50G \
--bs=128k \
--rw=write \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--time_based \
--runtime=600 \
--group_reportingfio \
--name=fio-test \
--filename=test.data \
--size=50G \
--bs=128k \
--rw=read \
--ioengine=libaio \
--direct=1 \
--iodepth=32 \
--time_based \
--runtime=600 \
--group_reportingfio 的輸出示例:
fio-test: (groupid=0, jobs=1): err= 0: pid=41759: Fri Dec 4 16:05:04 2020
write: IOPS=245k, BW=956MiB/s (1003MB/s)(560GiB/600001msec); 0 zone resets
slat (nsec): min=1251, max=1708.0k, avg=2791.81, stdev=1767.94
clat (usec): min=7, max=2171, avg=126.93, stdev=17.05
lat (usec): min=10, max=2174, avg=129.78, stdev=17.15
clat percentiles (usec):
| 1.00th=[ 85], 5.00th=[ 102], 10.00th=[ 111], 20.00th=[ 119],
| 30.00th=[ 123], 40.00th=[ 125], 50.00th=[ 126], 60.00th=[ 128],
| 70.00th=[ 130], 80.00th=[ 135], 90.00th=[ 145], 95.00th=[ 159],
| 99.00th=[ 182], 99.50th=[ 192], 99.90th=[ 210], 99.95th=[ 223],
| 99.99th=[ 253]
bw ( KiB/s): min=644168, max=999168, per=100.00%, avg=979886.11, stdev=23593.11, samples=1199
iops : min=161042, max=249792, avg=244971.55, stdev=5898.29, samples=1199
lat (usec) : 10=0.01%, 20=0.01%, 50=0.05%, 100=4.22%, 250=95.71%
lat (usec) : 500=0.01%, 750=0.01%, 1000=0.01%
lat (msec) : 2=0.01%, 4=0.01%
cpu : usr=28.70%, sys=71.31%, ctx=47128, majf=0, minf=31
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=100.0%, >=64=0.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.1%, 64=0.0%, >=64=0.0%
issued rwts: total=0,146906790,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=32
Run status group 0 (all jobs):
WRITE: bw=956MiB/s (1003MB/s), 956MiB/s-956MiB/s (1003MB/s-1003MB/s), io=560GiB (602GB), run=600001-600001msec
Disk stats (read/write):
nvme2n1: ios=2/146881125, merge=0/0, ticks=0/2404296, in_queue=2433748, util=100.00%上面是我在某臺測試機器上用 fio 測試隨機寫 IOPS(4K 塊、隊列深度 32) 的輸出結果。這裡簡單解釋一下:
第二行總結了本次測試的結果:寫操作 IOPS 245k,帶寬(BW)1003 MB/s。接下來的 slat、clat、lat 是延遲信息:slat: 提交延遲(submission latency)。提交延遲只對異步 I/O 有意義。clat: 完成延遲(completion latency)。I/O 提交成功到 I/O 完成的時間。lat: 總延遲(total latency)。I/O 請求從創建到完成的時間。min 是本次測試的最小值;max 是本次測試的最大值;stdev 是標準差。總結本文介紹了 3 個可以用來測試 SSD 的工具:dd、pg_test_fsync、fio:
dd 命令功能比較簡單,已經不適合用來測試 SSD 了。pg_test_fsync 可以很方便快捷地測試 SSD 的 sync 性能。fio 是個強大的工具,各種工作負載都能模擬,輸出信息比較豐富:IOPS、帶寬、延遲應有盡有。本文舉的幾個例子只是裡面的九牛一毛(不過也達到了我本次的測試目的)。一般網上或產品提供商提供的性能數據只能作為一個參考,因為不同的工作負載、不同的型號的 SSD 都會影響實際的使用性能,真實數據還是要自己實際測試才靠譜。
參考資料[1]Linux man page: https://man7.org/linux/man-pages/man1/dd.1.html
[2]PostgreSQL 的官網: https://www.postgresql.org/
[3]postgresql-13.1.tar.bz2: https://ftp.postgresql.org/pub/source/v13.1/postgresql-13.1.tar.bz2
[4]fio: https://fio.readthedocs.io/en/latest/fio_doc.html