ページリクレームとスワップ:メモリを空けながら必要なデータを失わない方法

このページの目次

サービスが繰り返し使用する 100 MiB のホットデータが、1回の 2 GiB ファイルスキャンによって追い出されました。リクレーム統計は良好ですが、ビジネスロジックは繰り返し読み込みを続けています。リクレームの目的はページを解放するだけでなく、⁠コンテンツがどのように復元されるか、誰が停止時間を負担するか、解放後にすぐに必要とされるかどうかも考慮する必要があります。

まずコンテンツバックアップに基づいて候補を説明し、ワークセット、ページアウト、並列ページインを導出した後、圧縮、整理、観測を1つのラインで結びつけます。

説明すべき現象読むべき箇所
どのページを破棄でき、どのコンテンツを保存すべきか?第1節:バックアップと参照
リクレームは成功しているのに、なぜビジネスがより遅くなるのか?第2~3節:refault と停止の負担者
ページアウト後、複数のスレッドはどのように同じコンテンツを見つけるのか?第4節:スワップのアイデンティティと並列復元
圧縮、整理、容量増強はそれぞれ何を解決するのか?第5~7節:リソースの計算と診断

1. コンテンツがどこから復元されるかをまず問い、次にそれがどれくらい「冷たい」かを問う

変更されていない通常のファイルページの場合、バックエンドがコンテンツを再提供できるのであれば、関連する参照を解除した後にメモリコピーを破棄できます。ページにまだ書き戻されていない変更が含まれている場合、直接破棄するとファイルセマンティクスが壊れるため、書き戻しプロトコルに従って処理を進める必要があります。匿名メモリには通常、再読み込みできる元のファイルがありません。そこに保持する必要があるコンテンツには、スワップなどのバックアップが必要か、またはまだ有効なバックアップコピーが存在する必要があります。

物理ページのリクレーム前:コンテンツを保持する必要があるか、どこから復元できるか? クリーンな通常ファイルページ バックエンドに再読み込み可能なコンテンツがある 参照解除後に破棄 将来のアクセスはバックエンドから再読み込み可能 ダーティな通常ファイルページ メモリには書き戻されていない変更が含まれる プロトコルに従って書き戻しを進める 書き戻し中およびダーティ状態の調整が必要 保持が必要な匿名 / shmem コンテンツを凭空から生成できない 復元可能なバックアップを構築 スワップ / 圧縮キャッシュなど、または既存のコピー マッピング、参照、ピン、ロックの条件も満たす必要がある;アプリケーションが明示的に破棄したり、使用されなくなったページは異なる経路を辿る可能性がある。

「匿名ページは毎回ディスクに書き込んで初めて解放される」というのも極端な一般化です。有効なスワップコピーが存在する場合、圧縮キャッシュが引き受ける場合、アプリケーションが明示的にコンテンツを放棄する場合、およびマッピングのライフサイクル全体が終了する場合は、処理経路が異なります。逆に、tmpfs はファイルインターフェースとして表示されますが、そのコンテンツを通常のディスクファイルのように自由に破棄できるわけではありません。その shmem バックアップ関係は、スワップ可能なメモリに近いものです。

ホット/コールド、コンテンツバックアップ、参照は3つの独立した次元です:

候補ページ有利に見える条件依然として解決すべき問題
非常にコールドだが、デバイスによって長期間ピン留めされている最近あまりアクセスされていない参照がリクレームを妨げる可能性がある
非常にホットなクリーンなファイルページコンテンツを再読み込み可能破棄後にすぐに再読み込みされる可能性があり、コストが高い可能性がある
唯一の有効なコンテンツを含む匿名ページすでにコールドである可能性がある復元可能なバックアップを保持する必要がある

したがって、候補の選択は常に「クリーンなファイル、ダーティなファイル、匿名」に固定されてはいけません。解放条件と将来の復元コストの両方を考慮する必要があります。

以下では、淘汰戦略については議論せず、「このコンテンツを RAM から安全に追い出せるか」のみをチェックします。保存、変更、リクレーム、再アクセスを試みて、新しい変更が元のバックアップコピーをどのように無効にするかを観察します。

図を準備しています
リクレーム前、このコンテンツはどこから取り戻せるか?

