The Go Blog

コンテナを意識したGOMAXPROCS

Michael PrattとCarlos Amedee
2025年8月20日

Go 1.25には、コンテナを意識した新しいGOMAXPROCSのデフォルトが導入されました。これにより、多くのコンテナワークロードでより適切なデフォルト動作が提供され、末尾のレイテンシに影響を与える可能性のあるスロットリングを回避し、Goのすぐに使える本番環境対応を向上させます。この投稿では、Goがゴルーチンをどのようにスケジュールするか、そのスケジューリングがコンテナレベルのCPU制御とどのように相互作用するか、そしてGoがコンテナCPU制御を認識することでどのようにパフォーマンスを向上させることができるかについて掘り下げていきます。

GOMAXPROCS

Goの強みの1つは、ゴルーチンによる組み込みの使いやすい並行処理です。セマンティックな観点から見ると、ゴルーチンはオペレーティングシステムのスレッドと非常に似ており、シンプルでブロッキングなコードを記述できます。一方、ゴルーチンはオペレーティングシステムのスレッドよりも軽量であるため、その場で作成および破棄するコストがはるかに低くなります。

Goの実装は各ゴルーチンを専用のオペレーティングシステムスレッドにマップできますが、Goはランタイムスケジューラによってスレッドを代替可能にすることでゴルーチンを軽量に保ちます。Goが管理するどのスレッドでもどのゴルーチンも実行できるため、新しいゴルーチンを作成するために新しいスレッドを作成する必要はなく、ゴルーチンを起動するために必ずしも別のスレッドを起動する必要もありません。

とはいえ、スケジューラにはスケジューリングの問題が伴います。たとえば、ゴルーチンを実行するために正確にいくつのスレッドを使用すべきでしょうか?1,000個のゴルーチンが実行可能である場合、それらを1,000個の異なるスレッドにスケジュールすべきでしょうか?

ここでGOMAXPROCSが登場します。セマンティックには、GOMAXPROCSはGoランタイムにGoが使用すべき「利用可能な並列性」を伝えます。より具体的には、GOMAXPROCSは一度にゴルーチンを実行するために使用するスレッドの最大数です。

したがって、GOMAXPROCS=8で実行可能なゴルーチンが1,000個ある場合、Goは8つのスレッドを使用して一度に8つのゴルーチンを実行します。多くの場合、ゴルーチンは非常に短い時間実行されてからブロックし、その時点でGoは同じスレッドで別のゴルーチンの実行に切り替えます。Goは、自身でブロックしないゴルーチンもプリエンプトし、すべてのゴルーチンが実行される機会を確実に得られるようにします。

Go 1.5からGo 1.24まで、GOMAXPROCSはマシンのCPUコアの総数にデフォルト設定されていました。この投稿では、「コア」はより正確には「論理CPU」を意味することに注意してください。たとえば、ハイパースレッディングを備えた4つの物理CPUを持つマシンには、8つの論理CPUがあります。

これは通常、「利用可能な並列性」の適切なデフォルトとなります。なぜなら、ハードウェアの利用可能な並列性と自然に一致するからです。つまり、8つのコアがあり、Goが一度に8つ以上のスレッドを実行する場合、オペレーティングシステムはこれらのスレッドを8つのコアに多重化する必要があります。これはGoがゴルーチンをスレッドに多重化する方法とよく似ています。この追加のスケジューリングレイヤーは常に問題となるわけではありませんが、不要なオーバーヘッドです。

コンテナオーケストレーション

Goのもう1つの主要な強みは、コンテナを介したアプリケーションのデプロイの利便性であり、コンテナオーケストレーションプラットフォーム内でアプリケーションをデプロイする場合、Goが使用するコアの数を管理することは特に重要です。Kubernetesのようなコンテナオーケストレーションプラットフォームは、一連のマシンリソースを取得し、要求されたリソースに基づいて利用可能なリソース内でコンテナをスケジュールします。クラスターのリソース内に可能な限り多くのコンテナを詰め込むには、プラットフォームが各スケジュールされたコンテナのリソース使用量を予測できる必要があります。Goには、コンテナオーケストレーションプラットフォームが設定するリソース使用量制約を遵守してほしいのです。

