豆寄席第33回『システム開発の課題を解決する「所謂」マイクロサービスアーキテクチャ実践の勘所』参加レポート

塚野 皓平 下川部 将紘

本稿は、豆寄席第33回の開催報告です。

開催概要

タイトル システム開発の課題を解決する「所謂」マイクロサービスアーキテクチャ実践の勘所
講演者 GMOインターネットグループ デベロッパーエキスパート 成瀬 允宣(なるせ まさのぶ)
開催日時 2024年6月10日 (月) 18時30分~20時00分

講演の流れ

本講演では、マイクロサービスアーキテクチャの概要と、その導入によるメリットとデメリットについてご説明していただきました。その後、それらを踏まえ、マイクロサービスを使用した目指すべきシステムについて解説していただき、最後に既存のモノリシックアーキテクチャからマイクロサービスアーキテクチャへの移行方法についてお話していただきました。

  1. そもそもマイクロサービスとは
  2. マイクロサービスのメリット・デメリット
  3. マイクロサービスを使ってどのようなシステムを目指すべきか
  4. どのようにマイクロサービスへ移行するのか

そもそもマイクロサービスとは

一つのシステムに多くの機能が入っており、システム全体で一つのデータベースを使用している伝統的な構造をモノリシックアーキテクチャと呼びます。それに対してマイクロサービスとは、機能レベルでサービスを独立させ、それら複数サービスの連携により一つのシステムを構築する手法を指します。マイクロサービスの定義には様々なものがありますが、本講演では機能レベルで独立しているという点に加え、各サービスごとにデータベースを持つという点を基本的なマイクロサービスの定義としてお話いただいています。

マイクロサービスのメリット・デメリット

まず前提として、既存のモノリシックなサービスと比べ、マイクロサービスを導入するメリットとデメリットについて以下の通りお話しいただきました。

メリット

  • 小さい

小さなプログラムは理解しやすく、修正も簡単。
これにより、システム全体が泥団子のように複雑になることを避けられる。

  • 大規模アプリケーションでCI/CDができる

テスト容易性とデプロイ容易性が高く、継続的インテグレーション/継続的デリバリー(CI/CD)のプロセスがスムーズになる。サービスごとにデプロイが可能なので、部分的な更新が容易。

  • サービスごとにスケールできる

特定のサービスだけをスケールアウト/スケールインすることができる。
例えば、支払い処理機能だけが高負荷の場合、その部分だけをスケールアウトすることが可能。

  • 障害分離が優秀

サービスが独立しているため、一部の機能に障害が発生しても他の機能には影響が及ばない。
例えば、アカウントサービスがダウンしても、オーダー処理や支払い処理は継続できる。

  • 新しいテクノロジを実験・採用可能

サービスごとに異なる技術スタックを使用できるため、新しい技術を試しやすい。
例えば、あるサービスはJava、別のサービスはKotlinで開発することも可能。
          

デメリット

  • サービスの分割が難しい

どの機能をどのようにサービスとして分割するかの判断が難しく、プロジェクトごとに異なる最適解を見つける必要がある。

  • 分散システム特有の難しさがある

サービスが分散されることで、ネットワークの遅延やシステム間のデータ整合性など、新たな課題が生まれる。

  • これまでのやり方とは大幅に変わる部分が多い

新しいミドルウェアの導入や、各サービスのテスト、デプロイの仕組みが必要となるため、技術的な知識が求められる。

マイクロサービスを使ってどのようなシステムを目指すべきか

次に、マイクロサービスを活用して目指すべきシステムについてお話いただきました。
サーバー負荷などによる遅延や障害が発生すると、ユーザーは競合他社のシステムに流れてしまい、システムの存続が危ぶまれます。実際に応答性の低さが深刻な問題を引き起こした事例を交え、「あらゆる状況下で応答性を高める」ことが目指すべきシステムの目標であるとご説明いただきました。

では、どのようにすれば高い応答性が確保できるのでしょうか?ここで、AkkaやScalaを開発したLightbend社が発表したリアクティブ宣言についてご紹介していただきました。

この宣言では、応答性(即応性)を担保するために「弾力性」「耐障害性」「メッセージ駆動」が必要だとされています。弾力性とはシステムが負荷や障害に柔軟に対応する能力であり、耐障害性と弾力性は互いを支え合う関係にあります。これらの要素の根底にはメッセージ駆動が必要であり、あらゆる状況下で応答性の高いシステムを実現するためにはメッセージ駆動が重要とされています。