コンテンツの復元可能性と、リクレームを妨げる参照がないことが、保留ページを解放するための必要条件です。変更により古いバックアップは期限切れになります。リクレームに成功した直後に復元すると、重複作業のコストを払う可能性があります。

2. ワークセットの例を使ってホット/コールドの判断を理解する

サービスが各ラウンドで 100 MiB のホットデータを繰り返しアクセスし、別の1回のスキャンで 2 GiB のファイルを絶えず読み込むと仮定します。スキャンが直近で発生したという理由だけで、すべての新しいページを長期的に保持すべきホットスポットと見なすと、ホットワークセットが追い出される可能性があります。次のラウンドでビジネスがそれを再び読み込んでくることになります。空けられた各ページは一見成功していますが、システムは破棄と再読み込みの繰り返し作業を行っています。

従来の active/inactive メカニズムは、アクセスのヒントとキューの位置を使用して使用状況を近似し、anon/file などのタイプを分離して管理します。これは、各アクセスの正確なグローバル LRU を記録するものではなく、5.x 固有の履歴パスでもありません。MGLRU は有効化されている場合、マルチジェンダーで年齢情報を整理し、aging はページテーブルのアクセスビットなどのヒントを通じて最近の使用を検出し、eviction はより古い候補から推進し、refault などのフィードバックと組み合わせて選択を調整します。

1世代は、各ページ上の正確なタイムスタンプではなく、すべてのページが固定された秒数で集団的に期限切れになることも意味しません。ページテーブルと逆マッピングのスキャンにはコストがかかります。MGLRU は情報収集と整理方法を改善しましたが、すべてのページのホット度をスキャンなしで把握できるわけではありません。ファイルディスクリプターを介したアクセスとページテーブルを介したアクセスは、異なるヒントを提供することもあります。MGLRU 設計 を参照してください。

refault が1回の成功したリクレームよりもなぜ問題を示しやすいか

2つの教育用ウィンドウを比較すると、リクレーム率のみを見ると何が欠けるかがわかります:

ウィンドウスキャンリクレームその後すぐに復元されたページ問いかけるべきこと
A10000 ページ8000 ページ7000 ページ繰り返し使用されているコンテンツを誤って傷つけたか?
B同じく 10000 ページと仮定3000 ページほとんどないリクレーム率は低いものの、ビジネスはより安定しているか?

A の scan/steal は良好に見えますが、大量の重複復元コストを支払う可能性があります。B の低いリクレーム量は、必ずしも悪くないことを直接示すものではありません。

これは、refault を毎回エラーとして判断するわけではありません:ワークセットが利用可能なメモリよりも大きい場合、コンテンツのいくつかは繰り返し出入りする必要があります。フィードバックの価値は、候補グループ、タイプ、保護戦略が依然として適切かどうかを判断することにあります。pgsteal/pgscan を分配者のスコアリングとしてのみ使用すると、復元コスト、ページサイズの違い、およびアプリケーションのレイテンシーを見逃すことになります。

3. kswapd とダイレクトリクレーム:誰がリクレーム時間を支払うか

バックグラウンドで先行して行う:将来の割り当てに余裕を残す

バックグラウンドの kswapd は、リソースが逼迫する前後にノード/ゾーンの利用可能な余裕を回復しようとし、将来の割り当てが高速パスで完了しやすくします。一般的な簡略化モデルでは、低水位条件で起動し、高水位までバランスを取りますが、実際の目標は order、ゾーン、およびその他の戦略の影響を受けます。これは「free が high を下回ると固定して1ラウンドが始まる」という意味ではありません。

要求者が自分で行う:リクレームが今回のレイテンシーに入る

アロケータが許可されたリソースからページを取得できず、GFP がダイレクトリクレームを許可する場合、自身でリクレームを実行できます。これにより、スキャン、待機、その他の作業が今回の割り当てのレイテンシーに含まれます。そのため、明らかなディスクスループットのピークがなくても、明らかなメモリ停止が発生する可能性があります。ダイレクトリクレームは、成功するまで常に待機することを保証するものではなく、その後、フォールバック、失敗、またはその他の処理に入る可能性があります。

リソースドメインをまず特定する:グローバルか cgroup か

