以下の内容はhttps://product.10x.co.jp/entry/2025/05/30/093811より取得しました。


イベント駆動設計を支える非同期処理について | お届けチーム取組紹介

前回記事で書いたように、お届けチームの扱うシステム領域ではさまざまな非同期処理が行われています。

product.10x.co.jp

この記事では

  • 非同期処理の採用するモチベーション
  • 非同期処理の実現方法

を書いています。

非同期処理の採用するモチベーション

主には次の2つのような目的がある箇所で非同期処理を行なっています。

  • 領域間をまたぐため
  • 同期的な処理をミニマルにするため

非同期処理を積極的に採用するにあたり、同期処理とのトレードオフや監視すべきメトリクス等、運用する際のポイントはいくつかありますが、これらは後続の記事で詳しく解説する予定です。

「領域間をまたぐ」

まず前提として「ピックパック」「お客様注文」「配達」のような領域を見出したとします。 その領域を「お客様の注文内容に応じてピックパックが指示される」「ピックパックで商品を品切れにしたら、お客様との取り引き中の商品を品切れにより、お届け数を減らす」というようなケースで「領域間をまたぐ」としています。

領域をまたぐ際にはサービスが目指す方向性と相談し、狙って領域同士を疎結合にするメリットがあれば非同期処理を採用しています。

とくにカスケード障害がさけられるメリットの大きい箇所は進んで非同期処理にしています。

領域間を非同期処理で繋ぐ

「同期処理をミニマルにしたい」

「同期処理をミニマルにしたい」ケースだと、

  • ピックした商品をパック予定のものへ追加する
  • 特定時間(便)におけるピッキングの進捗をサマリする

ようなケースがあります。

予定に追加では同期的にしても意味的には問題ないですが、ピックのAPIはとにかく素早くレスポンスを返したいので、クライアントアプリから呼び出されるRPCではピックのみ行い、パック予定への追加は非同期にしています。

RPCではパッキング用のデータは不要で、ピッキング用のデータは読み込み/書き込みで済みます。

  • 「ピックができたら必ずパック予定の追加できる」ような前提条件を意図的に作る。
  • 予定の追加が結果整合で問題ないかをUIや業務手順上確認する。

など、下準備があって非同期処理を実現できます。  おかげでピックのRPCは低いレイテンシを保てています。

左: 同期処理にすべて収める | 右: 非同期処理

95pertileのグラフで業務がある9時から16時に250ms程度で収まっているので優秀な方です

サマリというのは主にFirestoreでデータ集計をするには都度クエリを実行では実現が難しく、事前に集計しておきたいケースです。 イベント駆動による非同期処理導入前ではRPCをハンドリングしたプロセス上でバックグラウンド処理を実行していました。 サマリというだけあり、データ操作の競合も多いのとサーバが停止するとサマリされないケースもありました。 単一プロセス内での非同期処理からシステム単位での非同期処理になったことで確実に処理することを可能にしました。

ピックや品切れのイベントは三系統の集計の元になる

こういったイベント駆動な非同期処理はFirestoreとEventarc、Cloud Runの組み合わせで実現されています。

https://cloud.google.com/eventarc/docs/run/route-trigger-cloud-firestore?

実現するためのoverview

EventarcによってFirestoreの書き込みをトリガーをにして、ドキュメントを非同期メッセージとしてCloud Runに送りつけていますが、EventarcはCloud Pub/Subで成り立っています。 なので、監視やリトライ設定はCloud Pub/Subへの設定で行なっています。

またCloud Pub/SubのSubscriptionはPush Subscriptionで作られるので、Cloud RunへはプレーンなhttpリクエストとしてCloudEventsの形式にそって送られます。 簡単なメトリクス確認はCloud Run用のダッシュボードで賄えます。

簡単な図

一文で流れを書いてみると、「Firestoreに書き込んだドキュメントが、非同期メッセージとしてCloud Pub/SubにPushされ、CloudEventsの形式でCloud Runへhttpリクエストで送信されます」のようになります。

publish side

