1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

イベント駆動アーキテクチャ入門と実戦知見

1
Posted at

前書き

この記事は、以前お世話になったエネルギー会社で開発したシステムの経験を記録したものです。

コレオグラフィからメディエイター、そしてDBボトルネックまで

この記事では、イベント駆動アーキテクチャ(Event-Driven Architecture: EDA)の基本概念を押さえたうえで、AWS上での実装例を通して「コレオグラフィがハマる領域」と「メディエイター(オーケストレーション)が必要になる領域」を具体的に比較します。さらに、実装して運用し始めて初めて見えてきた現実的な課題――特に並列イベント処理とDBのボトルネックの衝突――について、実際にとった対策とその結果を説明します。
結論だけ言えば、EDAは「正常系を高速に流す疎結合の仕組み」として非常に強力ですが、業務フローの複雑さや運用要件が乗った瞬間に設計のフェーズが変わり、最後はDBをどう扱うかが勝負になります。

1. イベント駆動アーキテクチャとは何か

イベント駆動アーキテクチャとは、「システム内で起きた出来事(イベント)」を中心に、処理連携や状態変化を組み立てるアーキテクチャです。従来の同期型アーキテクチャでは、あるサービスAが別サービスBのAPIを直接呼び出し、結果を待って次の処理へ進む形が一般的でした。これは構造が直感的である一方、サービス同士が強く結びつきやすく、「AがBを知っている」「Bの応答速度がAの処理速度を決める」といった依存関係がシステム全体に波及します。

EDAでは発想が逆です。サービスAで何か重要な状態変化が起きたら、それを「イベント」という形で外部に発行します。イベントは「起きた事実の宣言」であり、発行側は「誰が受け取るか」「その後どう使われるか」を知りません。イベントを受け取りたい側のサービスは、イベントを購読(Subscribe)し、届いたイベントに基づいて自律的に処理を実行します。これにより連携は非同期化し、サービス間の依存は“API呼び出し”から“事実の通知”へと置き換わります。

基本構造を図にするとこうなります。

この図のポイントは、A(発行者)がC/D/E(購読者)を一切知らないことです。依存が一方向になり、イベントの受け手が増えても発行者の変更が不要になります。結果としてシステムは拡張に強くなり、後付けで要求が増える現実に耐えやすい構造になります。

2. EDAで頻出する用語と考え方

EDAの議論では、まず「イベント」と「コマンド」の区別が極めて重要です。コマンド(Command)は「こうしてほしい」という要求であり、成功するかどうかは実行時に決まります。たとえば「注文を作成してくれ」「支払いを実行してくれ」といった未来向きの命令です。一方、イベント(Event)は「こうなった」という過去の確定事実で、成功・失敗の結果がすでに内包された不変の記録です。「注文が作成された」「支払いが完了した」という形になります。

最後に残るのは「イベント配送の性質」です。現実の多くのブローカー(SQSやKafkaなど)は「少なくとも1回配送(At-least-once)」を採ります。つまり同じイベントが複数回届く可能性がある。したがってEDAのコンシューマー側は、必ず冪等性(同じイベントを何回処理してもシステムが壊れない性質)を持たせる必要があります。
加えて、イベントの前後関係(順序保証)やイベントスキーマの変更(互換性)も運用設計として避けて通れません。このへんは後半の「踏んだ課題」で実例として扱います。

3. EDAのメリットとデメリット

EDAの最大のメリットは「疎結合による拡張性」です。イベントを発行する側は受け手を知らないため、新しい処理を後から追加するのが非常に楽になります。たとえば既存の「注文作成イベント」に対して、後から「通知」「分析」「不正検知」「推薦更新」などの処理を足したくなっても、イベント発行側のコードを変更せずに済むことが多い。これは“機能が増え続ける現実”に対して構造的に強い性質です。

また、処理が非同期であるためピーク負荷の吸収や負荷平準化がやりやすい点も重要です。同期APIのチェーンは下流が詰まった瞬間に全体が連鎖的に遅くなりますが、EDAではブローカーがバッファとして働き、下流の回復を待てます。

一方デメリットも明確です。第一に、非同期ゆえに最終的整合性(Eventual Consistency)が前提になります。つまりある瞬間に各サービスの状態が揃っていないことが普通に起きる。これを受け入れられない設計やUIは破綻します。第二に、イベントが時間差で複数サービスへ波及するため、障害解析やデバッグが難しくなります。因果関係を追うための分散トレーシングや相関IDなどの仕組みがないと、どこで何が起きたか見失いやすい。第三に、重複配送・順序・スキーマ変更など、分散システム特有の運用課題が必ず発生します。

EDAは「変化に強い疎結合」を売りにする代わりに「整合性・可観測性・運用成熟度」を要求してくるアーキテクチャだと言えます。

