我々はこれまでに、マイクロサービスの採用に関する成功と失敗を取り上げた、実体験に基づく記事を、長年にわたって数多く紹介してきた。SegmentのAlexandra Noonan氏による先日の記事では、同社がモノリスからマイクロサービスに移行し、再びモノリスに戻った経験が語られている。記事の中でNoonan氏は、同社がオリジナルのシンプルなアーキテクチャからマイクロサービスに移行した理由について、次のように説明している。
イベントを取得し、それを分散メッセージキューに転送するAPIがありました。この場合のイベントは、Webあるいはモバイルアプリによって生成され、ユーザとそのアクションに関する情報を格納したJSONオブジェクトです。イベントがキューから取り出されると、ユーザの管理するセッティングがチェックされて、イベントを受信する宛先が決定されます。[...] 続いてそのイベントが、各宛先のAPIに次々と送信されます。開発者はイベントを、唯一のエンドポイントであるSegmentのAPIに送信すればよいので、数十のインテグレーションの構築が必要となる可能性のある場合に比べれば、はるかに便利です。
配信に失敗したイベントはシステムに再度キューされた。つまり、場合によってワーカは、新たに配信したイベントとともに、すべての宛先に対して、配信に失敗したイベントが遅延して再配信される場合も考慮しなければならなかった。Noonan氏は言う。
HOL(head-of-line) Blockingの問題を解決するために、宛先ごとに別々のサービスとキューを作成しました。この新しいアーキテクチャでは、インバウンドイベントを受信し、そのイベントのコピーを選択した宛先それぞれに分散するルータプロセスが新設されていました。ひとつの宛先に問題が発生しても、そのキューがバックアップされるだけで、他の宛先に影響を与えることはありません。このマイクロサービススタイルのアーキテクチャでは、宛先をそれぞれ分離していました。ひとつの宛先で問題が多発するような場合、これは非常に重要です。
記事では続いて、Segmentの開発チームが当初すべてのコードを単一のリポジトリに格納していて、それが問題を引き起こしたことが説明されている。
特に不満の大きかったのは、テストがひとつ失敗すると、すべての宛先でテストが失敗することでした。変更をデプロイしたい場合には、最初の変更とは関係がなくても、失敗したテストの修正に時間を費やさなくてはなりませんでした。この問題に対処するため、各宛先のコードをそれぞれ独自のリポジトリに分割する決定が下されたのです。
これによって開発チームは、より柔軟に作業できるようになったが、宛先の数が増えるにつれてリポジトリの数も増加した。これらコードベースをメンテナンスしなければならない開発者の負担を軽減するため、Segmentチームは、すべての宛先に共通する変換と機能を行なう共有ライブラリを多数開発した。一連の共有ライブラリは、メンテナンス性に明らかなメリットをもたらしたが、明らかではない欠点もあった — 共有ライブラリのアップデートとテストに多くの時間を取られるようになったことと、関係のない宛先のテストを失敗させるリスクが発生したことだ。最終的にはライブラリのさまざまなバージョンが現れ始めて、それぞれが分離し、各宛先のコードが異なるバージョンの共有ライブラリに依存するという、予期せぬ問題につながった。Noonan氏も認めているように、これらライブラリの変更を自動的にロールアウトできるようなビルドツールを用意しておくべきだったのだ。しかし、この頃の同社は、マイクロサービスアーキテクチャに関わる別の問題にも直面していた。
新たな問題は、各サービスが独自の負荷パターンを持っていたことです。1日数件のイベントを処理するサービスもあれば、毎秒数千件を処理するものもありました。少数のイベントを処理する宛先では、予期しない負荷のスパイクが発生した場合、その要求を満たすために、オペレータが手動でサービスのスケールアップを行わなくてはなりませんでした。
自動スケーリングは同社の実装機能に含まれていたが、各サービスが特定のCPUとメモリリソースを必要としていたため、その設定は“科学というよりも芸術(more art than science)”の域だった。前に述べたように、リポジトリの数は宛先を追加するたびに増加していった。平均で月に3つの宛先が追加されていたが、同時に多くのキューとサービスが必要になっていた。
2017年始め、Segmentのプロダクトの中核部分がある転換点に達しました。マイクロサービスのツリーから落ちてきて、途中のすべてのブランチに当たっているかのようになったのです。より迅速に行動できるどころか、小規模なチームが爆発的な複雑性の中に埋もれてしまったのです。このアーキテクチャの持つ基本的なメリットは負担に転じてしまいました。ベロシティは急落し、欠陥率は爆発的に増加しました。[...] そこで私たちは、一歩退いて、パイプライン全体を再考することにしたのです。
記事の残りの部分では、同社がマイクロサービスアーキテクチャを放棄して、個別のキューをリプレースするためにCentrifugeを開発し、イベントを単一のモノリシックなサービスに送信するようにした状況が説明されている。宛先別であったコードもすべて単一のリポジトリに戻したが、今回はコード管理のための特別なルールをいくつか設定した — すべての宛先をひとつのバージョンとして、全体が同時に更新されるようにしたのだ。すべての宛先が同じバージョンを使用し、今後もそうすることになったため、依存バージョン間の違いを心配する必要はもはやなくなった。開発者としては、少ない時間消費と少ないリスクで、より多くの宛先をメンテナンスできるようになったのだ。
Noonan氏の記事には、モノリシックなサービスに戻った経緯についても詳しく書かれているので、興味があれば一読して、アーキテクチャやリポジトリ構造の考え方、レジリエントなテストスイートを構築するためのアプローチなど、詳細を確認するとよいだろう。いずれにせよ、チームが最終的に手にしたメリットは、次のようなものだった。
マイクロサービスアーキテクチャを運用していた2016年中に、私たちは共有ライブラリに32の改善を加えました。今年はこれまでに、46の改善を行なっています。この6ヶ月間で、2016年に行った以上の改善をライブラリに行なっていることになります。今回の変更は、運用上のストーリにも役立っています。すべての宛先がひとつのサービスに存在するため、CPUやメモリを多く使用する宛先がうまくミックスされて、ニーズを満たすためのサービスのスケールアップが極めて容易になりました。大規模なワーカプールがロードのスパイクを緩和してくれるので、少量のロードを処理する宛先に手を煩わせることもなくなりました。
一方でこのアーキテクチャには、障害の分離が難しい(ひとつの宛先のバグでサービスがクラッシュすると、他の宛先すべてに障害が発生する)、依存関係のバージョンアップが他の宛先に問題を発生させて、バージョンアップを余儀なくされる可能性がある、などといったマイナス面やトレードオフも存在する。Noonan氏の記事は、次のような現実主義的な記述で締め括られている。
マイクロサービスとモノリスを選択する場合、それぞれに考慮すべき点があります。インフラストラクチャのある部分においては、マイクロサービスはうまく機能します。しかし当社のサーバサイドは、人気の高いこのトレンドが、実は生産性やパフォーマンスに悪影響を与えるかも知れないという、完璧な例となりました。結果として、私たちに最適なソリューションはモノリスだったのです。
マイクロサービスに関わるこれらの懸念のいくつかは、実際によく耳にするものかも知れない。今年初め、我々は、マイクロサービスがThoughtWorksのTechnology RadarでAdopt Ringに到達しなかったことを報告した。その記事でも報告しているように、“おもな理由のひとつは、多くの組織にマイクロサービスを受け入れる体制が整っておらず、操作や自動化に関する基本的なプラクティスに欠けている”ためだった。さらにJan Stenberg氏の報告による、数年前からのマイクロサービスに関わる障害を報告した別記事では、Berico TechnologiesのチーフエンジニアであるRichard Clayton氏が、当時の問題点のひとつを示唆している。
独立したサービス間でユーティリティコードを共有したいという欲求と、機能の複製とのバランスを取ることが大きなトレードオフとなり、結果的には大規模なリファクタリングを行うことになりました。
元記事に戻ると、このトピックに関しては、Hacker NewsやRedditなどで活発な議論が交わされる結果になった。その中のいくつかは、マイクロサービスが原因ではないかも知れない、他の領域に関わる懸念を指摘している。例えば、あるコメントでは、Noonan氏の元記事にCIに関する記述がない一方で、一般的な組み合わせとは言えないCDのみが取り上げられている、という点を指摘している。また別のコメントでは、SOAでの同様の経験を引合に出しながら、問題はマイクロサービスに特有ではなく、おそらくはかつて経験したように、分散システムに共通するものなのではないか、と示唆している。
私はかつて、クラウドより前、SOAと呼ばれていた頃に、同じようなコードベースで開発作業をしていました。サービスへのすべての呼び出しがサービスのフルインスタンスを起動し、メソッドを呼び出して、インスタンスをシャットダウンする仕組みです。アーキテクチャのダイアグラム上、ネットワークのレイテンシは必須のものと考える必要がある、と私は思います。
興味深いことに、コメントスレッドの多くが、マイクロサービスのコンテキストでのデータに関わる問題を論じている。この問題については、InfoQでも何度か取り上げており、問題点や否定的意見の根拠としてよく見られるものだ。Hacker Newsのあるコメントでは、
事態はもっと深刻です — 私の見解では、ほとんどのマイクロサービスアーキテクチャは、一貫性をまったく無視(“腐ったトランザクションはいらない!”)した上で、ハッピーパスのみに盲目的に従っています。低速で信頼性の低いネットワークを使ってソフトウェアモジュールを分離した上に、面倒な手組みのREST処理でそれらを接続して、それでアーキテクチャが改善できるのではないかと考える理由が、私にはまったく理解できません。結局は、生産性の錯覚に陥っているのではないかと思います — “作業完了、左パッド・アズ・ア・サービスの完成だ! 2ヶ月かけて開発した、クールなダッシュボードで緑色に小さく光るステータス表示を見てくれ!”
さらに、マイクロサービスのためのドメイン定義は、マイクロサービスのデプロイを成功させるために重要なものとして、我々が以前から取り上げてきたものだ。実際に、DDDを使ってモノリスを分解するプレゼンテーションが実施されたこともある。Redditのスレッドで交わされたもうひとつの議論は、これと関係があるのかも知れない。
優れたマイクロサービスアーキテクチャの構築は困難です — ドメインを適切に分離すること、システムの進化に従ってこの面を再評価することが重要なのではないか、と私は思います。名前とは裏腹にマイクロサービスでは、小さいことは必要ではなく、むしろアーキテクチャ上の特定の性質を満たすことが求められます — これがほとんどのケースで陥りそうな、最大の落とし穴なのです。
他の意見はどうだろう?例えば、Segmentのマイクロサービスアーキテクチャの問題は、モノリスベースのアプローチに戻らずに、他の方法によって解決可能だったのだろうか?あるいは、そもそもマイクロサービスを導入せずに、最初のモノリスベースのアーキテクチャを発展させた方が、増大するニーズにうまく対処できたのではないか?
このニュース項目は、2018年7月16日、Hacker NewsとRedditのからの詳細が更新されている。
この記事を評価
- 編集者評
- 編集長アクション