はじめに
Datadogは20個を超えるバイナリで構成されるほど巨大なサービスです。開発者やオペレーターに多様な観測データを提供するプラットフォームだけあって、初期から実装すべき機能も多くありました。
Python時代:GILという制約
DatadogはもともとPythonで作られていました。しかしPythonには**GIL(Global Interpreter Lock、グローバルインタプリタロック)**という制約があり、1つのプロセス内で同時に実行できるスレッドは常に1つだけです。(これはPythonのメモリ管理方式と深く関わる話なので、別の記事で改めて扱う予定です。)
興味深かったのは、「グローバル」という名前の通り、このロックがマルチコア環境にもそのまま適用されるという点でした。つまりPythonは元々マルチコアを十分に活用できない構造で、そのため初期のDatadogのプログラムもマルチコアを使うために複数プロセスに分かれて動作するしかなかったのです。
Goへ移行した後も、なぜ複数プロセスのままなのか
現在はコードの大部分がGoに移植されているので、「もうプロセスは1つにまとまっているのでは」と思っていましたが、実際には今も複数プロセスに分かれて動作しています。気になって調べてみた理由は、大きく次の3つでした。
1. 権限分離
まず思い浮かんだ理由は権限分離です。たとえばeBPFを使う機能はroot権限が必要なため、他の一般的な機能とは異なる特権が要求されます。
2. 権限を下げても、アドレス空間は共有されたまま
調べてみると、1つのプロセスの中でも権限を分けて処理する方法はあります。高い権限で起動して必要な特権作業を先に行い、その後は権限を下げて残りの作業を続ける方式(privilege dropping)です。
ただしこの場合でも、結局は同じプロセス・同じアドレス空間を共有することになります。MMUのようなメモリ保護機構によって空間そのものが分離されるわけではないため、権限を下げたとしても危険は残ります。
3. 障害分離と独立したライフサイクル
これに加えて、機能ごとに障害を分離し、それぞれが独立したライフサイクルを持てるという点も、プロセスを分ける重要な理由でした。
まとめ
結局、プロセスを複数に分けるという大枠自体はPython時代と同じです。ただし変わった点があるとすれば、Goに移行したことでスレッドでマルチコアを活用できるようになったため、以前のようにコアを使うためだけに無理にプロセスを分ける必要はなくなったということです。代わりに、機能単位で必要なプロセスだけをまとめて構成できるようになりました。つまりプロセスが複数あるという結果は同じでも、その数と境界を決める基準が「コアの数」から「機能単位(権限・障害分離・ライフサイクル)」へと変わったわけです。
おわりに
当たり前の結論かもしれませんが、AIにDatadogの言語変遷について尋ねたとき、PythonからGoに移行したという答えと共に「今はプロセスを複数起動する必要はない」というニュアンスの内容が出てきたので、気になって自分で調べてまとめてみました。