システムに関する実験で、スワップを有効にした状態でGoアプリケーションがメモリ逼迫にさらされると、大きな遅延が生じるリスクが特定された。ガベージコレクタが必要とする管理データ自体が、物理メモリから退避されることがある。ランタイムが後にプログラム全体を停止する「stop-the-world」の段階に入り、そのページにアクセスすると、ストレージからの読み出しによってGoプロセス全体が待たされる可能性がある。
実験では、Goの処理と、ほぼアイドル状態のHTTPサーバーを同じLinuxのコントロールグループに配置し、メモリ逼迫を発生させた。Goプロセスは511 KiBのメッセージからメモリ上のオブジェクトを繰り返し構築し、バイトバッファと、ガベージコレクタが走査する必要のある多数のポインタを含む構造体を生成した。ホストは多世代LRUを有効にしたLinux 6.8を使用し、ローカルのNVMeストレージとネットワーク経由のボリュームの両方で実験を行った。
ガベージコレクションによる停止時間は、大半が非常に短かった。報告された中央値は約51マイクロ秒だった。しかし、ランタイムのメタデータがNVMeにスワップアウトされた場合、最も長い停止は約40ミリ秒に達し、中央値の約800倍となった。BPFを使った計測では、そのうち39ミリ秒が、プログラム停止中に発生した228回のページフォールトによるものとされた。
この仕組みは、通常のヒープ走査とは異なる。Goはランタイムのメタデータのページを保持し、ガベージコレクションの各サイクルで再利用する。カーネルはページの利用状況に応じて退避対象を決めるため、アクセス頻度の低い管理用ページがスワップの候補になることがある。スイープ終了処理とマーク終了処理の際、Goはアプリケーションの実行を停止し、このメタデータを読み込む。必要なページがRAMに存在しなければ、ランタイムはカーネルがメジャーページフォールトを解決し、ストレージからページを取り出すまで待たなければならない。
そのため、影響は1つのgoroutineにとどまらず、プロセス全体に及ぶ。停止中に実行可能になった処理も、ガベージコレクタがプログラムの停止を解除するまで実行できない。30分間のテストで、ランタイムはこうしたstop-the-worldの段階に312回入り、模擬的なメモリ使用量の急増のたびに、その前後で長い停止が2~3回発生した。
実験では、メモリ逼迫時にメッセージの構築も遅くなることが観測された。通常は3~5ミリ秒かかる処理が、NVMeによるスワップでは105ミリ秒、ネットワーク経由のボリュームでは903ミリ秒に延びた。実験の著者は、この速度低下の正確な原因を特定していない。そのため、原因が判明したランタイムの不具合ではなく、観測結果として扱うべきだ。メタデータによる停止とは異なり、この負荷の影響はメモリを割り当てるgoroutineに限られていた。
この結果は、スワップがあらゆるGoサービスに不向きだと示すものではない。メモリ使用量の急増を吸収するためにスワップを使う際、運用担当者が計測すべき、テールレイテンシに関する特定の問題を示している。実験によると、Go 1.26のGreen Teaコレクタを用いたテストでも、この現象に対する変化はごくわずかだった。このため、応答時間の目標が厳しいワークロードでは、メジャーページフォールトとstop-the-worldの継続時間を監視し、スワップを余裕確保の手段として頼る前に、現実的なメモリ逼迫の下でメモリ制御の設定を検証する必要があるかもしれない。



