マイクロサービスはもう十分

要旨

モノリスとして管理するには複雑すぎるというシステムでない限り、マイクロサービスは検討さえしなくていい。ソフトウェアシステムの大多数は、単一のモノリシックアプリケーションとして構築されるべきである。そのモノリス内のモジュール性が良好になるよう注意を払う必要はあるが、別個のサービスに分けようとしてはいけない。

– Martin Fowler

明確に構造化されたモノリスを構築できない時、なぜマイクロサービスがその答えだと思うのか。

Simon Brown

始めに

マイクロサービスの利点と欠点については色々書かれていますが、残念ながら私には、成長段階のスタートアップ業界ではいまだカーゴカルト風に追い求められているように思えます。そこで、Martin Fowlerの記事「Microservice Premium」(マイクロサービスプレミアム)をあえて書き直すことにはなりますが、いくつかの見解を書き上げておくと良いのではないかと考えました。そうすれば、このテーマが持ち上がった時にクライアントに送ることができ、私が見てきたようなミスをいくらかでも避けられるかもしれないからです。ネットで見つけたいわゆる”ベストプラクティス”の記事に基づいて、そのアーキテクチャや技術への道を選択するというミスは、コストが高くつきます。そのような企業を1つでも減らすことができるなら、この記事を書いた価値があるでしょう。

背景

マイクロサービスは(残念ながら)現在でも大きな潮流であり、”旬の”ハイテクバズワードです。そのアプローチはずいぶん前からありますが(サービス指向アーキテクチャをご存じですか)、私が目にする企業の大部分にとって、マイクロサービスは単なる時間の無駄や混乱の元だけではありません。実のところ状況を悪化させるものなのです。

そう言われてもピンと来ないかもしれません。マイクロサービスに関する大抵の記事では、技術システムの非干渉化や水平拡張性の向上、開発チーム間の依存性除去といった数え切れないほどの利点が褒めたたえられているのですから。UberやAirbnb、Facebook、Twitterのような規模の組織であれば、おそらく全てそのとおりなのでしょう。私が大組織のマイクロサービスへの移行を支援した際、メッセージングシステムや他の技術のセットアップを手伝ったことがありますが、それは驚くほどの拡張性を与えるものでした。しかし、成長段階のスタートアップにとっては、その全ての技術やマイクロサービスはめったに必要がないのです。

Netflixではないのだから、彼らをまねるのはやめよう。

–Russ Miles

これはRuss Milesが記事「8 ways to lose at microservices adoption」(マイクロサービスの採用で失敗する8つの方法)で述べている1点目で、私が絶えず目にしていることです。成長段階のスタートアップは、そうした企業の”ベストプラクティス”をまねようとすることがよくありますが、多くの場合は失敗します。ベストプラクティスは背景に依存します。Facebookのような企業にとってのベストプラクティスは、エンジニアリングチームの人数が合計100人以下という規模のスタートアップにとってのベストプラクティスになるとは限らないのです。

ハイテク大企業より規模が小さな組織であっても、マイクロサービスアーキテクチャを指向することで最終的にはいくらかの恩恵を得られるかもしれません。しかし、成長段階のスタートアップがマイクロサービスに全面移行することは、関係する技術担当者にとって大打撃になるはずです。

なぜマイクロサービスを採用するのか

成長段階のスタートアップでは大抵、マイクロサービスに移行する主な動機は、そうすることで開発チーム間の依存性を除去したり、システムのトラフィック負荷処理能力(すなわち拡張性)を高めたりしたいからです。不満や兆候としてよく持ち上がるのは、マージの競合、アプリケーションの一部が部分的実装機能を使う準備ができていないことによるデプロイ時のバグ、そして水平拡張性に関することです。個別に分析していきましょう。

依存性

初期段階のスタートアップでは、開発チームも技術チームも小規模です。互いの領域を侵さずにうまく協力することができ、実装も全体的になかなかスピーディーで、とにかく順調です。

