HeadlinesBriefing HeadlinesBriefing.com

Go GC一時停止スパイク:スワップ排出

Hacker News •
×

これを書いているのは、メモリスパイクを吸収するためにプロダクションでスワップを実行することにしたとき、私のようにほぼ額に手を叩かないようにするためです。私は2つのプロセスを持つcgroupを持っていました:1つはGoプロセスで、io.ReadAllを呼び出した後にproto.Unmarshalを呼び出し、blobを作成し、次にグラフ構造を作成します(これはGoのアロケータによってスキャンとしてマークされています)。もう1つは、ほとんどの時間静かにしているHTTPサーバーです。コレクタが実行されるたびに、それらのスキャンスパンをポインタごとに読み取り、それらをどうするかを決定します。だから私は思いました:OK、メモリの圧力の下で、カーネルはページをスワップデバイスに退避させますが、退避はcgroupごとに行われ、プロセスごとには行われないため、両方のプロセスのページが退避されるため、これがカーネルとガベージコレクタの間のスワップインとスワップアウトの悲しいダンスになる可能性は非常に小さいです。私は間違っていました。これを実験している間、私は自分を傷つける可能性のある問題を見つけました:Goのガベージコレクタは、stop-the-worldの一時停止中にメタデータ(ヒープ外、解放されていない領域)を読み取り、そのメタデータはスワップに存在する可能性があります。私はMGLRUを有効にしたカーネル6.8を使用してHetznerのマシンでシミュレーションを行いました。中央値の一時停止は約51μsでした。メタデータをNVMeに配置した場合、最悪の一時停止は40msでした。その40msがどこに行ったのかを確認するために、世界が停止している間にページフォルトをカウントする小さなbpfスクリプトを書きました。これが最悪でした:39902μs、その間228回のフォルト、39013μsがフォルトに費やされました。40msのうち39msは228回のページフォルトに費やされました。これらのフォルトはGCの簿記の中で発生しました。これは潜在的な障害モードです。GoのGCは2つのポイントで世界を停止する必要があります:スイープ終了を実行するとき、およびマーク終了を実行するとき。私たちは30分間で312回の一時停止を経験しました。だからこれが起こる理由は次のとおりです:ランタイムはこれらのページを割り当てます。それらは解放されませんが、再利用されます。これらのページはGCサイクルで読み取られます。カーネルは年齢に基づいてページを退避させるため、最も最近アクセスされていないページをスワップに送信します。GCが実行され、世界を停止し、それらのページを読み取ろうとしますが、今度はメジャーページフォルトが発生します。カーネルはPTEを読み取り、次にdo_swap_pageを呼び出し、新しいフレームを見つけ、それをcgroupに課金し、ページを読み取り、bioを送信し、ディスクを待ち、それらをメモリに戻します —簡潔に言えば。最初にこれらの40msは無害に見えます。しかし、私たちはstop-the-worldの一時停止について話しています。これらの40msはすべてが停止したことを意味します —Goの用語では、すべてのPが停止しているため、たとえば、goroutineがI/Oを待っていた場合、その一時停止中にI/Oが戻ってくる可能性があり、それを処理する人はいません。40msは中央値の800倍です。テスト中のメモリスパイクごとに2〜3回発生します。それはたくさんです。

出典: Hacker News · 要約:HeadlinesBriefing