適応度関数
複数のマイクロサービスが連携して動くような分散システム全体の品質管理において、
適応度関数は極めて強力でモダンなアプローチです。
なぜ適応度関数が有効なのか?
マイクロサービスアーキテクチャでは、各サービスは独立してデプロイされるため、
全体のアーキテクチャが意図しない方向に「劣化」していくリスクが常に伴います。
例えば、あるサービスが他のサービスを過度に同期的に呼び出すようになり、
全体の耐障害性が低下する
といった事態です。
適応度関数は、このようなアーキテクチャ上の重要な品質特性(パフォーマンス、信頼性、保守性など)を継続的かつ自動で検証するための仕組みです。
これにより、アーキテクトやチームは、アーキテクチャが健全な状態を保っているかを客観的な指標で常に監視し、劣化を早期に検知して修正できるんです。
全体の品質を測る適応度関数の実装例
全体の品質を管理するために、以下のような様々な観点から適応度関数を定義できます。
ただし、前提として利用者にとって、重要な外部品質は何なのか?
はすでに特定され、かつ重みづけまでされている必要があります。
①信頼性・耐障害性
目的は、特定のサービスの障害が、システム全体に致命的な影響を与えないことを保証することです。
②適応度関数の例
カオスエンジニアリングのツールを使い、定期的に「商品レビューサービス」を意図的に停止させる。
その状態で「商品詳細API」を呼び出し、レスポンスが50xエラーではなく、
レビュー部分が空になった200 OKで返ってくるかをテストする。
失敗すればアラートが発報される。
③パフォーマンス
目的は、サービス間の連携を含めた全体のレスポンスタイムを一定の基準内に保つことです。
適応度関数の例
CI/CDパイプラインの一部として、デプロイ後エンドツーエンドのテストを実行する。
「注文処理」のように複数のサービスをまたぐ操作の95パーセンタイル値が500msを超えた場合、ビルドを失敗させる。
④結合度・依存関係
目的は、サービス間の循環参照や、不適切な密結合が生まれるのを防ぐことです。
適応度関数の例
サービス間のAPIコールをトレース情報から分析する静的解析ツールを毎晩実行する。
「サービスAがBを呼び、BがAを呼び返す」といった循環依存が検知された場合、開発チームに通知する。
⑤セキュリティ
目的は、既知の脆弱性が本番環境にリリースされるのを防ぐことです。
適応度関数の例
各マイクロサービスのビルド時に、コンテナイメージやライブラリの脆弱性スキャンを自動で実行する。
「Critical」レベルの脆弱性が一つでも見つかった場合、デプロイをブロックする。
適応度関数で制約をかけ安全性を担保する
これらの適応度関数をCI/CDパイプラインや監視システムに組み込むことで、各チームが自由に開発・デプロイを進めながらも、システム全体の品質という「ガードレール」から逸脱することを自動的に防ぎ、アーキテクチャの継続的な進化をサポートします。
全体のマクロ適応度関数のみで起きる問題点
しかし、上記のような全体を縛るマクロな適応度関数だけでは、
あるチームの変更が全体のテストを失敗させ、無関係な他のチームのデプロイまでブロックしてしまう
という問題が起こり得ます。
なぜマクロな適応度関数だけではダメなのか?
たとえば、以下の図のように5つのマイクロサービスからなる事業を考えてみましょう。
ある時、サービスEのテストが毎回失敗するとしましょうか。
すると、全体の適応度関数のチェックを通らないために、他のサービスを担当するチーム(ABC)は、どんなに早く開発テストが完了していても、それまで自分たちのプロダクトをリリースできない。
それだけでなく、アーキテクチャ上、疎結合であるにもかかわらず調整コストが発生し、
その密なコミュニケーションパスとアーキテクチャとの乖離による更なる負の循環サイクルに飲み込まれる
という事態にまで発展しかねません。(これって結果、モノリスやん💦)
そのために、特定チームが自分たちのプロダクトコードの変更を恐れたり、
デプロイの機会を失ったりして、「育たなくなる」というリスクが現実のものとなります。
適応度関数の階層化戦略
そこで、以下のように各抽象度に応じて、適応度関数を定義することが重要になってきます。
上記の全体マクロな適応度関数を定義する だけでは、結果的に各種具体のマイクロサービス
が育たなくなるという、どちにしても負の循環に陥るという理由を説明しました。
チームの自律性と全体の整合性という、相反しがちな2つの要求を見事に両立させるためには、この適応度関数を階層定義する戦略が必須です。
1. マクロ適応度関数 (全体のガードレール)
役割
システム全体のアーキテクチャ目標(例:エンドツーエンドのレスポンスタイム、特定のセキュリティ基準)を定義し、逸脱を許さない最終的なガードレールとして機能させます。
実行タイミング
主に結合テスト環境やステージング環境、あるいは本番環境の監視として実行。
2. ミクロ適応度関数 (各チームの自律的な品質保証)
役割
各サービスが「自分たちの責任範囲」で全体の目標にどう貢献するかを定義します。これは、チームが迅速なフィードバックを得て、自律的に動くための生命線です。
実行タイミング
開発者のローカル環境や、各サービスのCI/CDパイプラインの早い段階で実行。
このミクロ適応度関数の実行タイミングは、各々のマイクロサービス間の結合度合いに応じて変わってきます。
非同期であれば、別々に実行できるし、
同期的であれば、まだ蜜結合状態だから順番の前後を考える必要性があります。
具体的な連携例
マクロな目標を各チームのミクロな目標に「予算配分」するイメージです。
ロジックツリーの各階層に分解していくって言ったら伝わるでしょうか?
マクロな目標
「注文完了」までのエンドツーエンドのAPIレスポンスは、99パーセンタイルで800ms未満であること
というように、全体のマクロな目標値を決定したとしましょうか。
これをもとに、ミクロな各種マイクロサービスの目標を以下のように定めます。
ミクロな各種サービス目標
注文サービス:自分のサービスのAPIレスポンスは、200ms未満であること。
在庫サービス:自分のサービスのAPIレスポンスは、150ms未満であること。
決済サービス:自分のサービスのAPIレスポンスは、300ms未満であること。
ロジックツリーにしてみると、こんな感じです。
注意点:※ボトムアップに検算する
この時に、必ず「これらのミクロ目標で上位のマクロ目標が達成できそうか?」
とボトムアップに検算してみてください。
仮に、各種サービス目標を合計した結果、800msにすでに達していたら、
もうその時点で上位のマクロ目標は達成できないことになります。
なぜなら、サービス間には通信があるので、当然レイテンシーがあります。
それを加味しない状態で、800msになっているわけですから無理な目標設計です。
階層化適応度関数がもたらすメリット
このアプローチにより、以下のことが実現します。
自律性の確保
在庫サービスチームは、自分たちの「150ms未満」という契約さえ守っていれば、他のチームの状況に関係なく、自信を持ってデプロイできます。
余計な調整コストも発生させずに済みます。
迅速なフィードバック
開発者は、自分のコードが原因で性能が劣化したのかを、CIの段階で即座に知ることができる。全体のテストが失敗するまで待つ必要はありません。
迅速な責任分界点の明確化
全体の目標(マクロ)が未達になった際も、どのサービスの目標(ミクロ)が未達なのかが明確なため、原因究明が迅速に進みます。
その結果
マイクロサービスの理想である
「チームの自律性を最大限に尊重しつつ、全体のアーキテクチャ整合性を保つ」
という難しい課題を、適応度関数という仕組みで解決する支援となります。
TOCを使った費用対効果の高い品質保証
では、上記の階層化された適応度関数がある状態という土台が揃ったうえで、
具体的にどのように全体の系からなるマイクロサービス群の品質改善計画を立てたらいいでしょうか?
ここで、TOCの出番です!!
適応度関数の設計に制約理論(TOC) の考え方を持ち込むのは費用対効果の高い品質保証を実現するための要諦です。
システム全体のボトルネックを無視して各サービスを個別に最適化(部分最適)しても、
全体の目標数値は向上しません。
それどころか、改善した部分が新たなボトルネックを生み出すことさえあります。
以下のような効率的な品質管理サイクルを意識しましょう。
①制約の特定
まず、マクロな適応度関数(エンドツーエンドのテストなど)を使って、システム全体の現在の制約がどのサービスにあるのかを特定します。
例えば、「在庫確認サービスのレスポンスタイムが、全体の注文処理時間を律速している」といった事実を掴みます。
②制約への集中
特定したボトルネックとなっているサービスに対して、最も厳格なミクロ適応度関数を定義し、開発・改善リソースを集中させます。
ここが、品質保証活動のレバレッジポイントです。
ここで、ボトルネック以外のサービスに対して、余計に品質改善コストを投下したら絶対ダメです。
ボトルネックのサービスに対して、集中的にコストを投下しましょう。
③非制約の従属
制約となっていない他のサービス(非ボトルネック)のミクロ適応度関数は、
「新たなボトルネックにならない」程度の、比較的緩やかな基準で十分です。
これらのサービスを過剰に最適化しても、全体の成果には繋がりません。
貢献する価値
このアプローチにより、開発リソースと品質保証の取り組みが、最も費用対効果の高い、
ボトルネックのサービス一点に集中させられます。
漠然と全てのサービスの品質を上げるのではなく、システムの成果を最も左右する「制約」に的を絞ることで、無駄のない、戦略的な品質管理が実現できるのです。
適応度関数の進化
カオス実験との併用が必要
最後に重大なネタバレをします。
あくまでも適応度関数は、リスク予防の魔法ではありません。
既知なことへの予防線しか適応度関数は対処できない
そのため、未知のことへの対処は、カオス実験を行って浮き彫りにするしかないです。
なぜなら、各マイクロサービス間の相互作用に関するすべてのパターンのテストは不可能だからです。
結合状態で変わってくる
ちなみに今回は深く突っ込みませんでしたが、マイクロサービス間の結合状態によって、
各種ミクロ適応度関数の定義の仕方は変わってきます。
下図のように蜜結合状態であれば、各種ミクロ適応度関数は一旦定義せずに、
全体での適応度関数のまま運用し、疎結合化が進んできたら、個別マイクロサービスごとの
ミクロ適応度関数を定義するという風に、適応度関数自体の運用も心掛けてください。
絶対にガードレールである適応度関数自体が、
各種マイクロサービスの進化をストップさせるということがないように!!