LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Raspberry Pi 4 で USBフラッシュドライブ に Bcachefs を試してみる

背景

Bcachefs は LInuxカーネル 6.7 からサポートされた Copy-On-Write (CoW) のファイルシステムである。 Bcachefs は、従来のLinuxでサポートされていた "bcache" をベースとしており、堅牢性と信頼性に加えて、多くの機能をサポートしていることで注目を浴びている。

目的

手元の Raspberry Pi 4 Model B (Raspberry Pi 4) で Bcachefs のドキュメントに従って実行することで、挙動や機能の概要を把握する。

実行環境

Raspberry Pi 4 は microSDカード経由でRaspberry Pi OSを起動させる。

計測環境の概要

ここで使用するRaspberry Pi 4のスペックについて、必要な情報だけ抜粋したものを下記に示す。

項目 Raspberry Pi 4
CPU Cortex-A72 (ARM v8) 1.5GHz
メモリ 4GB LPDDR4-3200
OS Raspberry Pi OS Lite (Mar 15th 2024)
Linux kernel v6.9-rc31
bcachefs-tools version v0.1-nogit
fio fio-3.33
micro SD card KTHN-MW016G
USB 3.0 (1) USM32GU
USB 3.0 (2) SP032GBUF3B02V1K
USB 2.0 (1) USM32GR
ケース 陽極酸化アルミニウム製ヒートシンクケース

シングルドライブでの実験

単一のUSBフラッシュドライブのみに対して bcachefsを使用してみる。

ディスクパーティション /dev/sda を bcachefs でフォーマットするには、 bcacahefs format コマンドを実行する。

pi@raspberrypi:~$ sudo bcachefs format /dev/sda
External UUID:                              87cb6bbc-c417-4d66-8053-a96e07bc1dc2
Internal UUID:                              19bbba4a-3a3d-4f72-974d-dcaedc54bbdb
Device index:                               0
Label:
Version:                                    unwritten_extents
Oldest version on disk:                     unwritten_extents
Created:                                    Wed Apr 17 05:53:46 2024
Sequence number:                            0
Superblock size:                            816
Clean:                                      0
Devices:                                    1
Sections:                                   members
Features:                                   new_siphash,new_extent_overwrite,btree_ptr_v2,extents_above_btree_updates,btree    _updates_journalled,new_varint,journal_no_flush,alloc_v2,extentssCompat features:

Options:
  block_size:                               512 B
  btree_node_size:                          256 KiB
  errors:                                   continue [ro] panic
  metadata_replicas:                        1
  data_replicas:                            1
  metadata_replicas_required:               1
  data_replicas_required:                   1
  encoded_extent_max:                       64.0 KiB
  metadata_checksum:                        none [crc32c] crc64 xxhash
  data_checksum:                            none [crc32c] crc64 xxhash
  compression:                              [none] lz4 gzip zstd
  background_compression:                   [none] lz4 gzip zstd
  str_hash:                                 crc32c crc64 [siphash]
  metadata_target:                          none
  foreground_target:                        none
  background_target:                        none
  promote_target:                           none
  erasure_code:                             0
  inodes_32bit:                             1
  shard_inode_numbers:                      1
  inodes_use_key_cache:                     1
  gc_reserve_percent:                       8
  gc_reserve_bytes:                         0 B
  root_reserve_percent:                     0
  wide_macs:                                0
  acl:                                      1
  usrquota:                                 0
  grpquota:                                 0
  prjquota:                                 0
  journal_flush_delay:                      1000
  journal_flush_disabled:                   0
  journal_reclaim_delay:                    100
  nocow:                                    0

members (size 64):
  Device:                                   0
    UUID:                                   6b8b7038-fa14-4c21-8d1e-00b0cac0adf6
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118296
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0
initializing new filesystem
going read-write
initializing freespace
mounted version=unwritten_extents opts=noinodes_use_key_cache

kernel が bcachefs をサポートしている場合、従来のファイルシステムと同様に mountコマンドにより指定したマウントポイントに bcachefs をマウントすることができる。

pi@raspberrypi:~$ sudo mount -t bcachefs /dev/sda /mnt/

ここで、Flexible I/O tester (FIO) による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

// 1:
[global]
ioengine=libaio
size=4G
invalidate=1
direct=1
verify=0
randrepeat=0
unlink=0
sync=0
; 順次読み込みの場合は rw=read
rw=write
bs=1M
time_based=1

