マイクロサービス化って意味あるんですか? 〜オークションサービスをゼロから作り直すとしたら〜
概要
最近、社内で「オークションサービスをゼロから作り直すとしたらどんなアーキテクチャにするか?」を考える機会があった。現在、携わっているサービスはマイクロサービス化されているものの、運用で辛い部分もあり、「結局マイクロサービスって意味あるんだっけ?」という疑問が湧いてきた。そこで、最低限オークションサービスとして成立する機能を満たすモノリスアーキテクチャからどのようにシステムを発展させるか考えた。
この記事では、オークションサービスをゼロから作り直すとしたらをテーマに以下の観点について触れる
- モノリスアーキテクチャ
- モノリスアーキテクチャの限界
- マイクロサービスアーキテクチャ化の実現
- マイクロサービスアーキテクチャ化って意味あるの?
オークションサービスってどんなの?
オークションサービスとは、ユーザーが商品を出品し、他のユーザーが入札することで商品を購入できる仕組みを提供するサービスです。サービスのコアとなる機能やルールは以下のようなものです。
基本の流れ
- 出品者が商品を登録し、オークション終了時間を指定して出品する
- 入札者はオークション開催中に価格を提示して入札する
- 終了時点で最も高額の入札者が落札者となる
主なルール
- 時限式オークション:オークションには終了時刻があり、それを過ぎると入札できなくなる。
- 自動延長機能:終了時刻の直前(例:終了5分前)に新たな入札があった場合、自動的に終了時刻が一定時間(例:5分)延長される。これにより、いわゆる「スナイプ入札(締め切り直前の狙い撃ち)」を防ぎ、より公平な競りが行える。
- 最低入札価格/即決価格:商品ごとに最低入札価格や即決価格(固定価格購入)を設定できる場合がある。
落札後の流れ
- 落札者は決済情報や配送先を入力し、支払いを行う
- 出品者は商品の発送処理を行う
- システムが出品者に入金を行い、取引が完了する
モノリスアーキテクチャでの実現
最初に、オークションサービスとして実現するのに必要な機能をざっくりまとめてみる。
機能要件
- ユーザー管理機能
- 出品機能(画像含む)
- 商品閲覧機能
- 競り機能
- 時限式終了機能
- 自動延長機能(終了時間直前に入札があった場合は、終了時間を延期する)
- 即決機能
- 取引機能
- 支払い機能
- 配送機能
- 検索機能
- 商品名検索
- 入札数順
- 最新順
- 通知機能(落札時, 入札時 等)
モノリスアーキテクチャ図
- モノリスアーキテクチャでは、サーバ全機能を1つのリポジトリ・デプロイ単位で運用
- 特例で、オークションの時限式で終了する機能については「落札実行バッチ」が定期的にDBから終了時間の商品を取得し、オークションの終了を行うこととする。
- DBには基本的な検索機能にも対応しやすく、同時アクセスが発生するような競りの状況でも整合性を担保してくれるRDBを採用する。
モノリスアーキテクチャの限界
一般的に言われるモノリスアーキテクチャの限界としては以下のようなものが挙げられる。
- スケーラビリティの制限
- モノリスはすべての機能がひとつのコードベースにまとまっているため、特定の機能だけをスケールアウトするのが難しい。負荷が集中している一部の機能のために、システム全体をスケールしなければならず、リソースが無駄になったりコストが跳ねたりする。
- 障害耐性が弱い
- どこか一部の機能で不具合が起きると、サービス全体が落ちる可能性がある。ちょっとしたバグが全体の停止に直結するリスクがある。
- 開発速度が落ちる
- サービスが大きくなるにつれて、ビルドやテスト、デプロイの時間がどんどん長くなる。複数のチームが同じリポジトリを触ることでコンフリクトも増えるし、誰かの変更が他の誰かのバグを引き起こすこともある。リリースに複数の変更が混ざると、障害の原因調査も難しくなってくる。
- 密結合による影響範囲の広さ
- モノリスは機能同士の依存関係が強いため、何かを変えると別の場所が壊れる。ちょっとした変更でも予期しない不具合が起こりやすくなり、結果として保守がしんどくなる。
チームやコードが増えてくると、こうした課題はどんどん顕在化してくる。変更の影響範囲が広く、調整コストも上がる。スピードが求められる今の開発現場では、それがそのまま機会損失につながってしまう。
こうした課題を解決するために生まれて数々の大規模サービスで採用されたのがマイクロサービスアーキテクチャである。
マイクロサービスアーキテクチャ化
マイクロサービスアーキテクチャ化をするにあたってはどうサービスを分割するかがとても重要になる。これを間違えると、密結合は解消されず、手のかかるモノリス2つ目が爆誕といった最悪の形になる。
オークションの流れに沿ってドメインを分ける
オークションの流れは大きく以下のフェーズに分けられる。
- 出品フェーズ
- 購入フェーズ
- 取引フェーズ
それぞれのフェーズで必要とされる情報や振る舞いは異なり、そのフェーズでしか更新しない情報をなるべく閉じ込めることで、サービス間の結合度を下げることができる。
このように、データの更新範囲が局所的になる設計は、マイクロサービス設計において非常に重要なポイントとなる。
現実のオークションの流れに沿った分け方をすると、各フェーズで更新するデータもおおよそ分割できて、フェーズ間を跨いで連携しないといけない情報が最小限に抑えらえる。
例えば、出品フェーズに必要なデータは商品情報・初回商品価格, 購入フェーズに必要な情報は商品価格, 入札情報, 取引フェーズに必要な情報は値段, 発送先住所, 決済手段などということが分かる。
フェーズ | 出品者が更新する情報 | 購入者が更新する情報 |
---|---|---|
出品フェーズ | ・商品情報(タイトル、説明、画像など) ・初回商品価格(開始価格) ・出品期間(開始日時・終了日時) ・カテゴリ、配送方法、送料負担など |
(なし) |
購入フェーズ | (なし) | ・入札情報(現在価格、入札履歴) ・商品ステータス(入札中など) |
取引フェーズ | ・発送準備状況の更新 ・発送通知 ・支払い確認 |
・配送先住所の入力・更新 ・決済手続きの実行 ・受取確認 |
マイクロサービスアーキテクチャ図
FE/LoadBalancerはあるとごちゃごちゃになるので除いてバックエンド部分だけを抜き出している。
出品フェーズ
まず、出品フェーズでは「出品コーディネータ」が叩かれて、こいつが色々なトランザクション状態を管理する。
成功ケースの場合は以下のような流れになる
- 出品管理DBにレコード作成
- 商品管理APIを叩いて商品DBに商品情報保存(商品ステータス「出品処理中」)
- 出品管理DBの状態更新
- 競りAPIを叩いて競りDBに商品ID, 価格情報保存
- 出品管理DBの状態更新
- 画像API経由で画像ACLを外す
- 商品ステータスを「公開中」に更新
- 出品完了イベントMQを流す
リトライや異常時についての考慮まで書くと長くなるのでそちらは以下の記事に譲る
購入フェーズ
入札の際は、入札APIが叩かれてオークションのロジックに従って入札DBに入札履歴が溜まり、商品価格も更新されていく。落札は落札実行CronJobが終了時間を経過したオークションを落札する。落札または即決購入されると、購入イベントMQが流れて取引エントリ作成Consumerが取引APIを通じて取引管理DBにエントリが作成される。
取引フェーズ
このフェーズで、購入者による支払い・出品者による発送・出品者への売上支払いが行われる。ここ自体ももう少し細かく分けられると思うが、今回はこの粒度に留める。
マイクロサービスアーキテクチャ化によってモノリスアーキテクチャの課題は解決されているか?
先に挙げた、モノリスアーキテクチャの課題を振り返って課題が解決できているかを確認する。
- スケーラビリティの制限
- 解消されている。それぞれの負荷要件に合わせてスケールを行えるようになっている。また、検索APIが負荷に耐えきれずサービスダウンした場合でも、取引は正常に行えるなどのメリットがある。
- 障害耐性が弱い
- 解消されている。検索APIにバグを仕込んだり、負荷に耐えきれずサービスダウンした場合でも、取引APIに影響が出る事がなくなる。
- 開発速度が落ちる
- 解消されている。コードベースが小さくなることで、テスト・ビルド・デプロイの高速化ができ開発速度が高められる。一方で、分散トランザクション管理のための、コンポーネントが発生したり、MQ(メッセージキュー)により、異常ケースで考慮することも増えているため、そこも含めて総合的に考える必要がありそう。モノリスでは、人数を増やして開発速度を上げるということが難しかったかもしれないが、マイクロサービスでは人数を増やして開発速度を上げられると考える。
- 密結合による影響範囲の広さ
- 上手く疎結合になるようにサービス分割を行えた場合は解消される。サービスごとにAPI, DBを分けているため、DBのスキーマ変更によって他が影響を受けることをなくせる。
マイクロサービスアーキテクチャ化によって発生する新たな課題
モノリスアーキテクチャの課題を解消するためにマイクロサービス化を進めた結果、確かにいくつかの課題は解決された。一方で、マイクロサービスは銀の弾丸ではない。システムの構造が分散化されたことで、別の種類の複雑さやコストが発生している。
システム全体の複雑性の増加
マイクロサービス化によって、1サービスあたりのコードベースは小さくなったが、システム全体の構成は複雑になった。
APIゲートウェイ、サービスディスカバリ、認証認可、監視、分散トレーシング、リトライ・タイムアウト戦略など、サービス間通信をどう管理するかという新しいレイヤーの知識と設計力が求められる。
オペレーションコスト・インフラコストの増加
サービスが増えると、それに比例してCI/CD・モニタリング・アラート・ロギングの管理対象も増える。
また、ネットワーク通信が増えるため、単純なAPIコール1つでもレイテンシやタイムアウトの影響を受けやすくなる。
チーム間連携コスト
サービスが独立して動けるようになることで、チームごとに自律的な開発が可能になる反面、
- インターフェース(API仕様)の設計と管理
- 変更時の調整・通知
- 障害発生時の原因特定(どのサービスの責任か?)
といったチーム間の調整コストは確実に発生する。
スピード感のあるチーム同士の動きが逆に衝突の原因になることもある。
マイクロサービスアーキテクチャ化して本当に意味あるの?
マイクロサービスアーキテクチャには、確かに課題も多い。設計・運用の難易度は上がるし、インフラ・人件費のコストも跳ねる。
それでも、本当にマイクロサービスに踏み切る意味があるのか?、どんな状況でそのコストに見合うだけの価値があるのか?を、ここではあくまでビジネス視点で考えてみる。
マイクロサービス化に踏み切る理由
1. 開発速度をさらに加速したいとき
今以上にスピード感のある開発が求められていて、人を増やしてでも機能追加のペースを上げたいというフェーズなら、マイクロサービス化は有力な選択肢になる。
サービスを分割することで、各チームが自律的に開発・テスト・デプロイを行えるようになり、チーム間の衝突を減らしながら並列に開発を進められる。
また、コードベースが小さくなることで、
- ビルドやテストが速くなる
- スキーマ変更などの影響範囲が小さくなる
- 障害時の切り分けもしやすくなる
といった副次的なメリットも得られる。
2. サービス全体の停止を極力避けたいとき
モノリスの構造では、一部の不具合が全体の障害につながるというリスクがある。
たとえば、検索機能や通知機能のような、コアではない部分の障害であっても、プロセスが1つなら全体が落ちる。
もし、信頼性や可用性がビジネス的に極めて重要な価値であるならば、コストを払ってでも独立性の高いアーキテクチャに移行する価値はある。
重要なのは、「一部の障害が全体に波及しない」設計を手に入れることが、どれだけのビジネスインパクトを持つかだと考える。
マイクロサービス化に踏み切ったら覚悟すること
- 人件費の増加
- サービスが分かれれば、それぞれのインフラ・監視・デプロイフローの構築・保守が必要になる。運用を担う人も増やす必要が出てくる。
- インフラコストの増加
- サービス数の増加=プロセス・インスタンスの増加なので、単純にリソースも増える。ネットワーク経由の通信も増えるため、効率の良いスケーリング設計ができていないとコストは跳ね上がる。
- 設計・運用の複雑化
- 特にデータ整合性やトランザクションの扱いは、モノリスに比べて桁違いに難しい。チームに分散システムの知見がなければ苦戦する。
マイクロサービス化しなくていい場合
- 開発者数は増やさずに開発速度を上げていきたい
- マイクロサービス化する際には、それぞれのサーバ・DBの管理が必要となり、そこには人が必要になってくる。開発者数は増やさずに開発速度を上げるにはモジュラーモノリスで密結合を解消するのがいいのではないかと考える。これにより、影響範囲を小さくできるため、新たな機能追加・改修の必要が出た時でも取りかかりやすい。モジュラーモノリス化の実例としては以下の記事が挙げられる。
モジュラーモノリスを実践している記事が見つかったため掲載しておく
まとめ
結論、マイクロサービスアーキテクチャに意味はあったのか?の答えは、「場合による」になる。ただし、大規模サービスは導入するべきと考えていると、ビジネス的にはコストがかかるだけのものになる可能性がある。マイクロサービス化の決断は、ビジネスのスピード・スケール・信頼性を支える組織戦略の一環として行われるべき。
Discussion