ublk ドライバを試す

Linux の 6.0 では Userspace block device driver(ublk driver)という機能が追加された。

名前の通り、ユーザーランドでブロックデバイスを実装するためのドライバになっている。

ドキュメントは以下にある。 https://github.com/torvalds/linux/blob/v6.0/Documentation/block/ublk.rst

このドキュメントによると、既存の仮想ブロックドライバをユーザーベースに移行したいというモチベーションがあるらしい。

利点としては以下が挙げられている。

ユーザースペースプログラムのことを、ublk serverと呼称している。

実装には、blk-mq リクエストベースドライバを利用していて、各 IO に tag が振られる。 ublk serverも同様に tag を割り当てて、それが一対一にマッピングされるらしい。

リクエストのやり取りには io_uring を利用していて、ドライバとのやり取りで使うだけでなく、 ublk server内の IO にも使うことが推奨されている。

ここに実装の一例がある。 https://github.com/ming1/ubdsrv

楽に実装するためのライブラリもある。 https://github.com/ming1/ubdsrv/tree/master/lib

このドライバをコントロールするためのグローバルなコントロール用のデバイスとして、/dev/ublk-controlがある。 上述の実装を見ると、このデバイスとのやり取りも io_uring を使うらしい。

新しいデバイスを追加するためには、ublk serverが新しいデバイスとの IO をやり取りするためのデバイスを用意する必要がある。そのためのコマンドをコントロールバイスに発行すると、/dev/ublkc*が作られる。 その後、ブロックデバイスの追加コマンドを発行すると/dev/ublkb*にブロックデバイスが作られる。

ublk serverはキューごとにスレッドを作成し、io_uringをハンドルする必要がある。 server がUBLK_IO_FETCH_REQを発行すると、ドライバがIOリクエストの転送を始める。 IOはublksrv_io_descエンコードされており、serverが処理を終えたら結果をUBLK_IO_COMMIT_AND_FETCH_REQコマンドを使って返す。このコマンドで次のリクエスト処理も開始される。

将来の開発計画として、コンテナから ublk を使えるようにしたり、zero copy できるようにしたいそうだ。

それでは実際に使ってみる。実行環境は 2022/11/23 時点の最新の arch linux。 デフォルトではキューの数が1だったので、ublk add-qオプションでキューの数を32個に増やしている

sudo pacman -S autoconf-archive
git clone https://github.com/ming1/ubdsrv.git
cd ubdsrv
autoreconf -i
./configure
make
sudo modprobe brd rd_size=4194304
sudo modprobe ublk_drv
sudo ./ublk add -t loop -q 32 -f /dev/ram0
sudo mkfs.ext4 /dev/ublkb0
mkdir -p t
sudo mount /dev/ublkb0 t
cd t
sudo fio --name=rw --size=250M --rw=randrw --nrfiles=8 --ioengine=libaio --iodepth=16 --direct=1 --overwrite=1 --runtime=60 --time_based --numjobs=4 --group_reporting
rw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=16
...
fio-3.33
Starting 4 processes
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
Jobs: 4 (f=32): [m(4)][100.0%][r=703MiB/s,w=703MiB/s][r=180k,w=180k IOPS][eta 00m:00s]
rw: (groupid=0, jobs=4): err= 0: pid=32492: Wed Nov 23 15:35:29 2022
  read: IOPS=180k, BW=704MiB/s (738MB/s)(41.3GiB/60001msec)
    slat (nsec): min=1150, max=116402, avg=8394.65, stdev=4924.49
    clat (nsec): min=940, max=766996, avg=167269.66, stdev=49871.85
     lat (usec): min=16, max=775, avg=175.66, stdev=52.93
    clat percentiles (usec):
     |  1.00th=[   67],  5.00th=[   77], 10.00th=[   86], 20.00th=[  143],
     | 30.00th=[  159], 40.00th=[  167], 50.00th=[  174], 60.00th=[  178],
     | 70.00th=[  184], 80.00th=[  192], 90.00th=[  206], 95.00th=[  253],
     | 99.00th=[  322], 99.50th=[  343], 99.90th=[  388], 99.95th=[  420],
     | 99.99th=[  498]
   bw (  KiB/s): min=599648, max=1156024, per=100.00%, avg=721015.53, stdev=21708.24, samples=476
   iops        : min=149912, max=289006, avg=180253.88, stdev=5427.06, samples=476
  write: IOPS=180k, BW=703MiB/s (738MB/s)(41.2GiB/60001msec); 0 zone resets
    slat (nsec): min=1200, max=176111, avg=8791.47, stdev=5048.88
    clat (nsec): min=910, max=783474, avg=168873.39, stdev=50225.29
     lat (usec): min=18, max=799, avg=177.66, stdev=53.40
    clat percentiles (usec):
     |  1.00th=[   68],  5.00th=[   77], 10.00th=[   87], 20.00th=[  145],
     | 30.00th=[  161], 40.00th=[  169], 50.00th=[  176], 60.00th=[  180],
     | 70.00th=[  186], 80.00th=[  192], 90.00th=[  208], 95.00th=[  255],
     | 99.00th=[  326], 99.50th=[  343], 99.90th=[  392], 99.95th=[  424],
     | 99.99th=[  502]
   bw (  KiB/s): min=599456, max=1154560, per=100.00%, avg=720465.01, stdev=21605.03, samples=476
   iops        : min=149864, max=288640, avg=180116.25, stdev=5401.26, samples=476
  lat (nsec)   : 1000=0.01%
  lat (usec)   : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%, 50=0.23%
  lat (usec)   : 100=13.17%, 250=81.43%, 500=5.16%, 750=0.01%, 1000=0.01%
  cpu          : usr=8.49%, sys=31.36%, ctx=29876275, majf=0, minf=52
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=100.0%, 32=0.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.1%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=10813820,10805346,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=16