[job]
name=write_bandwidth_test
; ブロックデバイスでの測定の場合は file=/dev/sda
directory=/mnt
ramp_time=2
runtime=5m
numjobs=4
group_reporting=1
iodepth=1

上記の測定結果(bcachefs上のファイルにアクセスした場合)に加えて、ブロックデバイスに直接アクセスした場合の書き込み/読み込み帯域幅を図示すると次のようになった。

シングルドライブにおける bcachefs による書き込み帯域幅オーバーヘッド測定

シングルドライブにおける bcachefs による読み込み帯域幅オーバーヘッド測定

この結果だけ見ると、Bcachefs によるオーバーヘッドが大きく見えてしまうが、"データと"ファイル"へのアクセスを比較しているため、これを性能の優劣をつけることはできない。 本来であれば、測定結果の妥当性の確認もしておきたいが、それは本記事の目的から外れるため割愛する。

暗号化

Bcachefsでは、認証付暗号化方式 AEAD の暗号化 (ChaCha20/Poly1305) をサポートしている。 これにより、ファイルシステム全体に対して暗号化することができ、スーパーブロックを除くすべてのメタデータが暗号化される。

暗号化を使用して Bcachefs でフォーマットするには、bcachefs formatコマンドに --encryptedオプションを追加する。 このとき、passphrase の入力が求められる。

pi@raspberrypi:~$ sudo bcachefs format --encrypted /dev/sda
Enter passphrase:
Enter same passphrase again:
External UUID:                              0ac05aa8-9b50-48fd-9bfc-95a016a0e74e
Internal UUID:                              5da9ad34-0907-4eee-a2c4-e99378bb1719
Device index:                               0
Label:
Version:                                    unwritten_extents
Oldest version on disk:                     unwritten_extents
Created:                                    Mon Apr 15 00:08:25 2024
Sequence number:                            0
Superblock size:                            880
Clean:                                      0
Devices:                                    1
Sections:                                   members,crypt
Features:                                   new_siphash,new_extent_overwrite,btree_ptr_v2,extents_above_btree_updates,btree    _updates_journalled,new_varint,journal_no_flush,alloc_v2,extentssCompat features:

Options:
  block_size:                               512 B
  btree_node_size:                          256 KiB
  errors:                                   continue [ro] panic
  metadata_replicas:                        1
  data_replicas:                            1
  metadata_replicas_required:               1
  data_replicas_required:                   1
  encoded_extent_max:                       64.0 KiB
  metadata_checksum:                        none [crc32c] crc64 xxhash
  data_checksum:                            none [crc32c] crc64 xxhash
  compression:                              [none] lz4 gzip zstd
  background_compression:                   [none] lz4 gzip zstd
  str_hash:                                 crc32c crc64 [siphash]
  metadata_target:                          none
  foreground_target:                        none
  background_target:                        none
  promote_target:                           none
  erasure_code:                             0
  inodes_32bit:                             1
  shard_inode_numbers:                      1
  inodes_use_key_cache:                     1
  gc_reserve_percent:                       8
  gc_reserve_bytes:                         0 B
  root_reserve_percent:                     0
  wide_macs:                                0
  acl:                                      1
  usrquota:                                 0
  grpquota:                                 0
  prjquota:                                 0
  journal_flush_delay:                      1000
  journal_flush_disabled:                   0
  journal_reclaim_delay:                    100
  nocow:                                    0

members (size 64):
  Device:                                   0
    UUID:                                   466c5ef5-893c-4b7a-8781-0dcc2d2734ed
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118272
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0

暗号化された bcachefs ファイルシステムはロック状態となっているため、そのままではマウントすることはできない。

pi@raspberrypi:~$ sudo mount -t bcachefs /dev/sda /mnt/
mount: /mnt: mount(2) system call failed: Required key not available.
       dmesg(1) may have more information after failed mount system call.

pi@raspberrypi:~$ sudo dmesg | grep bcachefs
[  599.228656] bcachefs (cd0e560a-0916-4b26-9db8-5d4aa60500e4): error requesting encryption key: ENOKEY

暗号化された bcachefs ファイルシステムは、 bcachefs unlockコマンドによってロック解除することができる。 このとき、暗号化で使用した passphrase の入力が求められる。

pi@raspberrypi:~$ sudo bcachefs unlock /dev/sda                                                                                                                                             
Enter passphrase:

これにより、暗号化キーがカーネル内のキーリングに追加される。 ただし、ここからマウントなどする場合には、キーをセッションに手動でリンクする必要があるらしい。(または、unlockのときに-k sessionオプションを追加する)