なぜメッセージ駆動が弾力性、耐障害性に寄与するのかという点についてもご説明いただきました。システム間にメッセージブローカーを介することで、受信するメッセージ量に応じてシステムをスケールイン/アウトでき、弾力性を得ることができます。また、システム間の結合度を下げることで耐障害性も向上します。これにより、メッセージ送信側のシステムは他のサービスに影響されずに即応できます。ただし、メッセージブローカー自体が単一障害点となるリスクがあるため、メッセージブローカーの信頼性確保が重要となるそうです。例えば、AWSのフルマネージドサービスであるAmazon MSK(Amazon Managed Streaming for Apache Kafka)は、ブローカーインスタンスの冗長性を確保する仕組みを提供しており、信頼性を担保しています。

応答性の高いシステムに必要なメッセージング駆動ですが、考えるべき課題がいくつかあるそうです。

まず、「メッセージの送信取消」の問題について解説していただきました。
例えば、データベースへの書き込みとメッセージブローカーへのメッセージ送信が非同期で行われるシステムにおいて、データベースへの書き込みがエラーとなりロールバックが発生したとします。この時点ですでにメッセージブローカーへメッセージが送信されてしまった場合、この送信されたメッセージを取り消すことができません。この問題を解決するためには、データベースへの書き込みとメッセージ送信をアトミックに行う仕組みが必要です。

ここで活用されるのがOutboxパターンだそうです。
Outboxパターンでは、送信するメッセージを書き込むOutboxテーブルを用意し、データベースへのデータ書き込みとOutboxテーブルへの書き込みを同一トランザクション内で行います。Outboxテーブルへの書き込みがコミットされたときにだけ、メッセージブローカーへメッセージを送信することで、データベースへの書き込みとメッセージ送信をアトミックに行うことが可能となります。
次に考えるべき課題として「何を送るか」という点を解説していただきました。

複数のシステムがメッセージブローカーからメッセージを受信する場合、送信側で送り先を意識してメッセージを都度変更するのは煩雑です。そこで、メッセージ受信側がメッセージを解釈するPub/Sub方式を採用することで、送り先を意識せずにメッセージを送出することができます。具体的には、送信側のデータベースに書き込まれた内容をそのままメッセージとし、メッセージ受信側でそのメッセージを解釈・利用するようにします。また、メッセージ受信側の複数のサービス連携はコリオグラフィで自律的に行うことで、耐障害性が向上します。

具体的なメッセージの中身については、実際に動作するアプリケーションを使い、CQRS(Command Query Responsibility Segregation)とES(Event Sourcing)を活用できるものが良いと解説いただきました。CQRSとは、データの書き込みを行うコマンド(操作)処理とデータの読み取りを行うクエリ(問い合わせ)処理を分ける考え方であり、コマンド系とクエリ系の間をメッセージングサービスでつなぐことで非同期的に連携を行えます。ESとはイベントソーシングのことで、状態変化をイベントとして記録し、そのイベントを再生することで現在の状態を構築する設計パターンです。メッセージキューに送信するメッセージはこのイベントを表すオブジェクトをJSONなどにそのままシリアライズしたものにするのが良いそうです。これによりメッセージを送信する側は受信側のことを意識せずに済みます。

一方で、送信側が受信側を意識する必要がある場合もあります。
Pub/Sub方式では受信側(Subscriber)がメッセージを解釈するため、送信側(Publisher)に依存する状態ですが、例えば、SubscriberのサービスがUtility的なサービスで不特定多数のサービスに利用される場合、SubscriberのサービスがPublisherに依存するのは不自然です。この依存関係を逆転するためには、HTTPリクエストを使用してPublisherからSubscriberへ送信を行うのが良いとおっしゃっていました。ただし、この場合ではSubscriberが単一障害点となる可能性があるため、リトライ機能を実装し、Exponential Backoffなどを用いて自動的に再試行する仕組みが必要だそうです。

マイクロサービスにおいては、データベースはサービスごとに分けるべきだともおっしゃっていました。データベースの情報は各サービスのコードに密接に関連しているため、複数のサービス間でデータベースを共有すると、一方のサービスのデータベースに関わる変更が他方のサービスに影響を与える可能性があるためです。しかし、別々にしたデータベース間でデータを結合したい場合があります。この場合も前述のCQRS+ESの方式が役立つとご解説いただきました。JSON形式などでデータベースのデータをそのままメッセージとして送出し、受け取ったデータを結合するサービスを作成して、結合テーブルを作成するようにするのが良いそうです。