スタートアップが成長するにつれ、開発チームもコードベースも大きくなり、程なく複数のチームが同じコードベースで仕事をするようになります。これらのチームのメンバーは大抵、以前の段階でそのスタートアップに関わっていた人が大部分です。多くの初期段階のスタートアップは、多くのジュニア開発者にとって最初の仕事であるため、チームとコードベースの規模が大きくなるにつれてより効果的なコミュニケーションが必要になるということを理解できていません。限られた経験しかない技術担当者は往々にして、人の問題に対して技術的な解決策を追い求め、開発チーム間の依存性あるいは結合度を下げるにはマイクロサービスが必要だと判断するのです。

実際には、人に関わる問題は、より効果的なコミュニケーションを取ることで対処する必要があります。スタートアップに複数の開発チームがある場合、協調を保ち全員の仕事について情報を得られる環境が必要です。協力しなければならないのです。ソフトウェア開発は、この規模のどの組織においても、社会的な活動です。チーム間のコミュニケーションや情報共有が少なければ、マイクロサービスの有無にかかわらず、同じ依存性の問題が生じるでしょう。しかし、マイクロサービスがある場合は、それに付随する様々な負の技術的問題をも抱えることになるのです。

問題に対する技術的解決策としてコードのモジュール性を保つことで、ソフトウェア開発に伴うチーム間の依存性を軽減できるのは事実ですが、それでも、チームの規模が大きくなるにしたがってコミュニケーションの要素を拡大、向上させなければなりません。

非干渉化分散を混同してはいけません。非干渉化は、明確に定義されたモジュールで構成され明確に定義されたインターフェースを有するモノリスを持つことで実現できますし、そうするべきです。明確なインターフェースを有するモジュールを持つことで実現できる非干渉化の恩恵を得るがためにアプリケーションを別個のサービスとして分散する必要はないのです。

部分的機能実装

この点は多くの場合フィーチャートグルで対処されますが、いずれにせよマイクロサービスをうまく実装するには精通しておくべきであろう事柄です。特にラピッドデプロイメント(詳しくは後述)に着手する際には、特定のプラットフォームに対する準備ができていない、あるいはフロントエンドの実装は済んでいるもののそのバックエンドは済んでいないといった機能の一部をデプロイする必要があるかもしれません。企業が成長し、デプロイと運用システムがより自動化された複雑なものとなるにしたがって、フィーチャートグルを使うこと、しかも賢明に使うことが重要になってきます。

水平拡張性

この点は、拡張性の一形態を実現するために同じマイクロサービスの複数のコピーをデプロイできるという点で、一定のメリットがあります。しかし、マイクロサービスの採用が早すぎた企業は大抵、全てのマイクロサービスの基盤として同じストレージサブシステム(最も多いのは1つのデータベース)を使うでしょう。それはつまり、アプリケーションとして真の水平拡張性は実現しておらず、サービスとしてのみということです。もしその方法を拡張性実現のために取っているのであれば、ロードバランサの背後でモノリスのより多くのコピーを単にデプロイしてはどうでしょうか。同じ目的を、より複雑でない形で実現できるでしょう。それだけでなく、水平拡張性に伴う複雑さは最後の手段としてのみ発生するべきです。まず取り組む必要があるのは、アプリケーションのパフォーマンスを向上させるための合理的な段階を踏むことです。多くの場合、基本的な事柄に取り組むだけでもパフォーマンスを元のシステムより何百倍も高速にできますし、アプリケーションをサポートするサービスを賢く使えるようにもなります。その例は、Redisのパフォーマンス優先順位付けに関する記事でご紹介しています。

マイクロサービスの準備ができているか

