トランザクショナルアウトボックス

データベースへの書き込みとイベント発行を原子的に行うための分散システムパターン

分散システムイベント駆動

トランザクショナルアウトボックスとは

トランザクショナルアウトボックス (Transactional Outbox) は、データベースへのビジネスデータの書き込みとイベントの発行を原子的に行うパターンである。「注文を保存したがイベントの発行に失敗した」「イベントは発行したが注文の保存に失敗した」という不整合を防ぐ。

問題: 二重書き込み

二重書き込みが起きる典型的なコード例を示す。

// ❌ 二重書き込み問題: DB 保存とイベント発行が別トランザクション
await db.orders.create(order);        // 1. DB に保存 (成功)
await sns.publish({ Message: order }); // 2. イベント発行 (失敗したら?)
// → 注文は保存されたが、イベントが発行されない → 下流システムが同期されない

DB 保存とメッセージ発行は異なるシステムのため、片方だけ成功する可能性がある。

解決: Outbox テーブル

解決: Outbox テーブルのコード例を示す。

// ✅ Outbox パターン: 同一トランザクションで DB 保存 + Outbox 書き込み
await db.transaction(async (tx) => {
  await tx.orders.create(order);
  await tx.outbox.create({
    id: uuid(),
    aggregateType: 'Order',
    eventType: 'OrderCreated',
    payload: JSON.stringify(order),
    createdAt: new Date(),
  });
});
// → 両方成功 or 両方失敗 (原子性が保証される)

Outbox テーブルに書き込まれたイベントは、別のプロセス (ポーラー) が定期的に読み取り、SNS/SQS に発行する。

アーキテクチャ

アプリケーションはビジネスデータと Outbox レコードを同一トランザクションで DB に書き込む。Outbox ポーラーまたは CDC (Change Data Capture) が Outbox テーブルの変更を検知し、メッセージングサービス (SNS、EventBridge) にイベントを発行する。DB 書き込みとイベント発行の原子性が保証される。

[アプリケーション]
  ↓ 同一トランザクション
[DB: orders テーブル] + [DB: outbox テーブル]
                              ↓ ポーリング or CDC
                        [Outbox ポーラー / CDC][SNS / SQS / EventBridge][下流サービス]

DynamoDB での Outbox

DynamoDB はトランザクション (TransactWriteItems) で複数テーブルへの書き込みを原子的に行える。

await ddb.send(new TransactWriteCommand({
  TransactItems: [
    { Put: { TableName: 'Orders', Item: order } },
    { Put: { TableName: 'Outbox', Item: {
      id: uuid(),
      eventType: 'OrderCreated',
      payload: JSON.stringify(order),
      ttl: Math.floor(Date.now() / 1000) + 86400,
    }}},
  ],
}));

DynamoDB Streams で Outbox テーブルの変更を検知し、Lambda でイベントを発行する。

CDC vs ポーリング

CDC とポーリングの違いを以下にまとめる。

方式仕組み遅延複雑さ
ポーリング定期的に Outbox テーブルをスキャン秒〜分低い
CDCDB のトランザクションログを読み取りミリ秒〜秒中程度
DynamoDB StreamsDynamoDB のネイティブ CDCミリ秒〜秒低い

DynamoDB Streams を使えば、ポーラーを自前で実装する必要がない。

イベントの重複配信

Outbox パターンでは、イベントが重複配信される可能性がある (at-least-once)。下流のコンシューマーはべき等に実装する必要がある。

トランザクショナルアウトボックスの関連書籍も参考になる。

この記事は役に立ちましたか?

関連用語

関連する記事