4. AWSでの最小構成:APIGW + SQS + Lambda

AWS上でEDAを素直に組むなら、最小形として Amazon API Gateway(入口) + SQS(ブローカー) + Lambda(コンシューマー) が非常に使い勝手が良い構成です。
API Gatewayが外部からの入力(コマンド)を受け取り、それをSQSに積み、Lambda群が並列で購読・処理します。スケールも運用もAWS側に寄せられ、MVP〜中規模の現場で最もボスキャラを減らせる形です。

ここまでが「EDAの基本セット」です。
次の2章で、このセットを使った コレオグラフィ型の実例 と、そこから発展した メディエイター型の実例 を対比します。

5. コレオグラフィ型の実例:日次指針データから料金算出

5.1 なぜコレオグラフィが合うのか

日次指針データの取り込み→料金計算→現在料金の更新、という流れは、処理の目的が明確で、並列で走っても問題の少ない“計算・集計系”のワークロードです。この手の処理では、全体の進行役(中央の司令塔)を置くよりも、イベントをトリガーに各コンシューマーが独立に反応していく「コレオグラフィ型(自律分散)」が最もシンプルで壊れにくいです。

流れは単純です。指針データが入ったという事実(メッセージ)がSQSに積まれ、それを複数のLambdaがそれぞれの責務で処理していきます。後から分析や通知を追加したくなっても、SQSを購読するLambdaを1個増やすだけで済みます。

5.2 実際に踏んだ課題と解決策

ただし、現実には「並列に処理する」という性質ゆえに、コレオグラフィ特有の課題が確実に出ます。ここが“教科書では薄いけど現場では濃い”部分です。

まず重複配送と冪等性です。SQS/Lambdaの組み合わせはAt-least-once配送なので、同一メッセージが再配送される可能性をゼロにできません。そこで各処理の単位ごとにUUID(処理ID)を割り振り、処理開始時に「そのIDが既に処理済みか」を確認する軽量ガードを入れました。これにより二重計算や二重更新の事故を防ぎます。重要なのは、冪等性を“ブローカー側”ではなく“各コンシューマーが自分の責務の中で持つ”というスタイルに寄せることです。

次に異常系の扱いです。イベント駆動では、例外をフロー内のif分岐で全部吸収しようとすると、処理の因果が散らばり一気に崩れます。だから「フローは正常系中心に細く長く」「異常系は隔離して運用と再実行へ送る」という割り切りを採りました。具体的には、再実行で回復可能な異常はDLQに落とし、回復不能もしくは判断が必要なものはログ+Slack/Backlog通知で運用プロセスに渡します。EDAは“例外をコードで潰すための仕組み”ではなく、“例外の隔離と回復のための仕組み”にした方が長期的に健全だと感じました。

最後に順序です。順序が強く求められる処理を無理にEDAへ載せようとすると、FIFOキューや順序制御のための追加ロジックが必要になり、並列化メリットが消え、コストと複雑性だけが増えます。そこで順序が本質要件のものは、無理にFIFOを使って延命せず、EDA実装を諦めて同期処理やバッチ処理に寄せました。これは「EDAを採る/採らないを判断できる」ことが設計上の重要スキルだという学びでもあります。

6. メディエイター型の実例:問い合わせをCamundaで業務割当

6.1 なぜメディエイターが必要になるのか

一方で、お客からの問い合わせ対応のような業務フローは性質が違います。
緊急保安、ガス配送、営業などの作業は、問い合わせ内容によって分岐し、並列になったり、待ち合わせが発生したり、途中で失敗したら補償や再割当が必要になったりします。さらに「今この問い合わせはどこまで進んでいるか」を運用側が常に把握している必要があります。こうしたフローをコレオグラフィに任せると、因果と状態が各サービスに分散しすぎて、運用の可視性が崩壊します。

そこで採るのがメディエイタートポロジー(Mediator Topology)です。
メディエイターはイベントの単なる受け手ではなく、業務フローの進行役(オーケストレーター)として振る舞います。今回はCamundaなどのワークフローエンジンをメディエイターとして置き、UIからの問い合わせ登録を起点に、必要な作業を順序・条件に従って割り振り、結果を受け取りながらフローを進めます。

Camundaが「次に誰を動かすか」「どこで待つか」「失敗したらどう戻すか」を決めることで、業務フローの状態と責任が中心に集約され、運用しやすい形になります。

6.2 実際に踏んだ課題と解決策

メディエイター型で現場が詰まるのは、例外フロー、リトライ、補償(Saga)、進捗の可視化といった“フロー制御そのもの”の部分です。これを自作し始めると、あっという間に「ワークフローエンジンを自作しただけ」の地獄になります。そこで、これらの機能はCamundaに委譲し、自分たちで実装しない方針を徹底しました。
メディエイターは“買うべき複雑さ”の塊だという認識が重要です。