Run status group 0 (all jobs):
   READ: bw=704MiB/s (738MB/s), 704MiB/s-704MiB/s (738MB/s-738MB/s), io=41.3GiB (44.3GB), run=60001-60001msec
  WRITE: bw=703MiB/s (738MB/s), 703MiB/s-703MiB/s (738MB/s-738MB/s), io=41.2GiB (44.3GB), run=60001-60001msec

Disk stats (read/write):
  ublkb0: ios=10807636/10799329, merge=0/42, ticks=255538/258038, in_queue=513576, util=99.89%

以前作ったNBDベースのメモリーバックエンドのユーザーランドブロックデバイスで同様にベンチマークを取ると以下のようになった。 こちらもublkと同様にサーバは32スレッドで動かしている。

rw: (g=0): rw=randrw, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=16
...
fio-3.33
Starting 4 processes
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
rw: Laying out IO files (8 files / total 250MiB)
Jobs: 4 (f=32): [m(4)][100.0%][r=655MiB/s,w=653MiB/s][r=168k,w=167k IOPS][eta 00m:00s]
rw: (groupid=0, jobs=4): err= 0: pid=49755: Wed Nov 23 16:16:34 2022
  read: IOPS=166k, BW=648MiB/s (680MB/s)(38.0GiB/60001msec)
    slat (nsec): min=1730, max=664245, avg=10800.29, stdev=5755.01
    clat (nsec): min=1920, max=1455.3k, avg=180044.58, stdev=27830.83
     lat (usec): min=19, max=1472, avg=190.84, stdev=28.91
    clat percentiles (usec):
     |  1.00th=[  120],  5.00th=[  137], 10.00th=[  147], 20.00th=[  159],
     | 30.00th=[  165], 40.00th=[  174], 50.00th=[  180], 60.00th=[  186],
     | 70.00th=[  194], 80.00th=[  202], 90.00th=[  215], 95.00th=[  227],
     | 99.00th=[  255], 99.50th=[  269], 99.90th=[  306], 99.95th=[  326],
     | 99.99th=[  379]
   bw (  KiB/s): min=625760, max=714384, per=100.00%, avg=664019.76, stdev=4227.49, samples=476
   iops        : min=156440, max=178596, avg=166004.92, stdev=1056.89, samples=476
  write: IOPS=166k, BW=648MiB/s (679MB/s)(37.9GiB/60001msec); 0 zone resets
    slat (nsec): min=1660, max=532044, avg=11640.12, stdev=6046.44
    clat (usec): min=12, max=1441, avg=182.55, stdev=27.93
     lat (usec): min=29, max=1460, avg=194.19, stdev=29.04
    clat percentiles (usec):
     |  1.00th=[  124],  5.00th=[  141], 10.00th=[  149], 20.00th=[  161],
     | 30.00th=[  169], 40.00th=[  176], 50.00th=[  182], 60.00th=[  188],
     | 70.00th=[  196], 80.00th=[  204], 90.00th=[  217], 95.00th=[  231],
     | 99.00th=[  260], 99.50th=[  273], 99.90th=[  310], 99.95th=[  334],
     | 99.99th=[  392]
   bw (  KiB/s): min=626976, max=717352, per=100.00%, avg=663490.35, stdev=4212.33, samples=476
   iops        : min=156744, max=179338, avg=165872.59, stdev=1053.08, samples=476
  lat (usec)   : 2=0.01%, 10=0.01%, 20=0.01%, 50=0.01%, 100=0.05%
  lat (usec)   : 250=98.53%, 500=1.42%, 750=0.01%, 1000=0.01%
  lat (msec)   : 2=0.01%
  cpu          : usr=5.53%, sys=34.42%, ctx=23019937, majf=0, minf=44
  IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=100.0%, 32=0.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.1%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=9955048,9947240,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=16

Run status group 0 (all jobs):
   READ: bw=648MiB/s (680MB/s), 648MiB/s-648MiB/s (680MB/s-680MB/s), io=38.0GiB (40.8GB), run=60001-60001msec
  WRITE: bw=648MiB/s (679MB/s), 648MiB/s-648MiB/s (679MB/s-679MB/s), io=37.9GiB (40.7GB), run=60001-60001msec

Disk stats (read/write):
  nbd0: ios=9935485/9927606, merge=0/42, ticks=239988/241003, in_queue=480991, util=99.89%
sudo fio --name=rw --size=250M --rw=randrw --nrfiles=8 --ioengine=libaio       13.68s user 83.45s system 157% cpu 1:01.61 total

ublkは700MiB/s程度出ているが、nbdでは650MiB/s程度なのでublkのほうが少し速い。 またnbdではサーバ内で確保したメモリーに書くだけなので、ublkも同様にすればより速くなるかもしれない。

なお実験は、CPUがAMD Ryzen 9 3950X 16Coreでメモリーは32GiB(DDR4-2133)の環境を使った。