リクレームの範囲も同様に重要です。ある memory cgroup が制約に達した場合、マシン全体にまだ多くの空きページがあっても、その内部でリクレームを行うことができます。memory.high に関連するスルーリング、memory.max のハードリミット、NUMA 許可ノード、および通常のグローバルメモリプレッシャーは、同じ問題ではありません。どのリソースドメインがクォータ不足であるかをまず確認し、グローバル LRU を分析する必要があるかどうかを決定する必要があります。

slab オブジェクトはすべて通常のページ LRU にあるわけではありません。カーネルは、shrinker を介して、リクレーム能力を持つキャッシュサブシステムにオブジェクトの解放を要求します。例えば、不要なキャッシュエントリなどです。サブシステムは、参照、ロック、およびビジネスステータスを判断する責任があります。まだアクティブなユーザーが存在する場合、ページ割り当てを満たすために任意のオブジェクトを強制的に破棄することはできません。

4. 匿名ページのページアウトを完了する:コンテンツと名前を同時に保存する

プロセス P のアドレス A が物理ページ F を指しており、そこにはアプリケーションが保持しなければならないデータが含まれているとします。F をスワップに書き込むだけで、PTE が書き込みを許可し続けた場合、アプリケーションは書き込み後に F を変更し、バックアップコピーを期限切れにする可能性があります。PTE をクリアするだけで、コンテンツの場所を記録しない場合、後のページフォールトで復元できなくなります。

ページアウトでは、以下の3つの事実を同時に維持する必要があります:

事実対応する作業
コンテンツはどこから復元されるかスワップアイデンティティを構築し、バックアップを管理する
どのマッピングがそれをアクセスまたは変更できるか逆マッピングを介して PTE、書き込み保護、および TLB を調整する
物理ページがいつ再利用可能になるか必要な書き込みを完了し、ステータスと参照が解放を許可することを確認する

これら3つの作業は、並行ロックとリトライプロトコルによって接続されます。ディスクに書き込んでから任意に PTE を変更しても、その間に書き込みが行われていないことを保証できず、ページが誰も使用していないことを保証することもできません。

ページアウト後もアイデンティティを保持する必要がある:仮想アドレスからスワップエントリ、そしてコンテンツへ プロセス P:アドレス A PTE がスワップエントリ S をエンコード プロセス Q:アドレス B 同じ論理バックアップコンテンツを参照可能 スワップアイデンティティ S デバイス/タイプとオフセットなどの情報 スワップキャッシュ内の folio まだ保留中の場合、再利用を調整可能 圧縮またはストレージバックアップ 欠落時にコンテンツを復元してからマッピング アイデンティティとコンテンツの関係を示すものであり、現代のスワップキャッシュの内部インデックス構造を固定するものではありません。スワップエントリは、ディスク読み込みが発生したことを意味しません。

逆マッピングは、「このコンテンツがどの仮想マッピングによって使用される可能性があるか」に答えるものであり、すべてのプロセスのページテーブルを盲目的にスキャンする必要はありません。ファイルマッピングは、address_space のマッピングインデックスから候補 VMA を検索でき、匿名マッピングは anon_vma などの関係を活用できます。具体的なページテーブルを確認する必要があり、候補 VMA が有効な PTE を既に持っていることを意味するわけではありません。fork、COW、および大きな folio は、共有とマッピングの統計を変更し、参照カウントは正確なユーザーマッピングの列挙に代わるものではありません。

2つのスレッドが同時にページインする場合、なぜ各自が調整されていないコピーを取得してはならないのか

PTE がスワップアイデンティティ S を伴ってページフォールトをトリガーすると、カーネルはまず関連コンテンツがすでに保留中かどうか、または誰かが復元中かどうかを判断する必要があります。別のスレッドがすでに S のコンテンツをメモリに読み込んでいる場合、後から来たスレッドは待機するか、適切な結果を再利用できます。無条件で再度ストレージを読み込む必要はありません。これにより、重複 I/O が削減され、共有コンテンツ、ステータス、および参照の一貫性が維持されます。