Re: Mounting a encrypted disk: Fatal error: Required key not available - Martin Steigerwald

pi@raspberrypi:~$ sudo keyctl link @u @s

これによって、現在のセッションで暗号化された bcachefs が利用 (マウント) できるようになる。

pi@raspberrypi:~$ sudo mount -t bcachefs /dev/sda /mnt/

ここで、FIO による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

暗号化有効による書き込み帯域幅オーバーヘッド測定

暗号化有効による読み込み帯域幅オーバーヘッド測定

今回の測定では、暗号化機能を有効にした場合の書き込み帯域幅のオーバーヘッドは微小であった。 一方で、USB3.0(1)と(2)の読み込み帯域幅は25%程度の低下が見られた。また、USB2.0(1)の低下が微小であった。オーバーヘッドが微小であるのは、ストレージデバイスへのアクセスで律速しているケースと考えられる、

また、この機能を有効化していない場合、ブロックデバイス経由でbcachefsにあるファイルの名前が確認できたが、

pi@raspberrypi:~$ sudo xxd -a /dev/sda | grep -E "job\.[0-3]\.0" 
002801d0: 086a 6f62 2e30 2e30 0600 0000 0000 0000  .job.0.0........
0210fb70: 0210 0000 0000 0000 086a 6f62 2e33 2e30  .........job.3.0
03f19970: 0310 0000 0000 0000 086a 6f62 2e32 2e30  .........job.2.0
05d0c190: 0410 0000 0000 0000 086a 6f62 2e31 2e30  .........job.1.0
0ed40440: 0000 0020 0000 0000 086a 6f62 2e30 2e30  ... .....job.0.0
0ed40640: 0210 0000 0000 0000 086a 6f62 2e33 2e30  .........job.3.0
0ed40840: 0410 0000 0000 0000 086a 6f62 2e31 2e30  .........job.1.0
0ed40870: 086a 6f62 2e32 2e30 0000 0000 0000 0000  .job.2.0........ 

この機能を有効にしている場合、ブロックデバイス経由でbcachefsにあるファイルの名前を確認することはできなかった。

pi@raspberrypi:~$ sudo xxd -a /dev/sda | grep -E "job\.[0-3]\.0" 
pi@raspberrypi:~$  

圧縮

Bcachefsでは、データをエクステント単位による圧縮 (gzip、lz4、zstd) をサポートしている。 圧縮レベルは、015 を指定することができる。

さらに、コマンド bcachefs setattrによって特定のファイル/ディレクトリに対しても有効となっている。 また、rebalanceスレッドによって別のアルゴリズムによるデータを圧縮/再圧縮することもできる。

lz4で圧縮、zstdでバックグラウンド圧縮するためには、bcachefs formatコマンドに --compression--background_compressionオプションを追加する。

pi@raspberrypi:~$ sudo bcachefs format --compression=lz4 --background_compression=zstd /dev/sda

Bcachefs を /mnt以下にマウントする。

pi@raspberrypi:~$ sudo mount -t bcachefs /dev/sda /mnt/

ここで、FIO による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

圧縮有効による書き込み帯域幅オーバーヘッド測定

圧縮有効による読み込み帯域幅オーバーヘッド測定

また、この機能による効果を確認するために、巨大なテキストファイル群 (linux-6.9-rc4.tar) をコピーしてみる。

pi@raspberrypi:~$ ls -l /mnt
total 1454330
-rw-r--r-- 1 root root 1489233920 Apr 18 05:44 linux-6.9-rc4.tar
drwx------ 2 root root          0 Apr 18 05:42 lost+found    
pi@raspberrypi:~$ df /dev/sda
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/sda        27615550 662787  26538105   3% /mnt
pi@raspberrypi:~$ sync
pi@raspberrypi:~$ df /dev/sda
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/sda        27615550 397492  26799319   2% /mnt

対象のファイル群はファイルサイズ1.4GBであったが、bcachefsによる圧縮(lz4)の効果により 662KB の使用量まで抑えられている。 また、rebalanceスレッドによってzstdへと再圧縮されており、400KBまで減っていることが分かった。

マルチドライブでの実験

bcachefs はマルチデバイス2に対応しているファイルシステムである。

ここでは、Raspberry Pi OSがそれぞれのUSBフラッシュドライブを次のように認識している場合である。

