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 可変 データブロックを格納する領域。実際にファイルデータを保存する領域。


Group Descriptors と Reserved GDT Blocks のサイズ

Group Descriptors と Reserved GDT Blocks のサイズ関係を以下に示す。

i-node Tables のサイズ

mkfs.ext4 のオプション -i bytes-per-inode と i-node Tables のサイズ関係を以下に示す。

ext4 ファイルシステムのオーバーヘッド

オーバーヘッド要因

ext4 ファイルシステムのオーバーヘッド要因を以下に示す。これまでのブロックグループ内のオーバーヘッドに加えて、ジャーナル領域(J)と予約領域(M)を加えている。

ジャーナル領域

ext4ジャーナルファイルシステムであるため、ジャーナルを書き込む領域が必要である。このジャーナル領域はファイルシステムのサイズ(ボリュームのサイズ)により以下の通りに変化する。

ボリュームサイズ ジャーナル領域のサイズ
1GB未満 16MB
2GB未満 32MB
4GB未満 64MB
4GB以上 128MB


予約領域

ext 2/3/4 ファイルシステムでは root ユーザのみが書き込める予約領域をデフォルトで 5% 確保する。この予約領域は df -k の Available には含まれず、使用可能とは見なされない。厳密にはオーバーヘッドではないが、使用可能な領域としては見なされないため、オーバーヘッドとして考慮する。

mkfs.ext4 コマンドの -m オプションにて予約する割合(%)を指定できる。ファイルシステム作成後でも tune2fs コマンドにて変更できる。

16GB以上のサイズの ext4 のオーバーヘッド

16GB未満の場合、Group Descriptors, Reserved GDT Blocks, ジャーナル領域のサイズが変動する。16GB以上ではそれらが固定値となる。


固定値になったことで以下の単純な式でオーバーヘッドが計算できる。

変数
V ファイルシステムを作成するボリュームのサイズ(MB単位)
N ブロックグループの数
S プライマリ Super block およびバックアップ Super block を持つブロックグループの数
m 予約領域の割合(デフォルトは5%)
O オーバーヘッド


その他

検証した環境の /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
オーバーヘッド要因の漏れが無いことの確認

mkfs.ext4 -b 4096 -m 5

mkfs.ext4 -b 4096 -m 0

どこに収束するのか

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