🙌

組み込みLinuxでのモジュラーモノリス再設計 ― 技術的負債と向き合った話

に公開

長年拡張を続けてきた組み込みLinuxでのIoTゲートウェイが、いつしか誰も触れられない実行モノリスになっていました。技術的負債を抱えた自作アーキテクチャを、自分の手で再設計した記録です。
モジュラーモノリス化を軸に、組み込みLinux環境でも信頼性・保守性を両立させた取り組みを紹介します。

背景:膨れあがった実行モノリス

私たちの組み込みLinuxで動作するIoTゲートウェイは、長年の拡張でベテランしか触れない巨大な実装になっていました。
構成上、機能ごとに複数プロセスを立ち上げていてはいたものの、実際のロジックはメインプロセスに集中した実行モノリスとなっていました。さらには、ハードウェアとの依存関係があり、機能間連携が複雑で、1つの修正が全体に影響する。さらに、同期的なAPI制御にもなっていて、末端の、ファームウェア、ハードウェア環境がないと正確なテストできない。半ば技術的負債になりかけていました。

「このままでは、このプロダクトは成長できない。それどころか、維持すらできない」

危機感を抱いたのも当然でした。なぜなら、このアーキテクチャを構築したのは、他ならぬ自分自身だったからです。

刷新のきっかけ

アーキテクチャを設計した当時、私は社内で唯一、基本設計から実装、テストまでを一貫して担当していました。当時の私は「自分にとって最も効率的な形こそが最良の設計」だと考え、結果として、短期間で複数案件をこなせる効率的な仕組みを作り上げました。しかし、個人最適が故に、チーム全体での最適化や組織のスケーラビリティを考慮できていなかったのです。

チームや組織のために、ひいては、これからを担うジュニアエンジニアのために、もう一度アーキテクチャを見直し、作り上げたい。自分が招いた問題は、自分で解決すべきだ。

これが、アーキテクチャ刷新の原動力でした。


メンバーアサイン

刷新チームには以下の3名をアサインしました。

  • IoTゲートウェイの実装に精通したメンバー
  • 情報システム開発の知見を持つメンバー
  • 将来を担うジュニアエンジニア

課題や危機感を事前に共有し、上司や部門長の理解も得られたことで、理想的なチーム構成となりました。この人選が、結果として成功の大きな要因の一つになりました。


設計方針:モジュラーモノリス構成へ

別案件で組み込みLinux上でGoが動作することを確認していたため、情報システム側のアーキテクチャを参考にしつつ、議論を重ねて設計を開始しました。

従来の巨大なモノリス構成は明確に限界を迎えていましたが、組み込み環境ではマイクロサービスを採用するにはリソース的に難しい。メンバーで議論を重ねた結果、採用したのが、モジュラーモノリス構成です。共通機能・ハードウェア依存・顧客要件などの観点から要件を整理し、各機能を分離した上でプロセスを再構成しました。

さらに、情報システム開発で採用していたO11y基盤や自動起動機構を導入し、信頼性と可観測性の向上を図りました。

機能分割

分類観点 考え方・方針 具体的な機能例
通信制御・状態管理機能 ハードウェア間の制御やIoTゲートウェイ全体の状態管理を担う 装置全体の監視制御、回線接続制御、通信認証・回線管理など
ハードウェア依存 ハードウェアごとに専用のサービスを配置して、ハードウェアごとの個別制御手順や制約を隠蔽する。 レジスタ/BRAM、ハードウェア固有シーケンス
上位装置連携 上位システムやクラウド連携、UIなど、通信仕様が固定的な部分を独立モジュールとして構成する。 MQTT/HTTPクライアント、CMOS/RS-422、タブレット
顧客依存要件 顧客固有のカスタマイズやデータ形式を独立したプロセスまたは設定で切り替え可能にする。 プロトコル変換、接点信号変換
共通機能 各モジュール間で共通して利用される基盤的な処理をフレームワーク化して、重複を避ける。 ログ収集、設定管理、O11y、自動起動

決定事項の一部

Language: Go,C(Kernelモジュールなど)
SystemArchitecture: モジュラーモノリス(機能単位でモジュール分割)
SoftwareArchitecture: レイヤードアーキテクチャ/DDD
Communication: HTTP/HTTPS REST + PubSub(Redisプロトコル) 
Data: 軽量キャッシュ(Redisプロトコル)  
Monitoring: Prometheus + Grafana + 各種Exporter

ちなみにこの時、サービス内部についても一般的なシーケンス制御モデルから、レイヤードアーキテクチャとDDDへと変えましたが、本書では割愛します。

設計で重視したポイント

1. サービス間APIの非同期化

組み込み制御の基本形は、どのように安全に制御するかです。
その結果、割り込みを除く全ての制御系が同期系(ブロッキング)になることも少なくありません。

従来もすべての制御系が同期的(ブロッキング)に設計されており、ハードウェア実機がなければテストが不可能な状態でした。そこで、ハードウェア制御を除くすべてを非同期化することで、ホスト環境上でのシミュレーションやユニットテストが可能になりました。その結果、ハードウェアの制御以外については、ホスト環境での評価ができるようになりました。

2. 電源イベントとプロセス起動の分離

組み込みシステムでは「プロセス起動=電源ON」「KILL=電源OFF」として制御するケースが多く、異常終了時に再起動が誤動作を引き起こすリスクがありました。
そこで、電源ON/OFFイベントを管理する専用サービスを構築し、プロセス制御と電源制御を切り離す設計としました。これにより、再起動時の誤動作を防止できました。

最終評価で起きた問題

CPU超過

全機能を搭載した状態での評価中、UI表示を担うタブレットへのステータス通知処理においてCPU超過が発生しました。送受信状態に関する通知頻度が高くキャッシュ更新が過剰だったため、高頻度更新部分を内蔵キャッシュへ移行することで、設計上のスループット限界内に収めました。

ジッタの発生

高負荷時、ハードウェアアクセス系サービスでGC起因のジッタが発生。
各プロセスのメモリ配分見直しと、set_scheduler による優先度制御で軽減しましたが、恒久対策とは言えませんでした。この経験を通じて、組み込み領域におけるGo採用の難しさを痛感し、今後は部分的にRustやZigの導入検討も視野に入れています。

まとめ:個人最適からチーム最適へ

今回の刷新は、単なる技術改善ではなく、設計思想そのものの転換でもありました。
かつての「自分が書きやすいコード」から、「誰もが安全に拡張できるアーキテクチャ」へ。
技術的負債を返済しながら、チーム全体でプロダクトの成長を支える仕組みを作り直したことが、最大の成果です。

アーキテクチャ刷新は一度きりでは終わりません。
設計もプロダクトも“生き物”である以上、継続的な改善が真の価値を生みます。
そのためにも、技術選定・チーム構成・運用改善をセットで考えることが、エンジニアリング組織の成熟には欠かせないと感じました。

Discussion