シンプルにFirestoreへ書き込みます。 非同期メッセージとして扱われるので基本的にCreateのみ起きるようにしています。 場合によってはDeleteをトリガーにしますが、非同期メッセージをイベントとして扱う際はCreateです。 また、非同期メッセージとして扱うドキュメントはイミュータブルなモノとして扱いためUpdateをしないようにしています。 イベントして扱う場合には発行時間を必ずペイロードに含める、イベントが起きた操作を追跡可能にする識別子を含めるなど一定型にはめて、イベントの発生を非同期メッセージで表せるようにしています。

subscriber side

http requestのbodyがCloudEvents の形式にそっていることを前提にしています。 subscriberにFirestoreのドキュメントを受けとることを前提にサーバを実装できる小さなライブラリを用意してます。 そのライブラリではpublishした際に付与する一意性のある識別子を利用してメッセージを重複して処理しないように制御したり、メッセージを順序正しく処理できるようなユーティリティも実装しています。 なので実装側はシンプルに処理したいイベントと処理そのものをペアでコードが書けるようになっています。

例外系など省略しているが、「何が起きたら」「何する」をイベントハンドラには書いている

メッセージによる非同期処理を本番導入するまでに

今回説明した非同期処理は機能実装の最初のデプロイ時から利用していた訳ではありません。 大雑把に以下3ステップで本番環境での実現しました。

  1. gRPCのリクエストハンドラ内で同期的にイベントハンドラを実行する
  2. gRPCのリクエストハンドラ内で非同期的にイベントハンドラを実行する
  3. gRPCのリクエストハンドラ内ではイベントの永続化だけし、別プロセスでイベントハンドラを実行する ← 今回紹介した仕組み

1. gRPCのリクエストハンドラ内でプログラム上、同期的にイベントハンドラを実行する

この段階ではこの記事で説明している「非同期を導入するメリット」を受けられないわけです。 とはいえリクエスト数が多くなかったり、リクエストのパターンによって問題なく動作します。 ここでのミソは将来非同期処理を導入していこうと画策することでイベントを処理するイベントハンドラで実装することです。

イベントハンドラと呼び出し側でイベント以外を共有しないことで、イベントハンドラの実行場所が変わっても同じロジックがそのまま動きます。

実際のコードを説明ように簡単にかつ色々カットしています

2. gRPCのリクエストハンドラ内でプログラム上、非同期でイベントハンドラを実行する

この段階は3. への準備段階です。 事前にイベント(メッセージ)の流量が本番同等で実行し続けられるか検証はしていましたが、3. の導入はどんなリスクがあるのか未知数でした。

そこでfeature flagを利用して2. と3. の切り替え(切り戻し)は即時でできるようにしました。 findy-tools.io

2, 3の並走している場合、二重処理が問題になるならイベントハンドラ側の仕組みで二重処理は抑制されます。

結果的に3の仕組みに問題がなかったので切り戻しをすることはありませんでした。

UseCaseから特定のイベントハンドラに直接依存しない実現方法を実際にはとっていますが記事用にシンプルにしています

3. gRPCのリクエストハンドラ内ではイベントの永続化だけし、別プロセスでイベントハンドラを実行する

この段階に来ると記事内で紹介しているようにFirestoreへの永続化をトリガーにEventarc経由でCloud Runによりイベントハンドラが実行されます。 2ではプログラム上非同期であるものの、システム的には同期的にイベントハンドラが実行されていました。 それが3ではシステム的にも非同期になります。 なのでイベントを発生させるロジックからは完全に後続の処理を気にすることはなくなります。

サンキューEventarc

メッセージによる非同期処理を実現するのために参考としているのはアウトボックスパターンです。

https://microservices.io/patterns/data/transactional-outbox.html

FirestoreがEventarcを利用することでドキュメントの書き込みをトリガーをするのが容易で非常に助かったところでした。 MessageRelay実現のためにクラウド側の仕組みのトリガーによってやり取りできるのはデータベースをポーリングするより運用しているうえで手間や気にすることが少なくありがたいです。 サンキューEventarc!

次回に続く

次回も引き続きお届けチームによるイベント駆動設計への取り組みを紹介していきます。

お届けチームでは絶賛エンジニアを募集中です。カジュアル面談もwelcomeです。 ご応募お待ちしております。

open.talentio.com




以上の内容はhttps://product.10x.co.jp/entry/2025/05/30/093811より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14