スワップキャッシュは、スワップアイデンティティと保留中の folio を関連付ける役割を果たします。これは概念的な責任であり、旧版の address_space/XArray 構造をすべての新カーネルの実装に固定すべきではありません。保留中のオブジェクトを見つけたとしても、すぐに使用できることを意味するわけではありません。I/O が完了したか、参照が安定しているか、ページが依然としてそのアイデンティティに対応しているかを確認する必要があります。最終的なマッピングのインストール後、スワップスロットの解放時期は他の参照も考慮する必要があり、「1回のページインで即座にバックアップを削除する」と推論してはいけません。

これが、swap-in fault が必ずしもディスク I/O を生成しない理由です:コンテンツはメモリまたは圧縮バックアップ内に残っている可能性があります。大きなページのページアウトも、常に 4 KiB に分割する必要はありません。サポートパスと連続バックアップの条件はカーネルの進化に伴って変化するため、現在の実装を基準とする必要があります。

5. zswap、zram、swappiness はそれぞれ何を制御しているのか

zswap はスワップパス上の圧縮メモリキャッシュであり、通常、構成済みのスワップバックアップと協力します。圧縮に成功すれば、より少ない RAM でより多くのコンテンツを保持でき、キャッシュが逼迫するとバックアップに書き出すことができます。圧縮できないデータや上限に達した場合は、異なる処理が行われます。これは、スワップアイデンティティを必要としない汎用ファイルキャッシュではありません。zswap を参照してください。

zram は圧縮メモリブロックデバイスを公開し、スワップとして構成することも、他のブロックデバイス用途に使用することもできます。その論理的な disksize は、同じ量の RAM がすでに予約されていることを意味しません。圧縮データとメタデータは依然として実際のメモリを占有し、一部の構成では書き戻しバックアップもサポートしています。「8 GiB の zram」を物理メモリに直接追加して、確実な追加容量として扱うことはできません。zram を参照してください。

スペースの計算:100 MiB を 40 MiB に圧縮した場合、純利益はいくらか

100 MiB の圧縮:空けられるのは差額であり、コンテンツ全体ではない 圧縮前 100 MiB の元のコンテンツ 圧縮後 40 MiB が依然として RAM を占有 60 MiB の純粋な空き 圧縮後に 40 MiB と仮定し、メタデータを無視します。圧縮と解凍の CPU コストを支払う必要があります。

メタデータを無視すると、純粋な空きは 60 MiB です。95 MiB までしか圧縮できない場合、利益はわずか 5 MiB ですが、依然として圧縮/解凍の CPU コストを支払うことになります。ワークセットが繰り返しアクセスする場合、これらの復元作業がリクエストのロングテールに入る可能性もあります。

swappiness:コストを比較し、使用率の閾値を設定しない

swappiness は、スワップとファイルページのリクレームコストの相対的な嗜好を表し、現在のインターフェース範囲は 0 から 200 です。「メモリが何パーセント使用されたときにスワップを開始するか」として扱うことはできません。圧縮メモリバックアップなどの比較的安価な環境では、100 を超えることにもコスト根拠があります。0 もスワップを無効にするスイッチではありません。調整時には、実際の再読み込み、ページイン、および CPU コストを見る必要があり、固定の 60 や 0 をすべてのワークロードの答えとして扱うことはできません。VM sysctl を参照してください。

6. リクレームとメモリ整理は2つの異なる制約を解決する

手段何を変更するか自動的に解決しないもの
ページリクレームコンテンツが現在の物理ページを占有しなくなり、利用可能な数が増加する必要な連続ブロックが形成されることを保証しない
compaction 整理保持コンテンツを移動し、分散した空きページを集約する通常、空きページの総数を増やさない
zswap/zram コンテンツ圧縮より少ない RAM でコンテンツを表す物理ページを再配置することを意味しない

引き続き 16 ページのモデルを使用して、数は十分だが形状が足りない場合に何が起こるかを見てみましょう。

16 ページのうち 8 ページが空きですが、空きと占有が交互になっていると仮定します。order 0 の割り当てには多くの候補がありますが、order 3 には既存の 8 ページの連続ブロックがありません。移動可能な占有ページを別の側に移動すると、適切なブロックが形成される可能性がありますが、空きページの総数は依然として 8 ページです。移動には、仮想アドレスセマンティクスの維持、ページテーブルと関連参照の処理が必要です。ピン留めされたページや移動不可能なオブジェクトに遭遇した場合、この理想的なレイアウトが必ずしも達成できるとは限りません。ページマイグレーション を参照してください。

