Goイベント駆動アーキテクチャ実践:Outboxパターンと信頼性の高いトランザクションメッセージング

技术架构

概要

  • イベント駆動アーキテクチャ(EDA)は2026年のマイクロサービス疎結合の主流アプローチ
  • Outboxパターンはビジネスデータとメッセージを同一トランザクションで書き込み、デュアルライト一貫性問題を解決
  • At-Least-Once配信 + コンシューマー冪等性 = 本番級メッセージ信頼性
  • Go + Kafka + PostgreSQLは2026年EDAの黄金スタック
  • 原理からGo実装までの完全ソリューション。Outbox Relayとデッドレターキュー処理を含む

目次


なぜイベント駆動アーキテクチャか

課題 同期呼び出し イベント駆動
サービス結合 強結合、AはBの応答を待つ 疎結合、Aはイベント発行のみ
可用性 下流障害で全体障害 下流復旧後にバックログを消費
拡張性 新コンシューマーは呼び出し元変更 新コンシューマーはTopic購読のみ
ピーク処理 同期ブロック、雪崩リスク 非同期バッファリングでピーク平準化

適したシナリオ:注文作成→在庫/物流/ポイント/SMS通知(1書き込みN読み取り)、ユーザー登録、決済完了、CDC駆動の検索インデックス更新。

不向きなシナリオ:即時応答が必要なクエリ、強一貫性が必要な金融送金。


デュアルライト問題:EDAの最大の課題

注文サービスは:(1) PostgreSQLに書き込み、(2) Kafkaメッセージ送信が必要。

ステップ1成功・ステップ2失敗 → 注文は作成されたが在庫は不明 → オーバーセル!

ソリューション 一貫性 複雑度 推奨度
Outbox(ローカルメッセージテーブル) 強一貫 ★★★★★
分散トランザクション(2PC/XA) 強一貫 ★★(性能差)
Sagaパターン 結果整合 ★★★★(長トランザクション)
メッセージ先→DB後 不整合 ★(非推奨)

Outboxパターンがほとんどのシナリオで最適解。


Outboxパターンの原理と実装

「送信するメッセージ」をレコードとして、ビジネスデータと同一DBトランザクションで書き込む。独立したRelayプロセスがOutboxテーブルを非同期読み取り、Kafkaに配信。

CREATE TABLE outbox_events (
    id            BIGSERIAL PRIMARY KEY,
    aggregate_type VARCHAR(64) NOT NULL,
    aggregate_id   VARCHAR(64) NOT NULL,
    event_type     VARCHAR(128) NOT NULL,
    payload        JSONB NOT NULL,
    status         VARCHAR(20) NOT NULL DEFAULT 'pending',
    created_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    published_at   TIMESTAMPTZ,
    retry_count    INT NOT NULL DEFAULT 0
);

CREATE INDEX idx_outbox_pending ON outbox_events (created_at)
    WHERE status = 'pending';
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*Order, error) {
    tx, err := s.db.BeginTx(ctx, nil)
    if err != nil {
        return nil, err
    }
    defer tx.Rollback()

    order := &Order{ID: uuid.New().String(), UserID: req.UserID, Amount: req.Amount, Status: "created"}

    _, err = tx.ExecContext(ctx,
        `INSERT INTO orders (id, user_id, amount, status) VALUES ($1, $2, $3, $4)`,
        order.ID, order.UserID, order.Amount, order.Status,
    )
    if err != nil {
        return nil, err
    }

    eventPayload, _ := json.Marshal(map[string]interface{}{
        "order_id": order.ID, "user_id": order.UserID, "amount": order.Amount, "items": req.Items,
    })

    _, err = tx.ExecContext(ctx,
        `INSERT INTO outbox_events (aggregate_type, aggregate_id, event_type, payload)
         VALUES ($1, $2, $3, $4)`,
        "order", order.ID, "order.created", eventPayload,
    )
    if err != nil {
        return nil, err
    }

    return order, tx.Commit()
}

注文とイベントは同一トランザクションでコミット — 両方成功か両方失敗。


Go Outbox Relay完全実装