合わせて、CQRS+ESを簡単に実現するフレームワークとしてAxonフレームワークを紹介していただきました。また、複数のデータベースにまたがるトランザクションを行う場合にはSagaパターンを利用するのがよいそうです。ただしSagaパターンは結果整合性を保証するため、局所的な不整合を許さないシステムの場合は2-phase commitを行うしかないともおっしゃっていました。

どのようにしてマイクロサービスへ移行するのか

最後に、既存のモノリシックなサービスからマイクロサービスへ移行する方法についてお話いただきました。
まず考えるべきは「やる価値があるのか?」という点だとおっしゃっていました。マイクロサービスに移行するには多くの難題があり、規模が大きくないと費用対効果が得られません。既存のシステムのままではアジリティが低下するなど明確な問題がある場合には移行を検討する価値がありますが、必要な知識が多いため、技術への関心が高く、ある程度のスキルがあるメンバーが求められるそうです。

既存のモノリシックサービスをマイクロサービスへ移行するとなった際には、ビッグリライトは現実的ではなく、少しずつサービスの移行を行うStranglerパターンが良いとおっしゃっていました。宿主の木に巻き付き、最終的には絞め殺してしまうStrangler Fig(絞め殺しの木)のように、モノリシックサービスの機能ごとにマイクロサービス化していき、徐々にモノリシックサービスからマイクロサービスへの移行を進める方法がStranglerパターンです。

移行の際は、まず重要度が高く移行しやすいサービスから始めるのが良いそうです。移行のしやすさは、システムの入り口となる機能(下右図のA)が最も簡単で、次いで最後に利用される機能(C)、最も難しいのはその間に利用される機能(B)だそうです。成瀬氏の場合はまず頻繁に更新されるサービスに注目し、その中でも入り口となる処理から移行を始めるとおっしゃっていました。

Aの機能をマイクロサービス化したら、マイクロサービスのAからモノリシックのBを呼び出すように変更し、徐々に移行を進めます。Bのような中間のサービスをマイクロサービス化する場合は、BとAの順番を入れ替えても問題がない場合に限り、Bを入り口にすることで難易度を下げることができるそうです。


ここで、移行中は必ずマイクロサービスとモノリシックサービスの連携が必要となります。
マイクロサービスからモノリシックのデータを参照する場合は、モノリシックにAPIを用意します(下左図)。逆に、モノリシックからマイクロサービスのデータを参照する場合は、マイクロサービスのデータベースの変更をモノリシックのデータベースへリレーさせるようにするのが良いと解説していただきました(下右図)。

データ更新の場合も、参照と同様にモノリシックにはマイクロサービスから利用されるAPIを用意します(下左図)。モノリシックからマイクロサービスへデータ更新を行う際には、更新処理を呼び出すインターフェースをモノリシックで新たに作成し、その実装クラスとしてマイクロサービスを呼び出す処理を作成するのが良いそうです(下右図)。

また、モノリシックとマイクロサービス間のデータ同期には、モノリシックのトランザクションログを監視し、それをトリガーとしてモノリシックのデータをマイクロサービスへ転送するCDC(Change Data Capture)サービス(Debeziumなど)を利用するとよいと解説していただきました。

以上のことをまとめると以下のようなフローで移行手順を判断できるとのことでした。
ロジックの先頭にある機能の移行にはマイクロサービスからモノリシックへの連携が、ロジックの最後尾にある機能の移行にはモノリシックからマイクロサービスへの連携が必要です。また、マイクロサービスからモノリシックへの連携の際にはAPIゲートウェイを仲介させることで、移行完了時に呼び出し先を簡単に移行先のサービスへ変更できます。

所感

本講演は、マイクロサービスの基本的な概念と定義の説明から始まり、導入時のメリット/デメリット、実践における課題とその解決方法や、移行方法までが幅広くカバーされていました。特に、既存のモノリシックからマイクロサービスへの移行ステップや、方法論などが具体的に示されていた点は、現在マイクロサービスへの移行を考えているプロジェクトや開発者にとって非常に有益な情報だと感じました。
一方で、マイクロサービスの導入には多くの利点がありますが、新たな課題やリスクも伴います。そのため、導入を検討する際には慎重な判断が必要であり、メリット/デメリットのバランスを考慮し、プロジェクトの状況に適したアーキテクチャを選択することが重要性だと再認識しました。

今後の 豆寄席 へのご参加もお待ちしております!