MongoDB 2.4 Analyzerをアナライズした!
前回のエントリーで釈然としなかったAnalyzerの戻す値が信用出来ない件。
このままじゃ、安心して使えないので解析した。
MongoDB analyzer 問題
- db.serverStatus().mem.resident
- mongodプロセスが利用している物理メモリサイズ
- db.serverStatus({workingSet
- 1}).workingSet.pageInMemory:mongodがmmapしたデータ(&Journal)ファイルの内、物理メモリに乗ってるページ数(1ページ=4kb)
これらの値はほぼ等しい(かresidentの方が若干大きい)ハズだ。
しかし前回、この値が大きく逆転する現象を見て混乱というか
『こりゃ信用出来ない』
と思った訳だ。
- resident
- 127MB
- pageInMemory
- 448MB
なんで仮想メモリー空間は全部で127MBしか物理メモリーに乗ってないのに
仮想メモリー空間内の領域の一部であるmapファイルはその3倍も物理メモリーに乗ってるのか?(意味不明)
ソースコードを読む
src/mongo/util/processinfo_linux2.cpp に全て書かれていた。
- #FF0000;font-size:x-large;">resident: ProcessInfo::getResidentSize()
結局/proc//stat からresidentを得ている事が解った。
- #FF0000;font-size:x-large;">pageInMemory: ProcessInfo::pagesInMemory()
mincore()システムコールが出所だった。
しかしちゃんと読んでもバグらしきモノは見つからない・・・
システムコールの先
『昔取ったなんちゃら』でカーネルに潜ってくと大体理由が解った。
- 解説
- ツールカウントしているのは破線の数(=ページ数)だ。
mincore()とstatは違う線を数えているので値が合わない事がある。
mincore()はmapfileが物理メモリーに乗ってるか否か?を知りたいのでこの実装で正解だろう。
実際ページにアクセスする際も(割り込みは入るが)PTE上の処理で終わるので高速(minor page fault)
カーネルコード(知りたい人だけ)
/proc//statm
residentデータ取得
- task_statm()
- (get_mm_counterはmm_structのメンバーを取るだけのマクロ)
- なのでresidentはanonymousを取って返すだけ。
データを作ってる部分も近くにあった
- mm_structはshow_smap()で作る。
- 関数ポインタが絡んで面倒だが結局smaps_pte_range()を呼んでいる。
- smaps_pte_entry()が実際のresidentカウントをやってる所。
- 普通はpte_present(ptent)がtrueでpageを見つけてanonymousがカウントアップされる。
- しかしmmap直後で触ってないページはpte_file(ptent)に
: if (pte_present(ptent)) { page = vm_normal_page(vma, addr, ptent); } else if (is_swap_pte(ptent)) { swp_entry_t swpent = pte_to_swp_entry(ptent); if (!non_swap_entry(swpent)) mss->swap += ptent_size; else if (is_migration_entry(swpent)) page = migration_entry_to_page(swpent); } else if (pte_file(ptent)) { // ※ ここに流れてくるとpage = NULL のままなのでanonymous のカウントアップは行わない if (pte_to_pgoff(ptent) != pgoff) mss->nonlinear += ptent_size; } if (!page) return; if (PageAnon(page)) mss->anonymous += ptent_size; :
mincore()
かたやmincore()ではmincore_pte_range()で同様の処理が走るがpte_present(pte)の場合は勿論、pte_file(pte)の場合もカウントアップしている。
これが差を生んでいる様だ。
: if (pte_none(pte)) mincore_unmapped_range(vma, addr, next, vec); else if (pte_present(pte)) *vec = 1; else if (pte_file(pte)) { // ※ vma->vm_file->f_mappingまで見てページカウント(*vec)している。 pgoff = pte_to_pgoff(pte); *vec = mincore_page(vma->vm_file->f_mapping, pgoff); } else { /* pte is a swap entry */ swp_entry_t entry = pte_to_swp_entry(pte); :
まとめ
MongoDBでminor page faultが起こるケースはmongod再起動位しか思いつかない。
なので、mongodが暫く仕事をすれば両者は合って来るだろう。
workingsetのpageInMemoryを信じて運用して構わない。
4/17追記
と思ったのだが、やっぱりダメだ!!
信じちゃいかん!!!
実メモリサイズを数倍、数十倍と大きく超えて報告する現象が多発。。。
また後で調べる。。。