FOR UPDATE SKIP LOCKED:複数Relayインスタンスが同じレコードを奪い合わない。PostgreSQLが他トランザクションでロックされた行をスキップ。

aggregate_idでパーティション分割し、同一注文のイベント順序を保証。

失敗時:retry_countが閾値超過 → failedマーク → デッドレターキュー。


Kafkaコンシューマーと冪等処理

At-Least-Once配信ではコンシューマー側の冪等性が必須:

func (h *InventoryHandler) HandleOrderCreated(ctx context.Context, msg *kafka.Message) error {
    eventID := getHeader(msg, "event_id")

    var processed bool
    h.db.QueryRowContext(ctx,
        `SELECT EXISTS(SELECT 1 FROM processed_events WHERE event_id = $1)`, eventID,
    ).Scan(&processed)
    if processed {
        return nil
    }

    tx, _ := h.db.BeginTx(ctx, nil)
    defer tx.Rollback()

    // ビジネスロジック + processed_events INSERT
    return tx.Commit()
}

イベントスキーマ設計とバージョン管理

CloudEvents標準フォーマットを使用。後方互換スキーマ進化戦略:

戦略 説明 適用シナリオ
後方互換 新フィールドはoptional ほとんどのシナリオ
二重書き込み移行 v1とv2イベントを同時発行 重大なスキーマ変更
コンシューマーバージョンルーティング 異なるコンシューマーグループが異なるバージョンTopicを購読 マルチチーム独立進化

本番デプロイと監視

メトリクス アラート閾値 意味
outbox_pending_count 5分間>1000 Relayが追いつかない
outbox_oldest_pending_age >60秒 メッセージ遅延過大
outbox_failed_count >0 配信失敗イベントあり
relay_publish_latency_p99 >5秒 Relay性能低下

面接ポイントとアーキテクチャ選定

Q1:OutboxとCDCの違いは?

Outboxはアプリケーション層でビジネスセマンティクス(order.created)を表現。CDCはDB層で行レベル変更(INSERT/UPDATE)をキャプチャ。複雑なビジネスはOutbox、シンプルなデータ同期はCDC。

Q2:Relayがダウンしたら?

Outboxテーブルのpendingイベントは失われない。Relay復旧後に配信再開。長時間ダウン時は遅延が発生するがメッセージは失われない。

Q3:Outboxテーブルは無限に増える?

増える。publishedイベントは定期アーカイブまたは削除。7日保持推奨、日付パーティションテーブル併用。

Q4:イベント駆動とSagaの選び方は?

イベント駆動は1書き込みN読み取りの通知シナリオ。Sagaは注文→決済→発送→確認のような長いクロスサービストランザクション(任意ステップ失敗時に補償)。両方組み合わせ可能。


Outbox vs CDC(Debezium)の選び方

Outbox はビジネスセマンティクス、CDC はゼロ侵入だがセマンティクスが弱い。

Saga パターンと Outbox の組み合わせ

オーケストレーション型 Saga で補償ロジック、Outbox イベントで下流に非同期通知。

DLQ とフルチェーン可観測性

リトライ超過で DLQ、Kafka Header で traceparent 伝播。

2026年トレンド

Outbox スターター標準化、Event Sourcing 復活、CloudEvents 標準化、イベント駆動 + AI。


まとめと関連記事

イベント駆動アーキテクチャの核心課題はメッセージ信頼性。Outboxパターンは「同一トランザクション書き込み」でデュアルライト一貫性問題をエレガントに解決。

設計要点

  1. デュアルライト一貫性はEDA最大の課題、Outboxが最適解
  2. ビジネスデータとイベントは同一DBトランザクションでコミット
  3. RelayはFOR UPDATE SKIP LOCKEDでマルチインスタンス対応
  4. コンシューマーは冪等必須:processed_eventsテーブル + ビジネス一意制約
  5. イベントスキーマはCloudEvents標準、後方互換で進化

関連記事

参考資料

ブラウザローカルツールを無料で試す →

#事件驱动架构#Outbox模式#Go微服务#事务消息#Kafka#2026