バイスファイル USBフラッシュドライブ 書き込み帯域幅(暫定) 読み込み帯域幅(暫定)
/dev/sda USB3.0(1) 11.8MB/s 109MB/s
/dev/sdb USB3.0(2) 15.4MB/s 110MB/s
/dev/sdc USB2.0(1) 4.1MB/s 26.6MB/s

ストライピング

bcachefsでは、複数のドライブを指定したときはストライピング(RAID0)として扱う。

pi@raspberrypi:~$ sudo bcachefs format /dev/sda /dev/sdb
External UUID:                              3e548aa6-9b4e-4465-988c-86f6c40c6348
Internal UUID:                              b95602d7-ce7c-47e0-b7bf-26b1300a9b5e
Device index:                               1
Label:
Version:                                    unwritten_extents
Oldest version on disk:                     unwritten_extents
Created:                                    Thu Apr 18 20:25:12 2024
Sequence number:                            0
Superblock size:                            872
Clean:                                      0
Devices:                                    2
Sections:                                   members
Features:                                   new_siphash,new_extent_overwrite,btree_ptr_v2,extents_above_btree_updates,btree    _updates_journalled,new_varint,journal_no_flush,alloc_v2,extents_across_btree_nodes
Compat features:

Options:
  block_size:                               512 B
  btree_node_size:                          256 KiB
  errors:                                   continue [ro] panic
  metadata_replicas:                        1
  data_replicas:                            1
  metadata_replicas_required:               1
  data_replicas_required:                   1
  encoded_extent_max:                       64.0 KiB
  metadata_checksum:                        none [crc32c] crc64 xxhash
  data_checksum:                            none [crc32c] crc64 xxhash
  compression:                              [none] lz4 gzip zstd
  background_compression:                   [none] lz4 gzip zstd
  str_hash:                                 crc32c crc64 [siphash]
  metadata_target:                          none
  foreground_target:                        none
  background_target:                        none
  promote_target:                           none
  erasure_code:                             0
  inodes_32bit:                             1
  shard_inode_numbers:                      1
  inodes_use_key_cache:                     1
  gc_reserve_percent:                       8
  gc_reserve_bytes:                         0 B
  root_reserve_percent:                     0
  wide_macs:                                0
  acl:                                      1
  usrquota:                                 0
  grpquota:                                 0
  prjquota:                                 0
  journal_flush_delay:                      1000
  journal_flush_disabled:                   0
  journal_reclaim_delay:                    100
  nocow:                                    0

members (size 120):
  Device:                                   0
    UUID:                                   16aa5679-4832-4197-bb0c-ea8004dac946
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118272
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0
  Device:                                   1
    UUID:                                   175843da-3652-40a1-ab42-13aed32fdc7f
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118296
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0
initializing new filesystem
going read-write
initializing freespace

マルチドライブによる bcachefs のマウントには、 :によってブロックデバイス名を指定する必要がある。

pi@raspberrypi:~$ sudo mount -t bcachefs /dev/sda:/dev/sdb /mnt/

/mnt/dev/sda/dev/sdbの2つのブロックデバイスから構成されているので、合計領域もそれらの総和となっている。

pi@raspberrypi:~$ df -h /mnt/
Filesystem         Size  Used Avail Use% Mounted on
/dev/sda:/dev/sdb   53G  1.5M   52G   1% /mnt

ここで、FIO による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

ストライピング機能を有効にした時の読み込み書き込み帯域幅

レプリケーション

2台のドライブによるレプリケーション(RAID1)するには、bcachefs formatコマンドに --replicas オプションを追加する。

pi@raspberrypi:~$ sudo bcachefs format /dev/sda /dev/sdb  --replicas=2
External UUID:                              de438111-fb04-402f-a8c9-a2a2033df85d
Internal UUID:                              07de9a56-0613-40a9-a6e3-5361ea4165f7
Device index:                               1
Label:
Version:                                    unwritten_extents
Oldest version on disk:                     unwritten_extents
Created:                                    Thu Apr 18 07:10:24 2024
Sequence number:                            0
Superblock size:                            872
Clean:                                      0
Devices:                                    2
Sections:                                   members
Features:                                   new_siphash,new_extent_overwrite,btree_ptr_v2,extents_above_btree_updates,btree    _updates_journalled,new_varint,journal_no_flush,alloc_v2,extentss
Compat features:

