Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Spanner Change StreamによるTransactional Outboxでの ...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Gaudiy Dev Gaudiy Dev
October 14, 2025
1

Spanner Change StreamによるTransactional Outboxでの 分散トランザクションの高速化

Go Night Talks – After Conference 11社合同開催!Goエンジニア交流LTイベント

by 雨宮 悟(rail44) https://x.com/rail44

Avatar for Gaudiy Dev

Gaudiy Dev

October 14, 2025
Tweet

Transcript

  1. Goでマイクロサービスをやる - gRPCでBFF/サービス間の通信 - gokitをベースに⾃社独⾃の共通ライブラリをメンテナンス - ドメイン単位でマイクロサービスがk8sに展開 - データベースにはSpannerを採⽤ -

    全IPがインスタンスに同居している - IP毎のTenantIDを各テーブルのPKとして持っているので - マルチテナンシーを実現しつつ、⾃然に分散する感じ - インデックスだったり分散のためのキーだったりで普通のRDBMSと差はある - が、今回の話だと割と普通のトランザクショナルなデータベースとして捉えてもら えれば⼤丈夫です - コストは思われている程ではないのと、将来のスケールコストを先払いしているという 理解でいる - 最近だとtidb、Supabaseとか、pgsql本体の進化が強くて個⼈的な気持ちは揺らい でいるが……
  2. マイクロサービスのおさらい Pros - スケールさせやすい - パフォーマンスに問題のあるサービ スを個別でスケールアップ/スケール アウトできる - 影響範囲をチームの単位で切りやす

    い - コンウェイ/逆コンウェイ的な - (私たちのような)マルチテナントと の相性がよい - テナント毎に、提供したい機能で必 要なマイクロサービスを組み合わせ る Cons - サービス間通信でのデータの⼀貫性 - 疎結合が故のやつ - gRPCやOpenAPIのようなスキーマ 駆動である程度対策ができる - ランニングコスト - 私たちで⾔えば、スケールに伴うコ ストを先払いしている形 - サービス間で⼀貫したトランザク ションを実現しにくい - ↑これ!
  3. Saga vs Outbox - アプリケーションの処理フローとして、「失敗しうる」処理かどうか - 外部サービスへの依存の有無 - 処理が失敗した場合のハンドリングをOutboxで⾏おうとするとちょっと⾯倒 -

    PubSubのリトライ戦略やデッドレターの設計が必要になる - 「確実に成功させたい」処理 -> Outbox - 「失敗も含め結果整合性を得たい」-> Saga - フローの傾向として、Outboxの⽅がフィットするパターンは多い - アプリケーションの特性にもよるが - アプリ全体としてレジリエンシー重視をする判断 - 補償トランザクションの必要性を減らす - コードやコンポーネントの⾒通しがよくなる 決済システムでは、ステートマシンでSagaっぽく状態管理しつつ、他サービスとの協調はOutboxでやっている(余談)
  4. Spanner Change Stream - https://cloud.google.com/spanner/docs/change-streams?hl=ja - データベース全体や特定のテーブルに対し、指定した変更が発⽣したことを イベントとしてPublishする機能 - タイムスタンプを指定して、それ以降のイベントをストリームで受信できる

    - ただし、イベントの保持期間(7⽇)はある - 他のDBでも類似機能はあるはず - CDC、あるいはLISTEN / NOTIFY - 標準ではなくプラグインなら、SupabaseのRealtimeや、ETLはかなり近い
  5. Outbox with Change Stream - チームメンバーが書いた記事 - マイクロサービスの共通ライブラリ(gokit)に、OutboxへのPublishのための コンポーネントを実装 -

    ここで記事からコードサンプルを⾒せる - サービスのプロセス内に、Change Streamを購読するgoroutineを起動 - インスタンス間でのAtomic性のために、ロックを取りつつPubSubへPublish
  6. 運⽤の上での知⾒ - 結果整合性が得られるまでの時間が5s -> 200msに 🎉 - イベント単位での再送機構はない - 「タイムスタンプを指定して、それ以降のイベントをストリームで受信できる」

    - 従来のPollingの機構を、頻度を下げつつ継続運⽤している - サービスメトリクスが⼀⾒変な内容になる - 「クエリが⻑時間実⾏され続けている」みたいな扱いになる - 監視の調整をすればよい話ではありつつ - 新しめの機能で、公式SDKでのコネクションの扱いが不安定だったりした - 急にSpannerインスタンス側からコネクションがDropされたりする - フォールバック機構を⼊れつつ、サポート依頼を投げたり - Subscriberをサービスプロセスに同居させるかは議論の余地がある - k8sを採⽤しているので、Outbox Sidecarとしてインスタンス分離した⽅がサービスレ ベルは上げられそう
  7. まとめ - 分散トランザクションは難しいが、やる価値がある - マイクロサービスの⼤きな弱点の⼀つ - だからこそ、ここに決まったパターンを設定できると⼤きい - マイクロサービスのメリットを純粋に享受できる -

    普段の普通の機能を作るときに意識することが減り、開発の速が上げられる - Goで作っていると、マイクロサービスの基盤に⼿を⼊れやすい - 今回みたいなアイデアを、軽量にプロセス内で試せる - ⾮同期プログラミングの強⼒さと、システムプログラミングの武器が揃っている