さらに重要だったのが運用設計です。
業務フロー型システムでは、運用は“後から決めるもの”ではなく、仕様そのものです。どの状態で誰が何を判断するのか、どの例外で誰に通知されるのか、どこまで自動化しどこから手動介入なのか――こうした設計をユーザー(現場)と繰り返しすり合わせ、今後の業務がどう変わるかをレクチャーしながら合意を作りました。
ワークフローエンジンを導入しても、運用の握りがないと成功しない、というのが実戦の結論です。

7. 最後にやってみてわかったこと:DBボトルネックとの殴り合い

EDAを現実のスループットで回し始めると、イベントプロセッサーの並列度が高まるにつれて、読み書き問わずDBMSアクセスがボトルネックになります。これはほぼ例外なく起こります。
特にRDBMSは同時接続、ロック、I/Oがネックになりやすく、並列イベント処理の圧を直接受けると、イベント処理のタイムアウトや遅延に直結します。これはEDAにおける致命的なデメリットになり得ます。

ただし、RDBMSはスキーママイグレーションや整合性、監査を含めた運用保守上の価値が高く、メインDBとして外せません。結局「RDBMSを使い続けながら、イベント並列の負荷をどう受け流すか」が実戦の課題になります。

7.1 読み込みの対策

まずRDS Proxyを入れて接続数上限の問題を緩和しました。Connection Refusedは吸収できるものの、最終的なパフォーマンスはRDSインスタンスのサイズに依存するため、並列度が上がるとスケールアップが必要になりコストが上がりがちでした。

次に、RDSから計算に必要なデータをDynamoDBにコピーする方式を試しました。DynamoDBは同時接続の性能が圧倒的に高く、読み込みのスループットは改善しました。ただし並列度が上がるほど必要な通信帯域・キャパシティを事前に購入する必要があり、結局高コスト化しやすい。またマスターデータをDynamoDBに移すと運用保守の複雑性が増え、RDSにマスタを置き続ける場合は同期コストが跳ね上がります。

最終的に最も効いたのは、RDSから読み込んだデータをLambdaインスタンスの/tmpにキャッシュする方式でした。Lambdaは一度立ち上がったウォームインスタンスが再利用されるため、キャッシュヒット率が上がるほどRDSアクセスが減り、並列イベント処理の圧を効果的に逃がせます。読み込みに関しては「DBを速くする」より「DBを読まない回数を減らす」方が勝ち筋でした。

7.2 書き込みの対策

書き込み先は、RDSや並列耐性が高くない外部API以外では要件を満たせませんでした。つまり「書き込み先は動かせない」前提を受け入れたうえで対策する必要がありました。
並列コンシューマーが直接RDS/APIへ書き込みに行くと同時接続問題が確実に出るため、書き込みだけは出口で直列化・制御する設計に切り替えました。

具体的には、並列処理が終わった結果を一旦SQSに集約し、出口にWriter Lambdaを置いてメッセージを順序制御・バルク化して書き込みます。

記録先がRDSの場合、Writer Lambdaは1インスタンスで200メッセージをまとめて取得し、RDSへバルクインサートしました。これにより接続数を絞り、I/O効率を高められました。
記録先がAPIの場合は、APIが耐えられる並列数に合わせてWriter Lambdaの並列度を制御し、1メッセージずつ取得してAPI Callしました。ここは“システム側の都合で並列を上げない”割り切りが必要でした。

要するに、並列化のメリットは計算で最大化し、DB/APIアクセスは薄く・少なく・まとめて寄せるのが現実解でした。

8. まとめ

イベント駆動アーキテクチャは、イベントという「確定事実」を中心に非同期・疎結合で連携することで、拡張性とスケーラビリティを得るアーキテクチャです。
計算・集計系のように中央制御が不要なワークロードではコレオグラフィ型が最もシンプルに機能し、重複は冪等性ガードとDLQで吸収し、順序要件が強い領域には踏み込まない判断が重要になります。
一方、業務フローが複雑で運用の可視化や補償が価値の中心になる領域では、メディエイター型(Camunda等のワークフローエンジン)に寄せてフロー制御を集中管理し、例外・リトライ・補償・可視化を自作しない方が長期TCOは確実に下がります。運用設計は仕様そのものなので、現場との握り込みが成功の前提です。

そして実戦最大の壁はDBです。EDAで並列処理を強めるほどDBアクセスがボトルネックになるため、読み込みは「回数を減らす」方向(/tmpキャッシュが最強)へ、書き込みは「出口で直列化・バルク化する」方向へ寄せるのが現実的な落としどころでした。

EDAは万能ではありませんが、 **“向く領域で、向く形で、向かない領域を見極めて使う”**と、現場で爆発的に効くアーキテクチャです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?