TLDR マイクロサービスの一部をJavaからGoに移植することにより、メモリ使用量を数桁削減しました。
最初はJavaでした
AeroFSアプライアンスのアーキテクチャは多くのマイクロサービスで構成されており、それらの大部分はJavaで記述されています。 これにより問題が発生することはありません。システム全体が、パフォーマンスの問題なく、さまざまなクライアントからの何千ものユーザーにサービスを提供します。
ただし、Dockerに切り替えた後、システムのメモリ使用量が急激に増加していることに気付きました。 Dockerを監視するためのいくつかのファッショナブルなユーティリティをテストした後、このややオタクですが、非常に便利なスクリプトに決めました。
for line in `docker ps | awk '{print $1}' | grep -v CONTAINER`; do \ echo $(( `cat /sys/fs/cgroup/memory/docker/$line*/memory.usage_in_bytes` / 1024 / 1024 ))MB \ $(docker ps | grep $line | awk '{printf $NF" "}') ; \ done | sort -n
使用中のメモリの数でソートされた実行中のコンテナのリストを表示します。 スクリプト出力の例:
46MB web 66MB verification 74MB openid 82MB havre 105MB logcollection 146MB sp 181MB sparta
この問題を検証した結果、一部のJavaサービスは驚くほど多くのメモリを使用していることがわかりました。多くの場合、複雑さやその欠如と相関することはありませんでした。 このメモリ使用につながったいくつかの主要な要因を特定しました。
- 各Tomcatサーブレットが個別のコンテナで実行されるため、実行中のJVMの数が増加します。
- 複数のJVMが読み取り専用メモリを共有する能力の低下:JVM自体、すべての依存ライブラリ、そしてもちろん、さまざまなサービスで使用される多くのJAR
- 場合によってはメモリの分離により、メモリ計算のヒューリスティックが混乱し、一部のサービスで大量のキャッシュが割り当てられた
64kbのメモリを搭載したデバイス用のZ80のアセンブリ言語を書くことに慣れている老人である私は、数百メガバイトの貴重なRAMを返す方法のアイデアに非常に触発されました。 幸いなことに、私たちの次のハッカソンはわずか数日後でした。問題に集中する絶好のチャンスであり、新しいツールを試す良い口実でした。
コードネーム:グリースファイア
(翻訳者のメモ:グリースファイア-「ストーブでの油/脂肪の発火による火災」)
このハッカソンの私の主な目標は、比phor的な脂肪の層を燃やして、AeroFSシステムが使用するメモリの総量を減らすことでした。
特に、私の成功基準は次のとおりです。
- CPU使用率が大幅に増加することはありません
- メモリの安定性とセキュリティを維持する必要があります
- 常駐メモリの使用を2回以上減らす必要があります
ハッカソンの精神で、新しい言語とツールを試してみたかったので、既存のサービスを微調整するだけでは選択肢になりませんでした。
また、表示できる結果を取得する可能性を高め、場合によっては展開することさえ可能にするために、適切なサイズと複雑さのターゲットを選択することが重要でした。 明らかな選択は、TeamServerプローブサービス(アプライアンスステータスページのチームサーバー)でした。これは、単一のHTTP呼び出しと非常に明確な内部ロジックを備えた小さなTomcatサーブレットです。
その結果、目標はサーバーを作成することでした:
- 完全に同一のAPI
- Dockerイメージにパッケージ化
新しいツールを試す
CPUとメモリの基準を満たすために、主要な候補はシステムプログラミング用に作成されたコンパイル言語でした。 ハッカソンは結果がすぐに使用できることを意味しませんでしたが、私はコードを簡単に維持し、より暗い代替物を避けるべきであるという事実に固執しました。
候補者のプールはすぐに2人のメンバー-GoとRustに絞り込まれました。 どちらも、コードを小さな静的バイナリにコンパイルするのに十分簡単で、理想的には最小限のコンテナで実行するように調整されています。 どちらも十分なパフォーマンス、メモリの安全性、競合プログラミングへの優れたサポートを約束し、特に私にとって重要だったのは、JVMの場合よりもメモリ使用量が少ないことです。
複雑なRust型システムは特に興味深く見えました。 しかし同時に、RustはGoほど成熟しておらず、当時はバージョン1.0にも達していませんでした。 Rustは、HTTPおよび低レベルネットワーキング用の優れたライブラリの欠如によっても妨げられました。
以前、私たちはすでにサービスの1つをGoに移植しようとしていましたが、2013年にその年でしたが、その瞬間に何らかのメモリリークが発生し、実験を中止することにしました。 2年後、Goはより成熟し、私たちの実験に最適な候補として選ばれました。
GoはCファミリの言語に非常に似ているため、非常に迅速に習得できますが、同時にコードとドキュメント間の最初の数日間の切り替えを必要とする十分な機能が含まれています。 幸いなことに、ドキュメントはよく書かれており、標準ライブラリのソースコードに非常にうまく組み込まれています。これは、さまざまなポイントを明確にし、慣用的なコードを理解するのに非常に役立ちました。
言語をフォーマットするための単一の標準と、vimのようなテキストエディターと非常に簡単に統合することを強制するマジックユーティリティgofmtがあることも非常に嬉しかったです(少し内部的な話:このハッカソンは、vimを単一行以上のものに使用する最初の試みでもありました編集)
結果
Goを知り、ハッカソン用に選択された簡単なサービスを移植するのに1日ほどかかりました。 結果は非常に有望でした:
- コードサイズは175行から96行に半分になりました
- 常駐メモリの使用量は87MBからわずか3MBに減少し 、 29倍減少しました !
- 結果のdockerイメージは668MBから4.3MBに減少しました-これは155倍の減少です! Dockerイメージの最大レイヤーがさまざまなサービスでまだ再利用されているため、多くのJavaサービスを使用した場合のディスク使用量の実際の減少ははるかに少ないことに同意します。 それでも、これらの数字は目を楽しませてくれました。
ハッカソンの前にはまだほぼ丸1日があり、別のサービス-認証局(アプライアンスステータスページのca)に気付きました。 このサービスは、内部サービスおよびデスクトップクライアントからの証明書署名要求を受け入れ、署名された証明書を返します。これは、クライアント間のピアツーピアコンテンツの転送の暗号化およびクライアント/サーバー通信に使用されます。
ハッカソンが終了してから数日後、この新しいCAが最終的にJavaに相当するものを置き換えたとき、信じられないほど100倍メモリ使用量が削減されました。
このプロジェクトは、「技術的急峻性」 (元々「技術的驚異」)にノミネートされ、システム全体のメモリ使用量を削減する継続的な取り組みになりました。
バージョン1.1.5までに、さらに4つのサービスがGo-6に移植され、総メモリ節約量は1ギガバイトでした。 いずれの場合も、コードサイズが同様に減少し、場合によっては、プロセッサの使用量が大幅に削減されるか、スループットが向上します。
-HuguesとAeroFSチーム。