この問いは、どのようなアーキテクチャにするべきか検討する際に考慮さえされないことが多いのですが、考えるべき問題です。よくあるのは、企業のシニア技術者が、不満あるいは開発者やビジネスの弱点を単純に特定して、マイクロサービスアーキテクチャがそのような問題に対処できると主張する資料をネットで見つけるパターンです。しかし、その主張には多くのただし書きがあるのです。マイクロサービスには、他の多くの事柄と同様、正の効果と負の効果があります。組織が十分成熟していて、技術も備わっているならば、マイクロサービス採用に伴う難題は最小限にとどめることができ、正の効果がいっそう際立ってきます。では、マイクロサービスの準備ができているとはどういう意味でしょうか。Martin Fowlerは数年前に「Microservice Prerequisites」(マイクロサービスの前提条件)という記事を書きましたが、私の経験上、成長段階のスタートアップの大部分はこれを完全に無視しています。Martinの前提条件は優れた出発点ですので、検討してみましょう。

  1. ラピッドプロビジョニング
  2. ベーシックモニタリング
  3. ラピッドデプロイメント

私が何十もの成長段階のスタートアップを見てきた経験から言えるのは、そのほとんどが、上記条件の全てはおろか1つも満たしていないということです。もしあなたの技術チームに全ての現行システムに対して迅速にプロビジョニング、デプロイ、モニタリングする能力がないなら、マイクロサービスへの移行を検討する前にその能力を獲得しなくてはなりません。各条件をもう少し詳しく見ていきましょう。

ラピッドプロビジョニング

仮想であれそれ以外であれ、あなたの組織で新サービスをセットアップできる開発チーム全体がたった1人か数人で構成されているなら、マイクロサービスの準備はできていません。外部の援助なしでインフラに対するプロビジョニングとサービスデプロイができる複数のメンバーが、各チームに必要となるでしょう。たとえ”DevOpsチーム”があるとしても、DevOpsを実践しているわけでは断じてないということを忘れないでください。開発者はインフラを含む、アプリケーションに関する全ての管理に関わるべきです。

同様に、あなたの現行アーキテクチャの基盤となるインフラが、スケールアップやスケールダウンがしやすくチームの様々なメンバーが管理できるような柔軟なものでないなら、マイクロサービスに向かう前にその問題に対処しなければなりません。もちろん、ベアメタルマシン上でマイクロサービスを稼働させ、より低コストでより優れたパフォーマンスを得られるかもしれませんが、それでもサービスの運用とデプロイに柔軟性を持つことができる必要があるのです。

ベーシックモニタリング

もしあなたのモノリスのシステムとアプリケーションのパフォーマンスをモニタリングしていないなら、マイクロサービスを採用すれば悲惨な時を過ごすことになるでしょう。システムのパフォーマンスを理解するには、システムレベルの指標(CPU、RAMなど)、アプリケーションレベルの指標(1エンドポイント当たりの要求レイテンシ、1エンドポイント当たりのエラー数など)、そしてビジネスレベルの指標(1秒当たりのトランザクション数、1秒当たりの収益など)に精通している必要があるのです。パフォーマンスに関して言えば、モノリスは複雑ですが、マイクロサービスの集合の方がはるかに複雑で、トラブルシューティングはおろか理解することさえ大変です。モノリスの一部をマイクロサービスに切り分ける前に、Prometheusなどのツールをセットアップして必要な全てのインストルメンテーションをモノリスに追加しましょう。

ラピッドデプロイメント

あなたのモノリスで優れた継続的インテグレーションおよびデプロイのプロセスとシステムが整っていないなら、マイクロサービスの統合とデプロイを管理しようとするのはほぼ不可能でしょう。例えば、10のチームと100のサービスがあり、その全てで統合テストとデプロイを手動で行う必要があるとしましょう。そして、同じ手動の作業が必要な、たった1つのモノリスがある状況も想像してください。100のサービスではトラブルが起こるパターンは何通りあるでしょうか。1つのモノリスの場合はどうでしょう? この前提条件は、マイクロサービスのアプローチに伴う複雑さを示す格好な例です。

Fowlerのリストは、Phil Calcadoによって条件がいくつか追加されていますが、真の前提条件というよりはむしろ重要な補足事項のようなものと言えるでしょう。

