DatadogがエージェントのGoバイナリサイズを77%削減した方法
Datadogのエンジニアチームは、5年間で428MiBから1.22GiBに肥大化したAgentのGoバイナリサイズを、依存関係の最適化やリンカーの調整により77%削減することに成功した。
キーポイント
バイナリ肥大化の背景
Datadog Agentのバイナリサイズが5年間で約3倍(428MiB→1.22GiB)に増加し、パフォーマンスやデプロイ効率に悪影響を与えていた。
肥大化の主な原因
隠れた依存関係、リンカー最適化の無効化、Goコンパイラとリンカーの微妙な挙動がバイナリ肥大の主要因として特定された。
最適化アプローチ
依存関係の見直し、リンカー設定の最適化、コンパイラ挙動の理解と調整を通じて、バイナリサイズを大幅に削減した。
削減効果
これらの最適化により、バイナリサイズを77%削減することに成功し、エージェントのパフォーマンスと運用効率を大幅に改善した。
影響分析・編集コメントを表示
影響分析
この事例は、大規模な本番環境で運用されるソフトウェアのパフォーマンス最適化における実践的な知見を提供しており、特にGoエコシステムにおけるバイナリサイズ管理のベストプラクティスとして参考になる。クラウドネイティブ環境では、コンテナサイズや起動時間の最適化がコストとパフォーマンスに直結するため、同様の課題に直面する多くの企業にとって有用なケーススタディとなる。
編集コメント
実運用環境での具体的な最適化事例として価値が高く、特に大規模なGoプロジェクトを扱うエンジニアにとって参考になる内容。技術的な深掘りは限定的だが、実践的な課題解決のプロセスが明確に示されている。
Datadog エージェントのバイナリサイズが 5 年間で 428 MiB から 1.22 GiB に増加した後に、Datadog のエンジニアたちはそのバイナリサイズを削減することに取り組みました。彼らは、Go バイナリの肥大化の大部分が、隠れた依存関係、無効化されたリンカー最適化、および Go コンパイラとリンカーにおける微妙な動作に起因することを発見しました。
この成長は、Datadog 側とそのユーザー双方に影響を及ぼしました:ネットワークコストとリソース使用量が増加し、エージェントに対する評価が悪化し、リソース制約のあるプラットフォームでエージェントを使用することが困難になりました。
これに対処するため、Datadog のソフトウェアエンジニアである Pierre Gimalac は、彼らのアプローチはインポートの監査、オプションコードの分離、および反射/プラグインの落とし穴の排除によってバイナリを可能な限り小さくすることであると記述しています。
実際、エージェントの成長を分析した結果、Datadog のエンジニアたちはそれが新機能、追加された統合、そして大規模なサードパーティ製依存関係(例:Kubernetes SDK)によって駆動されていることを発見しました。特に、Go の依存関係モデルには転送インポートが含まれているため、小さな変更であっても数百のパッケージを引き込む可能性があります。
Datadog のエンジニアは、不要な依存関係を削除するための 2 つの実用的な方法を考案しました。1 つ目はビルドタグ(//go:build feature_x)を使用してオプションのコードを除外する方法、2 つ目はコードを別パッケージに移動させ、必須のパッケージが可能な限り小さくなるようにする手法です。これらの両方の技術には、特定のビルドからどのファイルやパッケージを除外できるかを特定するために、インポートを体系的に監査することが必要でした。例えば、単に関数を独自のパッケージに移動しただけで、それを使用しないバイナリから約 570 のパッケージと約 36 MB の生成コードが削除されました。
依存関係の監査は容易な作業ではありませんが、Go エコシステムにはこれを支援する 3 つの有用なツールがあります。1 つ目は go list で、ビルドで使用されるすべてのパッケージをリストアップします。2 つ目は goda で、依存グラフやインポートチェーンを可視化し、特定の依存関係が必要とされる理由を理解するのに役立ちます。3 つ目は go-size-analyzer で、各依存関係がバイナリにどの程度の容量を割り当てているかを示します。
依存関係の最適化に加え、Datadog のエンジニアは反射(reflection)の使用を最小限に抑えることで、さらに 20% のサイズ削減を実現しました。反射を使用すると、リンカー最適化の一部が静かに無効化される可能性があります。その一例としてデッドコード除去(dead-code elimination)があります。
もし定数でないメソッド名を使用した場合、リンカーはビルド時に実行時にどのメソッドが使用されるかを把握できなくなります。そのため、到達可能なすべての型のエクスポートされたメソッドと、それらが依存するすべてのシンボルを保持する必要があり、その結果、最終的なバイナリのサイズが劇的に増加してしまう可能性があります。
この課題に対処するため、彼らはコードベースおよび依存ライブラリの両方で可能な限り動的リフレクションを排除しました。後者のステップでは、kubernetes/kubernetes、uber-go/dig、google/go-cmp などのプロジェクトに対して複数のプルリクエスト(PR)を提出する必要がありました。
デッドコード除去を無効化するもう一つの特徴は Go プラグインです。これは Go プログラムが実行時に動的に Go コードを読み込むメカニズムを提供します。実際、プラグインパッケージを単にインポートするだけで、リンカーはバイナリを動的リンクとして扱い、「これによりメソッドのデッドコード除去が無効化され、さらに非公開メソッドまですべて保持させることになります」。この変更により、一部のビルドではさらに約 20% の削減が実現されました。
最後に、Gimalac はこれらの改善は 6 ヶ月かけて達成されたものであり、最も重要なのは機能の削除を一切必要としなかったと強調しています。彼の報告にはここで取り上げきれない詳細な内容も含まれているため、完全なストーリーを知るためには必ず原文をお読みください。
著者について
セルジオ・デ・シモーネ
セルジオ・デ・シモーネはソフトウェアエンジニアです。セルジオは過去 25 年以上にわたり、シーメンス、HP、そして小規模なスタートアップなど、多様なプロジェクトや企業でソフトウェアエンジニアとして活動してきました。最近の 10 年以上は、モバイルプラットフォームおよび関連技術の開発に注力しています。現在では BigML, Inc. に勤務し、iOS および macOS の開発を率いています。
詳細を表示する/表示しない
原文を表示
After the Datadog Agent grew from 428 MiB to 1.22 GiB over a period of 5 years, Datadog engineers set out to reduce its binary size. They discovered that most Go binary bloat comes from hidden dependencies, disabled linker optimizations, and subtle behaviors in the Go compiler and linker.
This growth impacted both us and our users: network costs and resource usage increased, perception of the Agent worsened, and it became harder to use the Agent on resource-constrained platforms.
To address this, Datadog software engineer Pierre Gimalac wrote that their approach consisted of auditing imports, isolating optional code, and eliminating reflection/plugin pitfalls to shrink binaries as much as possible.
Indeed, after analyzing the Agent's growth, Datadog engineers found out it was driven by new features, additional integrations, and large third-party dependencies (e.g., Kubernetes SDKs). In particular, Go’s dependency model includes transitive imports, so even a small change can pull in hundreds of packages.
Datadog engineers devised two practical ways to remove unnecessary dependencies: using build tags (//go:build feature_x) to exclude optional code, and move code into separate packages so that non-optional packages remain as small as possible. Both techniques required systematically auditing imports to identify which files or packages could be excluded from a given build. For example, simply moving one function into its own package removed ~570 packages and ~36 MB of generated code from a binary not using it.
Auditing dependencies is not an easy task, but the Go ecosystem provides three useful tools to help: go list, which lists all packages used in a build; goda, which visualizes dependency graphs and import chains to help understand why a given dependency is required; and go-size-analyzer, which shows how much space each dependency contributes to a binary.
Besides dependency optimization, Datadog engineers got an additional 20% size reduction by minimizing the use of reflection, which can silently disable some linker optimizations, including dead-code elimination:
if you use a non-constant method name, the linker can no longer know at build time which methods will be used at runtime. So it needs to keep every exported method of every reachable type, and all the symbols they depend on, which can drastically increase the size of the final binary.
To address this issue, they eliminated dynamic reflection where possible, both in their codebase and in dependencies. The latter step required submitting several PRs to projects such as kubernetes/kubernetes, uber-go/dig, google/go-cmp and others.
Another feature that disables dead-code elimination is Go plugins, a mechanism that enables a Go program to dynamically load Go code at runtime. In fact, simply importing the plugin package causes the linker to treat the binary as dynamically linked, "which disables method dead code elimination and even forces the linker to keep all unexported methods". This change yielded an additional ~20% reduction in some builds.
As a final note, Gimalac emphasizes that these improvements were achieved over a six-month period and, most importantly, did not require removing any feature. His account includes many more details than can be covered here, so be sure to read it to get the full story.
About the Author
Sergio De Simone
Sergio De Simone is a software engineer. Sergio has been working as a software engineer for over twenty five years across a range of different projects and companies, including such different work environments as Siemens, HP, and small startups. For the last 10+ years, his focus has been on development for mobile platforms and related technologies. He is currently working for BigML, Inc., where he leads iOS and macOS development.
Show moreShow less
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み