マイクロサービス・アーキテクチャのメリット/デメリットまとめ


先日、Microservices Meetup vol.2という勉強会に参加してまいりまして、マイクロサービス・アーキテクチャ(以下、MSA)の教科書的なメリット/デメリットをまとめておくと有益ではないかと思うようになりました。

というわけで、本エントリーで、マイクロサービス・アーキテクチャ(以下、MSA)を従来のモノリシック・アーキテクチャと比較した際の、メリット/デメリットを書きたいと思います。

Sam Newmanのマイクロサービス本1の内容をベースに、個人的なアレンジを加えた内容となっています。

より実践的な話については、以下の@mosa_siruさんの資料などを参照されると良いかと思います。

メリット

P1. サービス毎の技術特異性

  • P1-1. サービスの機能に合わせて最適な技術を採用できる
  • P1-2. 新しい技術を取り入れ易い

解説

各々のサービスは、それぞれが公開したインターフェースを呼び合うだけで、それ以外の依存関係は原則持ちません。このため、各サービスを異なる技術スタックで構築することができます。公開しているインターフェース仕様を守っている限り、あるサービスはNode.js/NoSQL、またあるサービスはJava/RDBといったように、全く異なる技術スタックを採用できます。

これにより、サービスの機能に合わせて、最適な技術を選択できます。また、利用する技術を変更しても当該サービス以外に影響を与えないので、新しい技術を取り入れやすいというメリットもあります。

P2. 耐障害性

  • P2-1. 障害の影響を局所化できる

解説

サービス同士が独立しているので、障害が発生してもそれがシステム全体に連鎖しない(ように設計しやすい)と言われています。

P3. スケーリングの効率化

  • P3-1. リソースを効率良く利用できる
  • P3-2. スケーリングの処理自体が高速になる

解説

モノリシックなアーキテクチャでは、システム全体をまるごとスケーリングすることになりますが、MSAでは必要なサービスだけをスケーリングすることができます。リソースの無駄が少なくなりますし、スケーリング処理のスピードも全体をスケールするより高速です。

P4. デプロイの容易性

  • P4-1. 新規機能やバグ修正を迅速にシステムに反映できる
  • P4-2. デプロイ/ロールバック処理自体が高速になる

解説

モノリシックなアーキテクチャでは、いかに軽微な変更であっても、それを反映するために全体を再デプロイする必要がありますが、MSAではサービス単位でそれを行うことができます。デプロイの作業負荷が軽くなる分、高頻度で更新できるようになるので、新規機能やバグ修正を迅速に運用環境に反映できるようになります。

デプロイの処理自体も高速です。当然、ロールバックが必要なケースでも同じことが言えます(こっちの方がメリットとしては大きいかも)。

P5. レガシーシステムのリフレッシュ2

  • P5-1. レガシーシステムが更改し易い

解説

レガシーシステムの中にMSAで構築されたものはほとんどないかと思いますが、将来的な話ということで。いわゆる塩漬けシステムになってしまっても、サービス単位で更改できるで、だいぶやりやすいはず。

デメリット

MSA自体のデメリットと、MSA的な設計がうまくできていなかったことによる不利益が混同されがちだと思うのですが、ここでは主に前者について書いています。

C1. パフォーマンス

  • C1-1. サーバーサイドのパフォーマンスが悪くなりがち

解説

クライアントから見るとひとつの機能を呼び出しているだけでも、多くの場合、サーバー側ではサービス同士の連鎖的なAPI呼出しが発生することになります。このため、パフォーマンスの劣化が起きやすくなります。

対策として、非同期でAPIを呼出すようにして全体の処理の完了を待たずにクライアントに応答を返す方法が代表的です。しかし、非同期のサービス連携は、実装や障害解析の難度が比較的高いので注意が必要です。

C2. トランザクションまわり

  • C2-1. データの一貫性の保証が難しい

解説

基本的にサービス毎にテータストアが分かれるので、データの一貫性を保証することは難しくなります。サービス同士を非同期に連携している場合はなおさらです。

一般的には、一貫性を常時保証されていることをあきらめて、結果整合性が確保されていればよいと割り切る設計を取ることが多いようです。要件によっては結果整合性で十分ということは多いと思われますが、いずれにしてもどういった設計にするかを都度検討する必要は出てきます。

モノリシックなアーキテクチャでは、DBがひとつで、そこにクエリを投げていればトランザクションが保証されているということも多かったと思われますが、それに比べると難度が高いし手間もかかります。

C3. 運用管理の負荷

  • C3-1. 管理業務のオペレーションの負荷が増す
  • C3-2. パフォーマンスや障害状況の監視の負荷が増す

解説

1つのシステムを複数のサービスに分けるということは、管理対象が増加することにもなります。このため、運用管理は複雑化する傾向があります。

起動、停止、スケーリングなどをサービス毎に実施できるのはMSAのメリットでもありますが、サービスごとに運用管理業務が発生し、手間が増えるということにも繋がります。同様に、パフォーマンスメトリックや障害状況の監視もサービス毎に行なう必要があります。一箇所で統合的に監視する仕組みを作ることはできますが、それにもやはりコストが発生します。

C4. 設計/実装の高難度化

  • C4-1. アーキテクチャ設計の難度が高い
  • C4-2. 非同期でサービスを連携させる場合、それ自体実装の難度が高い

解説

サービスを適切に分割するスキルが必要です。適切に分割してサービスの依存性をうまく排除しないと、MSAのメリットを享受できなくなってしまいます。一方でサービスを細分化し過ぎると、その分システム全体が複雑化してしまいます。

サービスの分け方に関して確実なセオリーはありませんが、MSA本では、ドメイン駆動設計における「境界づけられたコンテキスト」で分けるべき、と解説されています。詳細はドメイン駆動設計の世界の話なのでそちらに譲りますが、少なくとも、設計者が対象システムのビジネスドメインも良く理解している必要はありそうです。

非同期のサービス連携はそれ自体実装の難度が高いです。これを自信をもって作れるエンジニアはあまり多くないのではないでしょうか。

C5. 障害解析の高難度化

  • C5-1. 複数サービスを跨る障害解析は手間とスキルが必要

解説

障害が発生したら、原因箇所がどのサービスかを特定する作業が必要です。複数サービスのログを見たり、デバッグ(ができるなら)して切り分け作業を行ったりする必要があります。また、MSAの考え方として、一連のAPI呼出しの流れを誰かが把握しているわけではないので、切り分けするにも取っ掛かりが得られないかも知れません。

ログについては、一括収集する仕組みを作ったり、一連の呼出し連鎖で共通の識別子が記録されるようにするなどして、後で解析しやすくすることが重要と言われています。

C6. 技術スタックのバリエーションが増えることによるオーバーヘッド

  • C6-1. サービス間で人を移動させる際に学習のオーバーヘッドが発生する

解説

サービス毎に異なる技術スタックを採用していると、サービス間で人を異動させる際、新たに学習するオーバーヘッドが発生します。

また、障害解析でサービスを跨るデバッグをするようなとき、異なる技術スタックを扱えないと作業がままならなくなることが予想されます。

このため、採用選択に制限を設ける企業もあるようです。ただ、これはMSAのメリットと真逆の施策なので、適切なラインを見極めて制限することが重要となります。

Netflixでは、サーバーサイドの言語について、"JVM言語を使う"という制限を設けていると聞いたことがあります。これはいいところを付いている気がします。メモリ管理など、比較的下位レイヤーの特性を共通化しつつ、一定の言語のバリエーションを持たせられます。

最後に

とりあえず以上ですが、他にも気づいたら追記していきます。