ハードウェア中級 #3 メモリ深掘り — available・ダーティページ・コンテナ上限

読了 6分

第2回 の CPU に続いてメモリです。基礎 #3 で、メモリ階層、スワップ、OOM Killer、そして「余ったメモリはページキャッシュとして使われる」という概念を押さえました。今回はその概念が運用の場面で投げかける問いに答えます。メモリが本当に足りないのかをどう判定するか、急にディスクへ殺到する書き込みはどこから来るのか、なぜコンテナはサーバーのメモリが余っていても死ぬのか、です。

free で見るべき列は 1 つ #

基礎 #3 の結論を運用の動作に置き換えるとこうなります。free の出力で判断基準にするのは free 列ではなく available です。

free -h
$ free -h
       total   used   free   shared  buff/cache  available
Mem:    62Gi   18Gi  1.2Gi    0.5Gi        43Gi       42Gi

free の 1.2GiB だけを見ると危険に見えますが、buff/cache の 43GiB の大部分はページキャッシュなので、アプリケーションが要求すれば即座に空けてくれます。それを反映した推定値が available の 42GiB で、「いま新しいプロセスがスワップなしで使える量」に対するカーネルの答えです。監視のアラートも free ではなく available 基準で掛けてこそ、ページキャッシュが働いている正常な状態を障害と誤認せずに済みます。

では本当の不足はどう現れるのでしょうか。第1回の枠組みで言えば、使用率 (available の減少) より飽和が決定的です。スワップ in/out が継続的に発生 し (vmstat の si/so)、available が減り続ける傾向にあるなら不足です。スワップが多少埋まっていること自体は問題ではありません。使っていないページを下ろしておいた痕跡である場合があるので、埋まっている量ではなく 出入りする流れ を見ます。

ダーティページ — 書き込みはまずメモリに積まれる #

アプリケーションがファイルに書き込むと、その内容はすぐにディスクへは行かず、ページキャッシュにダーティページ(dirty page、メモリには反映されたもののまだディスクに書かれていないページ)として積まれます。カーネルがバックグラウンドでゆっくりディスクへ下ろす構造なので、書き込みが速く感じられる代わりに、2 つの運用上の現象が生まれます。

  • 書き込みバースト (burst flush) — ダーティページが上限に近づくと、カーネルが一気にディスクへ吐き出します。普段は暇なディスクが周期的に 100% に達し、その瞬間に他の I/O の遅延が跳ねます。「数分おきにサービスが一瞬もたつく」という報告の定番の原因です。
  • 消失の可能性 — ディスクへ下りる前に電源が落ちると、そのダーティページは消えます。データベースが fsync にこだわる理由で、第6回でバッテリーバックアップ付きキャッシュと一緒に再び登場します。

バーストが問題なら、カーネルのダーティ比率の設定 (vm.dirty_ratiovm.dirty_background_ratio) で「少しずつ頻繁に」下ろすよう調整できます。ただし第9回で整理する原則のとおり、こうしたつまみは測定で原因を確定させてから触ります。

swappiness — スワップの性向を調整する #

vm.swappiness は「スワップを有効にするか無効にするか」ではなく、メモリが足りなくなったとき何を先に追い出すかの性向 です。カーネルの選択肢は 2 つです。ページキャッシュを縮めるか、使っていない匿名ページ (プロセスのメモリ) をスワップへ下ろすかです。値が高ければ (デフォルト 60) スワップも積極的に使い、低ければページキャッシュを先に縮めます。

データベースサーバーで swappiness を下げる (例: 10) 慣例はこの動作から来ています。DB プロセスのメモリがスワップへ下りるとクエリの遅延がディスクの速度まで落ちるため、むしろページキャッシュを譲る側を選ぶわけです。逆に一般のサーバーで 0 近くまで下げるのはおすすめしません。スワップという緩衝がなくなると、不足の状況がそのまま OOM へ直行します。

OOM Killer の対象選定にもつまみがあります。プロセスごとの oom_score_adj を下げれば (例: -500)、重要なプロセスが最後の候補になります。「メモリが破裂するとき DB だけは生かしたい」という要求への標準的な答えです。

コンテナのメモリ — 上限はサーバーではなく cgroup #

コンテナ時代にもっとも多いメモリの事件は、サーバーではなくコンテナ単位で起きます。コンテナのメモリ上限は cgroup(control group、プロセスのまとまりごとにリソース使用量を制限する Linux の機能)で掛けられ、上限を超えると、サーバー全体にメモリが残っていてもそのコンテナのプロセスが OOM で死にます。Kubernetes で出会う OOMKilled がこれです。

運用で混乱しやすい点を 2 つ押さえておきます。

  • コンテナの中の free はホストの値を表示します。コンテナの中で free を打つとホスト全体のメモリが見えるため、アプリケーションが自分の上限を誤認しやすいです。上限は cgroup のファイル (memory.max) かオーケストレーターの設定で確認する必要があります。
  • ページキャッシュもコンテナの使用量に計上されます。ファイル I/O の多いコンテナは、アプリケーションのメモリが小さくてもキャッシュのせいで上限に近づくことがあります。上限に達するとカーネルがそのコンテナのキャッシュを先に回収するため、たいていは死にませんが、使用量のグラフが上限に張り付いて見える理由にはなります。

処方の方向もサーバーとは違います。サーバーのメモリ不足は増設ですが、コンテナの OOMKilled は大半が、上限の設定とアプリケーションの実際の使用量 (ヒープ設定など) を揃える作業です。

よく出会う落とし穴 #

  • free 列を見て増設を決める — ページキャッシュを不足と誤認した過剰投資です。available とスワップの流れで判定します。
  • スワップに使用量があるだけで障害と見なす — 埋まっているスワップは痕跡、出入りするスワップが症状です。si/so が継続的に発生しているかを見ます。
  • コンテナの OOM をサーバーのメモリ問題と判断する — ホストにメモリが残っていても、cgroup の上限を超えれば死にます。死んだ単位がコンテナなら、まず上限と実使用量を比較します。

まとめ #

今回つかんだ絵です。

  • メモリの余裕の判断基準は free ではなく available で、不足の症状はスワップの継続的な in/out です。
  • 書き込みはダーティページとしてメモリに積まれ、まとめて下りる際に周期的なディスクのバーストを生むことがあります。
  • swappiness は不足時に何を先に追い出すかの性向で、oom_score_adj で OOM の優先順位を調整します。
  • コンテナのメモリの事件は cgroup の上限で起きます。サーバーではなくコンテナの上限と実使用量を見ます。

次回 — NUMA #

次回の「ハードウェア中級 #4 NUMA — メモリは均一ではない」では、メモリの話をもう 1 段下りていきます。CPU ソケットを 2 個以上積んだサーバーでは、メモリは均一ではありません。どのコアがどのメモリにアクセスするかで性能が分かれる構造と、それがデータベースと仮想化に与える影響を扱います。

X