例として、KubernetesのコンテキストにおけるGOMAXPROCS設定の影響について調べてみましょう。Kubernetesのようなプラットフォームは、コンテナが消費するリソースを制限するメカニズムを提供します。KubernetesにはCPUリソース制限の概念があり、これは基盤となるオペレーティングシステムに、特定のコンテナまたは一連のコンテナに割り当てられるコアリソースの数を通知します。CPU制限を設定することは、LinuxのコントロールグループCPU帯域幅制限の作成につながります。

Go 1.25より前は、Goはオーケストレーションプラットフォームによって設定されたCPU制限を認識していませんでした。代わりに、デプロイされたマシンのコア数にGOMAXPROCSを設定していました。CPU制限が設定されている場合、アプリケーションは制限で許可されているよりもはるかに多くのCPUを使用しようとする可能性があります。アプリケーションがその制限を超えるのを防ぐために、Linuxカーネルはアプリケーションをスロットリングします。

スロットリングは、CPU制限を超える可能性のあるコンテナを制限するための粗雑なメカニズムです。それはスロットリング期間の残りの間、アプリケーションの実行を完全に一時停止します。スロットリング期間は通常100ミリ秒であるため、スロットリングは、より低いGOMAXPROCS設定によるよりソフトなスケジューリング多重化効果と比較して、かなりの末尾のレイテンシ影響を引き起こす可能性があります。アプリケーションがそれほど並列性を持っていなくても、Goランタイムが実行するタスク(ガーベージコレクションなど)は、CPUスパイクを引き起こし、スロットリングを誘発する可能性があります。

新しいデフォルト

Goには可能な限り効率的で信頼性の高いデフォルトを提供してほしいと考えているため、Go 1.25では、GOMAXPROCSがデフォルトでコンテナ環境を考慮するようにしました。CPU制限のあるコンテナ内でGoプロセスが実行されている場合、GOMAXPROCSは、コア数よりも少ない場合はCPU制限にデフォルト設定されます。

コンテナオーケストレーションシステムは、コンテナのCPU制限を動的に調整する可能性があるため、Go 1.25は定期的にCPU制限を確認し、変更された場合は自動的にGOMAXPROCSを調整します。

これらのデフォルトはどちらも、GOMAXPROCSがそれ以外に指定されていない場合にのみ適用されます。GOMAXPROCS環境変数を設定するか、runtime.GOMAXPROCSを呼び出すと、以前と同じように動作します。runtime.GOMAXPROCSのドキュメントには、新しい動作の詳細が記載されています。

わずかに異なるモデル

GOMAXPROCSとコンテナのCPU制限はどちらもプロセスが使用できるCPUの最大量を制限しますが、それらのモデルは微妙に異なります。

GOMAXPROCSは並列性の制限です。GOMAXPROCS=8の場合、Goは一度に8つ以上のゴルーチンを実行することはありません。

対照的に、CPU制限はスループットの制限です。つまり、特定のウォールタイム期間で使用されるCPU時間の合計を制限します。デフォルト期間は100ミリ秒です。したがって、「8 CPU制限」は、実際にはウォールタイム100ミリ秒ごとに800ミリ秒のCPU時間の制限を意味します。

この制限は、100ミリ秒間継続的に8つのスレッドを実行することで満たすことができます。これはGOMAXPROCS=8と同等です。一方、この制限は、それぞれ50ミリ秒間16個のスレッドを実行し、残りの50ミリ秒は各スレッドがアイドル状態またはブロック状態であることでも満たすことができます。