前提条件を満たしているならどうか

たとえ前提条件を満たしているとしても、あなたのビジネスにとってマイクロサービスのアプローチが本当に理にかなっているかを確かめるため、マイクロサービスの欠点を検討することが重要です。純然たる事実として、技術系担当者の多くは分散コンピューティングの落とし穴がマイクロサービスの世界ではどういうわけか問題でないかのように振る舞いますが、成功させるためにはそれらも全て考慮に入れなくてはなりません。成長段階のスタートアップの大部分にとって、マイクロサービスを避ける理由はあまりにも多いのです。

運用のオーバーヘッドが増える

これはラピッドデプロイメントの前提条件と一部重なりますが、マイクロサービスが多くの場合、あらゆるものをコンテナに入れ(おそらくDockerを使用)、Kubernetesのようなツールでその全てを組織化しようという高尚な目的を伴っていることを考えてみてください。どちらのシステムも多くの点で素晴らしい技術であるものの、大抵の成長段階のスタートアップにとっては混乱の元となる可能性があります。私は、複数のスタートアップがデプロイと組織化にrsyncを使って壮大なレベルにスケールアップする様子を見てきました。また、それよりはるかに多くのスタートアップが非常に複雑な運用ツーリングの泥沼にはまり、顧客のための機能構築に使える貴重な時間を奪われている様子も目にしています。

アプリが遅くなる可能性がある

あなたのモノリスに明確に定義されたAPIを持つ複数のモジュールがあるなら、そのAPIとインタラクトする際のオーバーヘッドはほぼゼロです。これは明らかに、マイクロサービスに当てはまらないケースです。というのも、マイクロサービスは別のマシン上で稼働している場合が多く、サービスとの間にネットワークホップが生じるからです。このため、システム全体の速度がかなり落ちる可能性があるのです。要求を完了させるために複数の他のサービスと同期的に通信する必要のあるサービスを持っている場合、状況はさらに悪化します。私は、1つの要求の処理に10近くのサービスを呼び出す必要のある企業と仕事をしたことがあります。この場合、要求を処理するために各段階でネットワークオーバーヘッドや他の遅延が発生しますが、それらのサービスを全て1つのアーティファクトに、ことによると別モジュールとして、容易に格納し、おそらく何らかの追加の再設計を施して非同期にすることもできたでしょう。そうすれば、インフラのコストを大幅に節約できたのではないでしょうか。

ローカル開発が難しくなる

1つのデータベースなどを基盤とした1つのモノリスを持っているなら、開発プロセス中にアプリケーションをローカルで稼働させることは造作もありません。一方、依存性を含む複数のデータストアがあるような100のサービスを持っているなら、ローカル開発は完全なる悪夢になるかもしれません。このレベルの複雑さは、Dockerコンテナでも救い切れないでしょう。もちろん多少は助けになるとしても、やはり依存性の問題にはどうにか対処しなくてはなりません。マイクロサービスでは各サービスが最初から独立していることになっていますので、理論上はその要件をクリアしています。しかし、成長段階のスタートアップにとって、それはほとんど当てはまりません。新しい機能を適切に開発、テストするためには通常、全て(あるいはほぼ全て)のサービスをローカルマシン上で稼働させる必要があります。この複雑さは極めて無駄が多いのです。

スケールするのが難しくなる可能性がある

モノリスをスケールする最も簡単な方法は、ロードバランサの背後でモノリスのより多くのコピーを単純にデプロイすることです。システムが受信するトラフィックは増えるとしても、これはスケールアップの極めて単純な方法であり、運用の観点で見れば加わる複雑さが最小限で済みます。システムがElastic Beanstalkのようなサービス上で長く稼働すればするほど、好都合です。というのも、あなたとチームがデプロイ工程と格闘する代わりに、顧客のために実際に何かを構築する作業ができるからです。ラピッドデプロイメントの前提条件にあるように、適切なCI/CDシステムを持つことでその労力の一部は緩和できますが、マイクロサービスの世界では状況がはるかに複雑となり、その複雑さは多くの場合、価値以上に厄介なものなのです。