kcompactd とアロケータのダイレクト compaction は、整理作業を分担しており、バックグラウンド/同期という2つのコスト負担方式に似ています。高次割り当ての失敗、compaction のアクティブ化は、マシンに利用可能なメモリが全くないことを意味するわけではありません。一方、大量の匿名ワークセットに容量がない場合、ページをより整然と並べるだけでは解決できません。

OOM:失敗パスは割り当てコンテキストの制約も受ける

許可範囲内のリクレームで十分な進展が得られない場合、一部の割り当ては OOM 処理に入る可能性があります。しかし、すべての高次または GFP_ATOMIC の失敗が OOM をトリガーするわけではありません。OOM がタスクを殺した後、ページも参照の解放に伴って徐々に利用可能になります。外部によって固定されたページや他の共有参照は、リソースリクレームを遅らせる可能性があります。判断は、OOM レポート内の制約ドメイン、割り当てコンテキスト、および被害者情報と組み合わせて行う必要があります。

7. 起動時の累積的な大きな数ではなく、1つの時間ウィンドウを観察する

以下では、統計のみを読み取り、間隔は 0.2 秒とし、大量のメモリを割り当てず、キャッシュをリクレームせず、スワップも構成しません。スキャンとリクレームの同種のフィールドをペアで表示し、MGLRU の実際の有効化ビットを読み取ります。

from pathlib import Path
import time

def vmstat():
    return {key: int(value) for key, value in
            (line.split() for line in Path("/proc/vmstat").read_text().splitlines())}

before = vmstat()
start = time.monotonic()
time.sleep(0.2)
after = vmstat()
elapsed = time.monotonic() - start
for suffix in ("kswapd", "direct"):
    scan_key, steal_key = "pgscan_" + suffix, "pgsteal_" + suffix
    if scan_key in before and steal_key in before:
        scan = after[scan_key] - before[scan_key]
        steal = after[steal_key] - before[steal_key]
        print(suffix, "scan=", scan, "steal=", steal,
              "ratio=", steal / scan if scan > 0 else "スキャンなし")
for key in ("pswpin", "pswpout", "compact_stall", "compact_success", "compact_fail"):
    if key in before and key in after:
        print(key, after[key] - before[key])
print("window_seconds", elapsed)
for name in ("/sys/kernel/mm/lru_gen/enabled", "/proc/pressure/memory"):
    try:
        print(name, Path(name).read_text().strip())
    except OSError as error:
        print(name, type(error).__name__)

カウントはカーネルの構成によって欠落する可能性があります。アクティブでないウィンドウのゼロ値は、このウィンドウで対応するイベントが観測されなかったことを示すだけです。MGLRU の enabled はビットマスクであり、インターフェースドキュメントに従って各ビットを解釈する必要があります。ディレクトリが存在することは、インターフェースが公開されていることを示すだけで、メイン機能が有効化されていることを意味するものではありません。MGLRU 管理インターフェース を参照してください。

PSI memory の some は、少なくともタスクがそのリソースによって停止した時間を記述し、full は関連範囲内のすべてのアイドルでないタスクがブロックされている時間を記述します。これは停止を測定するものであり、RAM 使用率ではありません。累積 total の単位はマイクロ秒であり、avg10/60/300 は対応するウィンドウの平均です。グローバルと cgroup の範囲を混算してはいけません。PSI を参照してください。

カウントを読んだ後、因果関係の順序に従って位置特定を続けます:

  1. 制約ドメインを確認する。 プレッシャーはグローバル、cgroup、または特定の許可ノードに属しているか?
  2. 停止位置を確認する。 ダイレクトリクレームが作業をアロケータパスに押し付けているか?
  3. 同じウィンドウで比較する。 スキャン、リクレーム、refault、スワップ、compaction、およびビジネスレイテンシーを併せて見る。

anon/file と kswapd/direct は異なるグループ化次元であり、これらのカウントを区別せずにすべて合計してはいけません。制約、復元コスト、およびビジネスの停止が対応している場合にのみ、局所性を改善し、容量を拡大するか、不要な連続割り当て要求を減らすかを判断できます。