2025-05-26
1つのDBであればORMのPrismaを使えばトランザクション実行することができますが、複数のDB間でのトランザクションはそうはいきません。 先日別々なDB間でのトランザクションを初めてSagaパターンを用いて分散トランザクションを実装したのでその時に調べたことや実装方法をまとめます。
Sagaパターンは、複数のサービスやデータベースにまたがるトランザクションを管理するための設計パターンです。 従来の2フェーズコミット(2PC)と異なり、Sagaパターンは各サービスで独立したトランザクションを実行し、失敗した場合には補償トランザクション(ロールバック)を実行して状態を元に戻します。
例えば、ECサイトでの注文処理を考えてみましょう:
これらの処理は別々のサービスで管理されており、それぞれが独自のデータベースを持っています。 従来の2フェーズコミットでは、すべてのサービスが同時にコミットできる状態になるまで待機する必要があり、パフォーマンスの低下やデッドロックのリスクがありました。
Sagaパターンでは、これらの処理を順番に実行し、途中で失敗した場合は、それまでに実行した処理を逆順でロールバックします:
このように、各ステップで独立したトランザクションを実行し、失敗時には補償トランザクションを実行することで、分散システム全体の整合性を保ちながら、高いパフォーマンスを実現できます。
マイクロサービスでSagaパターンを実装する場合、RabbitMQ
などのメッセージキューを使って、各サービス間でのトランザクションの状態を管理することが一般的なようなのですが、
今回は1つのサーバーで複数のDBを使うマイクロサービスと比較するとシンプルな場合の、Sagaパターンを実装してみます。
SagaManager.ts
SagaManager
クラスは、トランザクションの各ステップを管理し、エラー発生時のロールバックを実行します。
各ステップはexecute
とrollback
の2つの関数を持ち、実行結果を保持して必要に応じてロールバックできるようにしています。
AuthService.ts
この例ではユーザーとクライアントのDBが別となっています。
ユーザー情報の作成
クライアント情報の作成
Stripeでの顧客作成
メール認証トークンの作成