ext4 ファイルシステムのオーバーヘッドを厳密に理解する
ファイルシステムを作成すると、ファイルシステム自体の管理領域などのため、ファイルシステムを作成するデバイス・ボリュームの容量を100%使えるようにはならない。
では何パーセントが減ってしまうのか。10%あれば大丈夫なのか、3%程度でもよいのか、厳密には決まらないのか、そんな疑問・不安を取り除くために検証および論理的な裏取りを行った。
検証環境は CentOS 6.4 (x86_64) で、ファイルシステムは ext4 である。なお、ブロックサイズは 4KB を前提にする。CentOS 7 (RHEL 7) でも考え方は同じだが、計算の元になる基礎値に差があるため注意が必要(「その他」にて触れる)。
検証結果
128M, 256M, 512M, 1024M, 1.5G, 2G, ・・・ と20GまでのLVを作成し、実際にファイルシステムを作成。マウントした際の df -k の Available 列の値について、LVサイズとの比をグラフ化した結果が以下である(95%の場合は、LVサイズの95%がdf -k の Available の容量だったことになる)。
- LVのサイズが小さい(ファイルシステムのサイズが小さい)場合には、オーバーヘッドが目立ちやすく、単純にLVのサイズに比例して使用可能(Available)な容量の比率が増えるわけではない。
- LVのサイズが4GB以上になれば使用可能な容量の比率は単調に増加する。
ext4 ファイルシステムのレイアウト
ブロックグループによる分割
ext4 ファイルシステムは、128MB 単位のブロックグループの集合として構成されている。1GBのボリュームであれば8個のブロックグループに分割される。ファイルシステム自体の情報(Super Block)、ブロックグループの情報(Group Descriptor)はブロックグループ0にマスタ(プライマリ)が格納され、バックアップ(ミラー)が 3, 5, 7 のそれぞれの冪乗の番号のブロックグループに格納される。
ブロックグループ内のレイアウト
各ブロックグループ内のレイアウトを以下に示す。ここで Data Blocks 以外は全て使用可能な容量としてはカウントできないものであり、オーバーヘッドである。Super block, Data Block Bitmap, i-node Bitmap は固定長であるため、ブロックグループ数に単純に比例する。そして Data Blocks のサイズは A, B, C が決まることで自動的に決まってくる。
領域 | サイズ | 用途 |
---|---|---|
Super block | 1ブロック | ファイルシステム自体の情報(メタデータ)を格納する領域。 |
Group Descriptors | 可変 | ブロックグループの情報を格納する構造(ブロックグループ記述子)の集合。1ブロックグループあたりに1記述子が対応し、1記述子のサイズは32バイトになる(64bitオプションがつく場合は64バイトだが、64bitのCentOSでもこのオプションはつかない)。ファイルシステムのサイズ16GB(4KB÷32byte×128MB(1ブロックグループのサイズ))ごとに1ブロックが必要。 |
Reserved GDT Blocks | 可変 | ファイルシステムの拡張により、ブロックグループ数が増加した際に、Group Descriptorsを拡張するための予約領域。ファイルシステム作成時の初期サイズの1024倍まで拡張できるようにこの領域は確保される(8GBのファイルシステムを作成した場合は、8TBまで拡張できるように、Reserved GDT Blocks が確保される)。ただし、CentOS 6 (RHEL 6) における、ext4 ファイルシステムのサイズ上限(16TB)を超えることはないため、Group Descriptors + Reserved GDT Blocks のブロック数は1024(16TB÷16GB)が上限となる。 |
Data Block Bitmap | 1ブロック | Data Blocks のブロックの使用状況をビットマップで管理する。1ブロック=4KB=32Kビットのため、ブロックグループ内のブロック数は32Kに限定される。 |
i-node Bitmap | 1ブロック | i-node テーブルの使用状況をビットマップで管理する。 |
i-node Table | 可変(実質512ブロック固定) | i-node を格納する領域。1 i-node のサイズは256バイトになる。データブロックに対するi-nodeの比率の指定により領域のサイズは変化する。デフォルトでは16KB(4ブロック)ごとに1 i-node であるため、2MB(32K÷4×256byte)=512ブロックになる。 |
Data Blocks | 可変 | データブロックを格納する領域。実際にファイルデータを保存する領域。 |
ext4 ファイルシステムのオーバーヘッド
ジャーナル領域
ext4はジャーナルファイルシステムであるため、ジャーナルを書き込む領域が必要である。このジャーナル領域はファイルシステムのサイズ(ボリュームのサイズ)により以下の通りに変化する。
ボリュームサイズ | ジャーナル領域のサイズ |
---|---|
1GB未満 | 16MB |
2GB未満 | 32MB |
4GB未満 | 64MB |
4GB以上 | 128MB |
その他
検証した環境の /etc/mke2fs.conf
# cat /etc/mke2fs.conf [defaults] base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr blocksize = 4096 inode_size = 256 inode_ratio = 16384 [fs_types] ext3 = { features = has_journal } ext4 = { features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize inode_size = 256 } ext4dev = { features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize inode_size = 256 options = test_fs=1 } small = { blocksize = 1024 inode_size = 128 inode_ratio = 4096 } floppy = { blocksize = 1024 inode_size = 128 inode_ratio = 8192 } news = { inode_ratio = 4096 } largefile = { inode_ratio = 1048576 blocksize = -1 } largefile4 = { inode_ratio = 4194304 blocksize = -1 } hurd = { blocksize = 4096 inode_size = 128 }
16GBのファイルシステムを作成した際の tune2fs -l 結果
ファイルシステムのオプションなどの確認用に。
# cat /tmp/overheadtest/tune2fs.txt tune2fs 1.41.12 (17-May-2010) Filesystem volume name: <none> Last mounted on: <not available> Filesystem UUID: cfcb2085-2fd2-49d5-be17-349db7559e2c Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash Default mount options: (none) Filesystem state: clean Errors behavior: Continue Filesystem OS type: Linux Inode count: 1048576 Block count: 4194304 Reserved block count: 209715 Free blocks: 4084463 Free inodes: 1048565 First block: 0 Block size: 4096 Fragment size: 4096 Reserved GDT blocks: 1023 Blocks per group: 32768 Fragments per group: 32768 Inodes per group: 8192 Inode blocks per group: 512 Flex block group size: 16 Filesystem created: Sun Mar 1 09:25:30 2015 Last mount time: Sun Mar 1 09:25:40 2015 Last write time: Sun Mar 1 09:25:40 2015 Mount count: 1 Maximum mount count: 30 Last checked: Sun Mar 1 09:25:30 2015 Check interval: 15552000 (6 months) Next check after: Sun Aug 30 09:25:30 2015 Lifetime writes: 388 MB Reserved blocks uid: 0 (user root) Reserved blocks gid: 0 (group root) First inode: 11 Inode size: 256 Required extra isize: 28 Desired extra isize: 28 Journal inode: 8 Default directory hash: half_md4 Directory Hash Seed: 9011fc60-1cb7-4fcf-b6d1-0ef3f4d72ee0 Journal backup: inode blocks
どこに収束するのか
CentOS 6(RHEL 6)の ext4 の最大サイズである16TBまでのオーバーヘッドを上記式に基づき計算してグラフ化したものを示す。
結局のところ log を含む S はV が 16TB となっても 25 にしかならない。そのため、S をファクターに持つ項は次第に無視可能になる。ジャーナル領域も固定値であり、無視できる。予約領域を除くと、2つのBitmapとi-node の領域のみが目立つオーバーヘッドである。128MB のブロックグループにおいて 514×4KB がオーバーヘッドであるとすれば、オーバーヘッドは約 1.569% となる。つまり、使用可能な容量は 98.431% に近付く。
検証用のスクリプト
- /tmp/overheadtest を作成し、/tmp/overheadtest/overheadtest.sh を作成する。
- 検証用のLVを作成するためのVGとして overheadtestvg を作成しておく。
- /mnt をマウントポイントとして使用する。
overheadtest.sh
#!/bin/bash OHTEST_LVSIZE_MB="$1" OHTEST_MKFS_OPTS="$2" OHTEST_VG=overheadtestvg OHTEST_LV=overheadtestlv OHTEST_MNTPNT=/mnt OHTEST_TMP=/tmp/overheadtest lvcreate -L $OHTEST_LVSIZE_MB -n $OHTEST_LV $OHTEST_VG &> /dev/null || exit 99 sleep 1 mkfs.ext4 -b 4096 $OHTEST_MKFS_OPTS /dev/$OHTEST_VG/$OHTEST_LV &> $OHTEST_TMP/mkfs.txt || exit 99 sleep 1 mount /dev/$OHTEST_VG/$OHTEST_LV $OHTEST_MNTPNT || exit 99 sleep 1 lvdisplay /dev/$OHTEST_VG/$OHTEST_LV > $OHTEST_TMP/lvdisplay.txt tune2fs -l /dev/$OHTEST_VG/$OHTEST_LV > $OHTEST_TMP/tune2fs.txt df -k > $OHTEST_TMP/df.txt umount $OHTEST_MNTPNT || exit 99 sleep 1 lvremove -f /dev/$OHTEST_VG/$OHTEST_LV &> /dev/null || exit 99 sleep 1 function grepcut { f="$1"; g="$2"; a="$3" grep "$g" $f | awk "{print \$$a}" } LVD=$OHTEST_TMP/lvdisplay.txt LVD_LV_SIZE=$(grepcut $LVD "LV Size" 3)$(grepcut $LVD "LV Size" 4) LVD_CURRENT_LE=$(grepcut $LVD "Current LE" 3) MFS=$OHTEST_TMP/mkfs.txt MFS_BLOCK_GROUPS=$(grepcut $MFS " block group" 1) MFS_JOURNAL_BLOCKS=$(grepcut $MFS "^Creating journal " 3 | tr -d '()') T2F=$OHTEST_TMP/tune2fs.txt T2F_INODE_COUNT=$(grepcut $T2F "^Inode count:" 3) T2F_BLOCK_COUNT=$(grepcut $T2F "^Block count:" 3) T2F_RESERVED_BLOCK_COUNT=$(grepcut $T2F "^Reserved block count:" 4) T2F_FREE_BLOCKS=$(grepcut $T2F "^Free blocks:" 3) T2F_FREE_INODES=$(grepcut $T2F "^Free inodes:" 3) T2F_BLOCK_SIZE=$(grepcut $T2F "^Block size:" 3) T2F_INODE_SIZE=$(grepcut $T2F "^Inode size:" 3) T2F_RESERVED_GDT_BLOCKS=$(grepcut $T2F "^Reserved GDT blocks:" 4) T2F_BLOCKS_PER_GROUP=$(grepcut $T2F "^Blocks per group:" 4) T2F_INODES_PER_GROUP=$(grepcut $T2F "^Inodes per group:" 4) T2F_INODE_BLOCKS_PER_GROUP=$(grepcut $T2F "^Inode blocks per group:" 5) DFK=$OHTEST_TMP/df.txt DFK_1KBLOCKS=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-4)") DFK_USED=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-3)") DFK_AVAILABLE=$(grepcut $DFK "$OHTEST_MNTPNT" "(NF-2)") HCSV="LVSIZE_MB,MKFS_OPTS" CSV="$OHTEST_LVSIZE_MB,\"$OHTEST_MKFS_OPTS\"" for v in $(set | egrep "LVD_|MFS_|T2F_|DFK_") do HCSV="$HCSV,${v%%=*}" CSV="$CSV,${v#*=}" done echo $HCSV echo $CSV exit 0
実行例・・・
# /tmp/overheadtest/overheadtest.sh 1024 "-m 5" LVSIZE_MB,MKFS_OPTS,DFK_1KBLOCKS,DFK_AVAILABLE,DFK_USED,LVD_CURRENT_LE,LVD_LV_SIZE,MFS_BLOCK_GROUPS, MFS_JOURNAL_BLOCKS,T2F_BLOCKS_PER_GROUP,T2F_BLOCK_COUNT,T2F_BLOCK_SIZE,T2F_FREE_BLOCKS, T2F_FREE_INODES,T2F_INODES_PER_GROUP,T2F_INODE_BLOCKS_PER_GROUP,T2F_INODE_COUNT,T2F_INODE_SIZE, T2F_RESERVED_BLOCK_COUNT,T2F_RESERVED_GDT_BLOCKS 1024,"-m 5",1032088,945608,34052,256,1.00GiB,8,8192,32768,262144,4096,249509,65525,8192,512,65536,256,13107,63
ブロックグループ0の内部を覗く方法
dumpe2fs コマンドを使えば良い。ブロックグループが多くなると出力が増えるので適当に head で切り捨てる。
# dumpe2fs /dev/testoverheadvg/testoverheadlv | head -n 100 ・・・ (中略) ・・・ Group 0: (Blocks 0-32767) [ITABLE_ZEROED] Checksum 0xb51a, unused inodes 8181 Primary superblock at 0, Group descriptors at 1-1 Reserved GDT blocks at 2-256 Block bitmap at 257 (+257), Inode bitmap at 273 (+273) Inode table at 289-800 (+289) 24281 free blocks, 8181 free inodes, 2 directories, 8181 unused inodes Free blocks: 8487-32767 Free inodes: 12-8192 ・・・ (中略) ・・・
CentOS 7 (RHEL 7) における差
CentOS 6.4 と CentOS 7.0 では以下の差が確認できた。基本的な計算の考え方は変わらないが、以下の差を反映すること。特に Group Descriptors + Reserved GDT Blocks の部分は元々のルール(1024倍の拡張余地を確保するルール)を守らない拡張になっている。
比較観点 | CentOS 6.4 | CentOS 7.0 | 備考 |
---|---|---|---|
Group Descriptorのサイズ | 32バイト | 64バイト | CentOS 7.0 では Filesystem features: 64bit が指定されており、ブロックグループ記述子は64バイトに拡張されている。 |
bytes-per-inode(inode_ratio) | 16KB | 32KBや64KBもある(規模が大きい場合) | mke2fs.conf 内に huge や big が追加されている。 |
ext4 ファイルシステムの最大サイズ | 16TB | 50TB | RHEL社が公式にサポートするサイズが増加。Group Descriptors + Reserved GDT Blocks が 50TB では 6400 ブロックまで伸長する。ファイルシステムのサイズごとの Group Descriptors と Reserved GDT Blocks のブロック数を別表に示す。 |
CentOS 7.0 での検証結果。Reserved GDT Blocks は 1024 を超えないように制限されているように見える。
ファイルシステムのサイズ | Group Descriptors ブロック数 | Reserved GDT Blocks ブロック数 |
---|---|---|
4GB | 1 | 511 |
16GB | 2 | 1024 |
50GB | 7 | 1024 |
100G | 13 | 1024 |
4TB | 512 | 1024 |
12TB | 1536 | 512 |
16TB | 2048 | 0 |
50TB | 6400 | 0 |