Options:
  block_size:                               512 B
  btree_node_size:                          256 KiB
  errors:                                   continue [ro] panic
  metadata_replicas:                        2
  data_replicas:                            2
  metadata_replicas_required:               1
  data_replicas_required:                   1
  encoded_extent_max:                       64.0 KiB
  metadata_checksum:                        none [crc32c] crc64 xxhash
  data_checksum:                            none [crc32c] crc64 xxhash
  compression:                              [none] lz4 gzip zstd
  background_compression:                   [none] lz4 gzip zstd
  str_hash:                                 crc32c crc64 [siphash]
  metadata_target:                          none
  foreground_target:                        none
  background_target:                        none
  promote_target:                           none
  erasure_code:                             0
  inodes_32bit:                             1
  shard_inode_numbers:                      1
  inodes_use_key_cache:                     1
  gc_reserve_percent:                       8
  gc_reserve_bytes:                         0 B
  root_reserve_percent:                     0
  wide_macs:                                0
  acl:                                      1
  usrquota:                                 0
  grpquota:                                 0
  prjquota:                                 0
  journal_flush_delay:                      1000
  journal_flush_disabled:                   0
  journal_reclaim_delay:                    100
  nocow:                                    0

members (size 120):
  Device:                                   0
    UUID:                                   28f83018-915d-4423-986c-32ad8f361fde
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118272
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0
  Device:                                   1
    UUID:                                   3b4b848a-89f2-48d2-96de-556b69f15cb4
    Size:                                   28.9 GiB
    Bucket size:                            256 KiB
    First bucket:                           0
    Buckets:                                118296
    Last mount:                             (never)
    State:                                  rw
    Label:                                  (none)
    Data allowed:                           journal,btree,user
    Has data:                               (none)
    Discard:                                0
    Freespace initialized:                  0
initializing new filesystem
going read-write
initializing freespace
mounted version=unwritten_extents opts=metadata_replicas=2,data_replicas=2,noinodes_use_key_cache

Bcachefs を /mnt以下にマウントする場合には複数のブロックデバイスを指定する。

pi@raspberrypi:~$ sudo mount /dev/sda:/dev/sdb /mnt/

ここで、FIO による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

レプリケーション機能を有効にした時の読み込み書き込み帯域幅

キャッシュ

bcachefs ではデバイスにラベルを付与することができる。 このラベルによってグループ化された特定のデバイスに特定にアクションを優先させたりすることで、ストレージデバイスの特性を活かすことができる。

bcachefs では、読み書きのターゲットとして、フォアグラウンドでの書き込み先 --forground_target、バックグラウンドで書き戻す先 --background_target、読み込み時にキャッシュとして使う --promote_target と設定することができる。

例えば、アクセス速度が速いUSB3.0(1) /dev/sdaUSB3.0(2) /dev/sdbssdラベル、遅いUSB2.0(/dev/sdc)には hddラべルを付与することで、上記のターゲットのルールは次のように設定することができる。[^3]

pi@raspberrypi:~$ sudo bcachefs format \
                      --label=ssd.ssd1 /dev/sda \
                      --label=hdd.hdd1 /dev/sdb \
                      --label=hdd.hdd2 /dev/sdc \
                      --foreground_target=ssd \
                      --promote_target=ssd \
                      --background_target=hdd

Bcachefs を /mnt以下にマウントする場合には複数のブロックデバイスを指定する。

pi@raspberrypi:~$ sudo mount /dev/sda:/dev/sdb /mnt/

そこで、いくつかのパターンで FIO による簡易な読み書きパフォーマンスのベンチマークを取ってみる。 I/O サイズを 1 MB で順次書き込みをするようなジョブファイル write1.fioを使って書き込み帯域幅を確認する。

パターン foreground_target promote_target background_target
2 SSDs USB3.0(1) + USB3.0(2) USB3.0(1) + USB3.0(2) USB2.0(1)
1 SSD USB3.0(1) USB3.0(1) USB3.0(1) + USB2.0(1)
Slow 1 SSD USB2.0(1) USB2.0(1) USB3.0(1) + USB3.0(2)

キャッシュ機能を有効にした時の読み込み書き込み帯域幅

この測定でも、フォアグラウンドでの読み書きを高速なデバイスに割り当て、そうでないデバイスをバックグラウンドに割り当てたほうがパフォーマンスが良い傾向が見られた。

変更履歴

  • 2024/04/23: 記事公開

参考文献


  1. 2024年4月17日現在の Raspberry Pi OS Lite のカーネルv6.6では、bcachefs がサポートされていないため、カーネルは手元でビルドしたものに更新している。
  2. バイスは同じサイズである必要はない。