前回は、分散シミュレーションにおける時刻同期の課題と箱庭コンダクターの役割としての時刻同期について説明しました。
今回は、箱庭コンダクターの内部設計について解説したいと思います。
数年前に作成した箱庭コンダクターってのがありました。
実は、従来の箱庭コンダクターはオープンソースとして公開しており、
Rust で実装していました。
マシン間の RPC 通信には gRPC を採用し、
シミュレーションの実行・停止・リセットといった制御を行っていました。
一方で、PDU データの転送については、
以前に紹介した 箱庭 PDU エンドポイントのような抽象化レイヤを用いず、
MQTT と UDP を直接実装する形を取っていました。
今から振り返ると、
通信方式や役割分担がコードに密結合した、
ややベタな実装だったと感じています。
設計を進めるうちに、
「これは一度きちんと整理し直した方がよいな」
と思うようになりました。
また、当時の実装は
2 ノード構成を前提とした設計であり、
ノード数の拡張や構成の一般化については、
十分に考慮できていませんでした。
新設計の箱庭コンダクタとは?
こうした反省点を踏まえ、
今回の新設計では、
- ノード数に依存しない構成
- 通信方式と役割の明確な分離
- 時刻同期とデータ転送の責務分離
を前提に、箱庭コンダクターの設計を見直すことにしました。
それで、新設計では、
箱庭 PDU-Endpoint、箱庭 PDU-Bridge、箱庭 PDU-RPC
を利用する構成にしました。
これにより、
・通信プロトコルの隠蔽
・転送方式の選択性
・gRPC に依存しない、PDU を中心とした軽量 RPC
を実現しています。
特に重要なのは、
「通信方式」や「実行形態」といった違いを、
コードではなく コンフィグ設定だけで切り替えられる 点です。
コンダクターの特性に応じて、
Server / Client 構成を変えたり、
通信経路を変えたりしても、
再コンパイルは不要です。
設計としては、
「何をやるか」はコードに、
「どうつなぐか」はコンフィグに、
という責務分離を徹底しています。
個人的には、ここが今回の設計で
いちばん気に入っているポイントです。
箱庭コンダクターの内部設計

そして、上図がこの設計です。
図の左側が、箱庭コア(hakoniwa-core-pro)です。
ここでは、シミュレーション資産(アセット)が実行されます。
中央にある Conductor は、
時刻同期と実行制御の中核を担っています。
構成に応じて、Conductor Server / Client に分かれ、
hakoniwa-remote-api を介して相互に通信します。
その下にある hakoniwa-pdu-rpc は、
PDU をベースにした軽量な RPC 層です。
従来の gRPC とは異なり、
箱庭のデータモデル(PDU)をそのまま扱える点が特徴です。
右側には、通信経路を担当する
hakoniwa-pdu-endpoint と
hakoniwa-pdu-bridge-core があります。
ここで、MQTT や UDP といった
具体的な転送方式が選択されますが、
上位の Conductor や Core からは、
それらの違いは見えません。
このように、
・時刻制御
・RPC
・データ転送
をレイヤとして分離することで、
構成変更に強い箱庭コンダクターを実現しています。
マルチノード構成における箱庭コンダクターの構成
マルチノード構成では、
箱庭コンダクターは master / slave 構成を取ります。
master ノード側には、
各 slave ノードに存在する コンダクタークライアントと
1 対 1 で対応するサーバープロセスを複数立ち上げます。
つまり、slave ノードが増えると、
それに対応する形で master 側にも
コンダクターサーバープロセスが増える構成になります。

なぜ server プロセスを分離するのか
この構成を取っている理由のひとつは、
コンダクター実装をできるだけシンプルに保ちたいからです。
1つのサーバープロセスが
複数のクライアントをスレッドでさばく構成も考えられますが、
その場合、
- スレッド間の状態共有
- ロック設計
- デッドロックや競合の考慮
といった複雑さが一気に増えます。
それよりも、
クライアント・ノードごとにプロセスを分けることで、
- 各コンダクターサーバは単純な 1 対 1 の関係を保てる
- 実装が直線的になり、状態遷移を追いやすい
- 障害の影響範囲をプロセス単位で切り分けられる
といったメリットがあります。
箱庭コアは「プロセス単位」で動作する
もう一つ重要な前提として、
箱庭のコア機能自体が プロセス単位で動作する設計になっています。
- 箱庭アセットはプロセスとして実行される
- 仮想時間の進行もプロセス境界を前提にしている
- プロセスが箱庭の最小実行単位になっている
この前提があるため、
コンダクターについても、
スレッドで細かく分割するより、
プロセスとして明示的に分けた方が、
箱庭全体のモデルと自然に一致する
という設計判断になります。
最後に、箱庭エンジニアとしてひとこと。
ぼくは、この箱庭コンダクターというものを作れて、
本当にしあわせなエンジニアだなと思いました。
なぜなら、これを
「誰かのために作っている」
という感覚が、ほとんどなかったからです。
これは、
なぜ自分がこの課題に向き合っているのか。
そして、このとても難解な問題を、
どう解こうとしているのか。
それを、
ソフトウェアアーキテクチャを学んできた
一人のエンジニアとして、
問題を切り分け、
分からない点を明らかにし、
構造に落とし、
実装し、
実際に動く形に持っていく。
その過程そのものを、そのまま形にしたものです。
こんな仕事、普通はなかなかできません。
本当に、得難い時間でした。
さて、次回は――
昨年気づき、実装まではしたものの、
箱庭の基盤に落とし込めていなかった
「Runtime Delegation」について、
あらためて解説したいと思います。
つづく。

コメントを残す