では何をするべきか

あなたが成長段階のスタートアップにいてアーキテクチャに何らかの変更が必要であり、マイクロサービスがその答えに思えてもそうでないなら、するべきことは何でしょうか。

Fowlerの前提条件は技術面でのちょっとした能力成熟度モデルである点に注目することは重要であり、もちろんFowlerは成熟度モデルというテーマについて自ら記事を書いています。もしこれが企業の理にかなうなら、彼の前提条件を活用して、マイクロサービスへの移行準備として他の中間段階を踏むことができます。Fowlerの言葉を引用しましょう。

ここで極めて重要な点は、成熟度モデル評価の真の成果は、自分がどのレベルにあるかではなく、向上するために取り組む必要のある事柄のリストを知ることだ。現在のレベルの把握は、次に習得すべきスキルのリストを判断するための単なる中間作業にすぎない。

では、向上するための事柄をどうやって知り、そこに達するためにどんな道を取ればいいのでしょうか。シンプルで一般的な段階がいくつかあります。最初の2つだけでチームをマイクロサービスに向かわせる問題の多くが概して解決され、その際に様々な複雑さも伴いません。

  1. アプリケーションをクリーンアップする。優れた自動テストがあることと、全てのライブラリ、フレームワーク、言語について現行バージョンを使っていることを確かめる。
  2. アプリケーションをリファクタリングして、クリアなAPIを持つクリアなモジュールにする。コードの一部がモジュールと直接通信することのないようにする。インタラクションは全て、モジュールが提供するAPIを介して行う。
  3. アプリケーション内のモジュールを1つ選び、同じホスト上の独自のアプリケーションとして分割する。これで、全く別個のマイクロサービスを持つ有用性がいくらか分かってくるが、運用上の厄介な問題はあまり伴わない。しかし、同じホスト上ではあっても、2つの異なるコンポーネント間の通信を扱う必要はやはりある。その事実のおかげで、マイクロサービスアーキテクチャのような完全な分散システムにおけるネットワーク分割と可用性に付随する複雑さの一部を無視することができる。
  4. その切り離したモジュールを別ホストのシステムに配置する。今やネットワークを介した通信にまつわる問題に対処しなければならないが、2つのシステム間の結合度はここまでの過程で少し下げることができている。
  5. 可能であればデータストレージシステムをリファクタリングし、他のホスト上のそのモジュールが今やそのコンテキスト内のデータストレージに全責任を負うようにする。

比較的大きな規模においても、私が見てきたほぼ全ての企業が、実際には最初の2つの段階を踏むだけで満足な成果を得ています。それでうまくいくのであれば、残りの段階は必ずしも当初考えていたほど重要ではありません。さらに良いことには、途中のどこかの地点で立ち止まることにしたとしても、システムはメンテナンス可能であり、おそらく開始時よりいい状態になっているでしょう。

終わりに

以上の見解はどれも独特なものであるとも、自分で思いついた考えであるとも言えません。本記事は、同じ状況に遭遇したであろう人たちから集めた記録や見解を単に要約したものです。私よりもっと長い間活動している多くの人がこのようなテーマで記事を書いており、Sander Makのモジュールとマイクロサービスに関する記事のようにずっと明快なものもあります。いずれにせよ、これらの教訓は重要であり、アーキテクチャを今後どうするべきか検討している企業にとって役立てば幸いです。全ての選択肢を入念に検討し、マイクロサービスがあなたの組織にとって適切な道であることを確かめてください。

少なくとも、前のセクションに挙げた最初の2つの段階から始め、それが完了したあとで、マイクロサービスがあなたの組織にとって正しい方向であるかを再検討してください。おそらく、かつて抱えていた問題の多くはあっさりなくなっていることでしょう。