ファイルキャッシュクリアの謎
技術Bar#9
2012/12/4 光成滋生
cybozu.comで障害発生
/ 20 2
ストレージ容量拡張操作の結果、Officeが動いているLinuxマシンのページキャッシュが約15分おきに破棄されるという未知の現象が発生したためです
現象の詳細
mdadmでLVMのパーティションを拡大すると15分毎にpage cacheがクリアされる
犯人はだれか?
/ 20 3
hard disk
kernel/device driver
LVM
mdadm
ext3
MySQLなどのアプリ
パーティション管理
ソフトウェアRAID
ファイルシステム
メモリにキャッシュ
これがクリアされて激遅に
調査のやりかた
現象の再現方法の構築
頻度が低かったり環境によって起こらないと面倒
原因を推測して絞り込み
トライアンドエラー
さまざまなツールを使う
特定したら修正して確認
はずれていたらまた別の原因を推測
/ 20 4
現象の確認
hazamaでの容量の変更は500GB→1TB
バックグラウンドリサイズが長時間
数十MB程度では17分以内に終わってしまう
手元の環境でディスクを用意して…
1時間待ったが発現しなかった
私の勘違い
キャッシュがクリアされるのはそのディスクに構築したファイルシステム上のもののみ
再度テスト
クリアされることを確認
約17分=>概ね1000秒であることも判明
/ 20 5
犯人は誰か
全然見当がつかない
普通ユーザラウンドで明示的に破棄なんてしない
page cacheを破棄しそうなkernelの関数を列挙
flush_disk, invalidate_disk, invalidate_parition, ...
これらの関数を呼び出すやつを探そう
ftraceを使って探す
ubuntuでは標準搭載
呼ばれた関数,stacktrace,eventなどがわかる
/sys/kernel/debug/tracingに様々な値を書き込むことでパラメータを設定する
結構複雑で慣れるまで難しかった
/ 20 6
ftraceの例
ext4系関数が呼ばれたときのcall treeを表示
ext4のファイルシステムをmoutすると関連関数のlogが表示される
/ 20 7
// debugfsをmount
mount -t debugfs debugfs /sys/kernel/debug
// stack tracerを有効
echo 1 > /proc/sys/kernel/stack_tracer_enabled
cd /sys/kernel/debug/tracing
// ext4で始まる関数名をターゲットに
echo "ext4*" > set_ftrace_filter
// 関数トレーサーをセット
echo function > current_tracer
echo 0 > trace cat trace_pipe
あたりをつける
ディスクを交換したり,resize開始直後に呼ばれる関数をセットして17分待つ
呼ばれない…
別の関数を試すことの繰り返し
このあたりが結構辛い
/ 20 8
漸くヒット
ここには重要な情報が… / 20 9
__invalidate_deviceが網にかかった!
mdadm-900 [001] .... 75491.384221: __invalidate_device <-invalidate_partition
mdadm-900 [001] .... 75491.384235: <stack trace>
=> ftrace_call
=> invalidate_partition
=> rescan_partitions
=> __blkdev_get
=> blkdev_get
=> blkdev_open
=> do_dentry_open
=> nameidata_to_filp
=> do_last
=> path_openat
=> do_filp_open
=> do_sys_open
=> sys_open => system_call_fastpath
下の関数が上の関数を呼び出している
ここから下はユーザ空間
犯人はmdadmのdaemon?
ユーザ空間側なのでgdbでひっかけられる
sudo gdb –p <processId>でatach
backtraceしてみる
時間の間隔も正確に1000秒なのがわかる / 20 10
__invalidate_deviceが網にかかった!
mdadm-900 [001] .... 75491.384221: __invalidate_device <-invalidate_partition
mdadm-900 [001] .... 75491.384235: <stack trace>
=> ftrace_call ...
gdb) bt
#0 0x00007f85b1ebf103 in __select_nocancel ()
at ../sysdeps/unix/syscall-template.S:82
#1 0x000000000040de66 in mdstat_wait (seconds=1000) at mdstat.c:317 #2 0x000000000042dea1 in Monitor (devlist=0x0, mailaddr=0x1a7b020 "root",
位置関係
/ 20 11
hard disk
md driver
monitor daemon
invalidate_partition disk driver
md daemon
clear page cache
use space
kernel ?
mdadmのソースをで追いかける
まずこれで1000秒を30秒に短縮できる
調査効率が大幅に改善される
しかしやや戸惑いが
monitorコマンドがクリアするとも思えない
ファイルキャッシュがクリアされる関数が何か確認しながらgdbでstep実行
daemon内部で/dev/mdをopenするだけでクリアされることが判明
mdのdevice driverの問題?
/ 20 12
fd = open("dev/md/test..", O_RDONLY);
位置関係
/ 20 13
hard disk
md driver
monitor daemon
invalidate_partition disk driver
md daemon
use space
kernel
md_open
clear page cache
?
md_openを追いかける
check_disk_changeが呼ばれている
これが怪しい?
しかしこいつは犯人ではなかった
もう一度call graphをみる
blkdev_getのあたりから分岐してそう / 20 14
__invalidate_deviceが網にかかった!
mdadm-900 [001] .... 75491.384221: __invalidate_device <-invalidate_partition
mdadm-900 [001] .... 75491.384235: <stack trace>
=> ftrace_call
=> invalidate_partition
=> rescan_partitions
=> __blkdev_get
=> blkdev_get
=> blkdev_open
blk_devの中身
このあたり
bd_invalidatedフラグが怪しい
/ 20 15
// こいつでmd_openが呼ばれる
ret = disk->fops->open(bdev, mode);
..
if (bdev->bd_invalidated) {
if (!ret)
rescan_partitions(disk, bdev);
else if (ret == -ENOMEDIUM)
// この中で__invalidate_paritionが呼ばれる
invalidate_partitions(disk, bdev);
位置関係
/ 20 16
hard disk
md driver
monitor daemon
invalidate_partition disk driver
md daemon
use space
kernel blk_dev
clear page cache
md_open bd_invalidated
bd_invalidatedフラグを探す
クリアしているのは初期化と二カ所
どちらもcheck_disk_size_change()を呼んで からbd_invalidatedを0クリア
/ 20 17
rescan_partitions() {
....
disk->fops->revalidate_disk(disk);
check_disk_size_change(disk, bdev);
bdev->bd_invalidated = 0;
invalidate_partitions() {
...
set_capacity(disk, 0);
check_disk_size_change(disk, bdev);
bdev->bd_invalidated = 0;
クリアし忘れ?
しかし,revalidate_disk()の中ではcheck_disk_size_change()を呼んだあとクリアしていない
このせいでopenするたびに毎回キャッシュクリア
結局LVMでもmdadmでもなくkernel/fsが原因
/ 20 18
revalidate_disk() {
...
mutex_lock(&bdev->bd_mutex);
check_disk_size_change(disk, bdev);
// bdev->bd_invalidated = 0; がない
mutex_unlock(&bdev->bd_mutex);
動作確認
クリアするようにしてkernelをビルド
キャッシュクリア問題は起こらない
てか,そもそもcheck_disk_size_change()の中でクリアすればええんとちゃうか?
/ 20 19
その後
LinuxのMLに投げる
作法がいろいろある
patchの作り方
scripts/checkpatch.plでパッチフォーマットの検証
patchのMLへの投げかた
git send-emailを使う
題名のつけかたと投げる場所が不適切だったかも
MLに投げたが黙殺
再度別のMLに挑戦
山本さんによる英語メールの推敲(という名の書き直し)
結局パッチを手動で張り付けたものがaccept...
http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=commit;h=9b4b0d9f9f9ad2
/ 20 20