その後のgolangと(Dockerと)OOM

Go製のプログラムでの最新のOOM対策をお届けします。

前の記事:golangとDockerとOOM を書いた後で Go側の事情に変化があったため、 あの記事に書かれた方法は現実的な選択肢ではなくなってしまいました。 この記事では私が使っているGo 1.14以降でのOOM対策と、 どうしてそうせざるを得なかったのか解説をお届けします。

TL;DR

  • Goの64ビット版はVSZの最小要求量が大幅に拡大した (500MB超)
  • 前の記事で紹介した方法が現実的ではなくなった
    • VSZの制限をRSSに転用する=最低でも500MBのRSSを設定することになる
  • 代わりに自プロセスのRSSを監視して閾値を超えたらアポトーシスするようにした

Background

あの記事を書いた翌月末にGo 1.14がリリースされました。 その変更点の中に以下の記述があります。

The page allocator is more efficient and incurs significantly less lock contention at high values of GOMAXPROCS. This is most noticeable as lower latency and higher throughput for large allocations being done in parallel and at a high rate.

「へーメモリアロケーションが速くなったんだー凄いねー」(CV:さとうささら)

って声が聞こえるかもしれませんが、これの副作用でGo製のプログラムが要求する最低VSZが500MBを超えました。 念を押しておきますがこれはあくまでも仮想メモリの話なので実際の物理メモリ使用量(RSS)はほぼ変わっていません。

ちなみにですが、これは64ビット版のGo製のプログラムの話で、32ビット版であればここまで大きくはなりません。せいぜい数MBです。 なぜならばGoではメモリ管理のためにアロケーションページ毎のビットマップ+αの構造を用いるのですが、 このとき32ビットアーキテクチャで利用可能な4GBならば4GB/4KBで高々100万ページ分を確保するだけで事足りるのですが、 64ビットアーキテクチャの広大なメモリ空間ではそれが最終的に500MBを超えてしまうのです。 なお起動時にそのためのメモリ領域を確保してしますが、 領域の確保だけで物理メモリの割り当ては起こりません。

ここで前の記事で紹介した方法のポイントは「VSZの制限をRSSに流用する」だったことを思い出しましょう。 これは裏を返せば「VSZの最低要求量がRSSの最低要求量になる」ということでもあります。

もうお分かりですね。 前回のOOM対策法を用いると、 Go製のプログラムの最低物理メモリ要求量が500MBとなってしまうのです。 私の実例では物理メモリ使用量は100MBを滅多に超えませんのでしたので、 乖離があまりに大きく現実的な解決策ではなくなってしまいました。

「OOM対策が使用メモリ増大を産むなら、みんな死ぬしかないじゃない! あなたも、私も…」

そうなのです。 もうこうなっては死ぬしかありません。 ということで前記事の方法はすっかり破棄しまして、 物理メモリ使用量=RSSを監視して指定した閾値を超えたら情報を残して自死するようにしました。

一口にRSSを監視すると言っても単に runtime.ReadMemStats() を使うわけにはいきません。 というのも runtime.ReadMemStats() はGoが管理用に使用している物理メモリの一部を合算しないようになっています。 そのためどうしてもズレが生じてしまいます。

ということでRSS=現在の物理メモリ使用量を取得するためのパッケージ koron-go/phymem を作りました。 このパッケージは現時点でWindowsとLinuxとPlan 9にしか対応していませんが、 phymem.Current() が現在使用中のメモリ量を返します。

私はこのphymemを使って定期的(15秒毎)に物理メモリの使用量をチェックし、 閾値を超えていたら情報を残して自らをgraceful shutodownするようにしています。 閾値はcgroup (/sys/fs/cgroup/memory/memory.limit_in_bytes) から取得すればdockerとの連携も、前記事同様に可能になります。 急激な物理メモリ使用量の増大には対応できませんが、 おおむねこの方法でうまく機能しているようです。 (というのはその後OOMで殺されるor死ぬことがないので、機能しているとの確信がない)

以上、前の記事の補足:Go製プログラムにおける最新のOOM対策でした。