言い換えれば、CPU制限はコンテナが実行できるCPUの総数を制限するものではありません。CPU時間の総量のみを制限します。

ほとんどのアプリケーションは100msの期間で比較的安定したCPU使用量を示すため、新しいGOMAXPROCSのデフォルトはCPU制限とかなり一致しており、コア総数よりも間違いなく優れています!ただし、特にスパイクのあるワークロードでは、CPU制限の平均を超える追加スレッドの短期間のスパイクをGOMAXPROCSが防ぐため、この変更によりレイテンシが増加する可能性があることに注意してください。

さらに、CPU制限はスループット制限であるため、小数部のコンポーネントを持つことができます(例:2.5 CPU)。一方、GOMAXPROCSは正の整数でなければなりません。したがって、Goは制限を有効なGOMAXPROCS値に丸める必要があります。Goは常に制限を切り上げて、CPU制限を最大限に活用できるようにします。

CPUリクエスト

Goの新しいGOMAXPROCSのデフォルトはコンテナのCPU制限に基づいていますが、コンテナオーケストレーションシステムは「CPUリクエスト」制御も提供します。CPU制限がコンテナが使用できる最大CPUを指定するのに対し、CPUリクエストはコンテナに常に利用可能であることが保証される最小CPUを指定します。

CPUリクエストはあるがCPU制限がないコンテナを作成することは一般的です。これにより、コンテナは、他のコンテナからの負荷不足によりアイドル状態になるであろうCPUリソースを、CPUリクエストを超えて利用できます。残念ながら、これはGoがCPUリクエストに基づいてGOMAXPROCSを設定できないことを意味し、追加のアイドルリソースの利用を妨げることになります。

CPUリクエストのあるコンテナは、マシンがビジー状態の場合、リクエストを超えると依然として制約を受けます。リクエスト超過の重みベースの制約は、CPU制限の厳格な期間ベースのスロットリングよりも「ソフト」ですが、高いGOMAXPROCSによるCPUスパイクは依然としてアプリケーションの動作に悪影響を与える可能性があります。

CPU制限を設定すべきか?

GOMAXPROCSが高すぎる場合に生じる問題と、コンテナのCPU制限を設定することでGoが適切なGOMAXPROCSを自動的に設定できるようになることを学びました。そこで次の当然の疑問は、すべてのコンテナがCPU制限を設定すべきかどうかです。

適切なGOMAXPROCSのデフォルトを自動的に得るには良いアドバイスかもしれませんが、CPU制限を設定するかどうかを決定する際には、制限を回避してアイドルリソースの利用を優先するか、制限を設定して予測可能なレイテンシを優先するかなど、他にも多くの考慮事項があります。

GOMAXPROCSと実効CPU制限の不一致による最悪の動作は、GOMAXPROCSが実効CPU制限よりも大幅に高い場合に発生します。たとえば、128コアのマシン上で2つのCPUを受け取る小さなコンテナが実行されている場合などです。これらのケースでは、明示的なCPU制限を設定するか、あるいは明示的にGOMAXPROCSを設定することを検討することが最も価値があります。

まとめ

Go 1.25は、コンテナのCPU制限に基づいてGOMAXPROCSを設定することで、多くのコンテナワークロードに対してより適切なデフォルト動作を提供します。これにより、テールレイテンシに影響を与える可能性のあるスロットリングを回避し、効率を向上させ、Goがすぐに本番環境に対応できるように努めます。新しいデフォルトは、go.modでGoバージョンを1.25.0以上に設定するだけで利用できます。

この現実を実現した長い議論に貢献してくださったコミュニティの皆様、特にUberのgo.uber.org/automaxprocsのメンテナの方々からのフィードバックに感謝いたします。このパッケージは長年にわたってユーザーに同様の機能を提供してきました。

次の記事: 時間(およびその他の非同期性)のテスト
前の記事: Go 1.25がリリースされました
ブログインデックス