ソフトウェアアーキテクチャ

トランザクション設計 ― ACID/結果整合性/Saga/Outbox ― 生成AI時代のアーキテクチャ超入門

トランザクション設計 ― ACID/結果整合性/Saga/Outbox ― 生成AI時代のアーキテクチャ超入門

本記事について

当サイトを閲覧いただきありがとうございます。 本記事はシリーズ『生成AI時代のアーキテクチャ超入門』の「ソフトウェアアーキテクチャ」カテゴリ第6弾として、トランザクション設計について解説する記事です。

「全部成功するか、全部なかったことにする」を保証する仕組みで、銀行振込のようなお金が消えたり増えたりする事故を防ぐ設計の核です。本記事ではACID特性・結果整合性・Saga/Outboxパターン・冪等性まで解説し、業務ごとに整合性レベルを仕分ける判断軸を示します。

本記事のテーマについてさらに詳しく知りたい方は『システム設計のセオリーと実践方法がこれ1冊でしっかりわかる教科書』も参考にしてみてください。

この記事の結論

  • データごとに必要な整合性レベルを仕分ける
  • 1DB完結(ローカルトランザクション)を最優先する
  • 分散が必要ならSaga+Outbox+冪等性の三点セット(2PCは使わない)
  • リトライを入れる前に冪等性キーを設計する

この記事を読む前に

本記事はプログラムやAPIといった開発寄りの話が中心です。IT用語にあまり馴染みがない方は、基礎編の「プログラムとAPIの基本」を先に読んでおくと格段に分かりやすくなると思います。また、読んでいて分からない用語が出てきたときは用語集で調べながら読み進められます。

そもそもトランザクションとは何か

トランザクションとは、ざっくり言えば「一連の処理を”全部成功”か”全部なかったこと”のどちらかにする仕組み」です。

銀行のATM振込を想像してください。AさんからBさんに1万円を送るとき、「Aの口座から引く」「Bの口座に足す」は必ずセットで成功しなければなりません。片方だけ成功すると1万円が消えるか増えるかします。この「全部成功か全部取消か」を保証する仕組みがトランザクションです。

なぜトランザクション設計が重要なのか

もしトランザクション設計を曖昧にしたらどうなるか。お金が消えたり増えたりする事故が現実に起きます。一度データがズレると原因追跡・補正・顧客対応のコストが膨大になり、事業全体の信頼を揺るがします。

一方で、銀行口座のように厳格な整合性が必要なデータもあれば、SNSの「いいね数」のように多少ずれても構わないデータもあります。全てに最強の整合性を適用するとシステムが重くなりすぎるため、業務ごとに求める精度を分けるのが設計の肝です。そして全体構造(特にマイクロサービス)とセットで検討する必要があります。1DBで済むならACIDだけで十分で、分散トランザクションを避けられる構成に倒せるかどうかが運用コストに直結します。

ACID特性と分離レベル

RDBMSが提供する古典的なトランザクション保証がACIDです。

特性内容
Atomicity(原子性)全部成功か全部失敗か。中途半端な状態は許さない
Consistency(一貫性)制約違反時は必ず失敗させる
Isolation(独立性)並行実行しても互いに干渉しない
Durability(持続性)コミット後のデータは障害でも消えない

ACID「1つのDBの中でなら整合性を強く保証できる」という前提で設計されており、単一DBの範囲なら金融・会計・在庫等の強整合業務で十分手堅く機能します。

このうち性能とのトレードオフになるのが「I」(独立性)の分離レベルです。READ UNCOMMITTED→READ COMMITTED→REPEATABLE READ→SERIALIZABLEの順に厳格になり、その分並行性能は下がります。PostgreSQLのデフォルトはREAD COMMITTED、MySQL(InnoDB)はREPEATABLE READで、高分離は必要な箇所(在庫引当・金融取引)だけ個別にSERIALIZABLEへ昇格させるのが定石です。全テーブルに適用すると並行性能が数倍遅くなります。

分散トランザクションと結果整合性

複数のDBやサービスを横断する処理では、従来の2相コミット(2PC、全DBに「準備OK?」と聞いてから一斉コミットさせる方式)が使われてきましたが、どこか1ノードが止まると全体がブロックされるため、ネットワーク障害が日常のクラウドでは実質機能しません。クラウドネイティブ時代の原則は2PCを避けることです。

代わりに使うのが結果整合性Eventual Consistency)です。「即座には整合しないが、最終的には整合する」という緩い保証を許容し、書き込みは即座に成功させて他への伝播は非同期で行います。Amazonの在庫数・SNSのフォロワー数・再生回数など、「1秒古くても業務が成立するデータ」をこれで扱うことで、システム全体の可用性とスケーラビリティを確保できます。

なお分散システムの根幹にはCAP定理(整合性・可用性・分断耐性は同時に満たせない)があります。ネットワーク分断は現実に起きるため、実際の選択は「CP(エラーを出してでも正確に)」「AP(古い値を見せてでも動き続ける)」かの2択で、銀行・決済はCP、SNS・閲覧系はAPと業務要件から逆算します。

Sagaパターン ― 分散を現実的に運用する

Sagaパターンの処理フロー(ローカルTX+補償処理)

Sagaは、分散トランザクション「小さなローカルトランザクションの連鎖+補償処理」で実現するパターンです。ホテル予約なら「予約作成」「決済」「在庫確保」「通知」の各ステップを別サービスで実行し、途中で失敗したらそれまでの処理を取り消す補償処理を逆順に実行します。2PCのように全サービスをロックしないため、クラウドでも現実的に運用できます。

async function bookHotel(input: BookingInput) {
  const completed: Array<() => Promise<void>> = [];
  try {
    const reservation = await reservationSvc.create(input);
    completed.push(() => reservationSvc.cancel(reservation.id));

    const payment = await paymentSvc.charge(input.amount);
    completed.push(() => paymentSvc.refund(payment.id));

    await inventorySvc.reserve(input.roomId);
    completed.push(() => inventorySvc.release(input.roomId));

    await notificationSvc.notify(input.userId);
    return { ok: true, reservationId: reservation.id };
  } catch (err) {
    // 逆順に補償処理を実行(冪等性が前提)
    for (const compensate of completed.reverse()) {
      await compensate().catch(logCompensationFailure);
    }
    throw err;
  }
}

実装には、中央のオーケストレータが順序制御するOrchestration型と、イベント駆動で各サービスが自律的に動くChoreography型があります。既定はまずOrchestrationから入ります。流れが見えてデバッグしやすく、全体像が見えないシステムは運用が地獄になるからです。補償処理は必ず冪等に作ります。リトライで二重に走っても結果が変わらない構造でなければ、Saga自体が新たな整合性事故の原因になります。

Outboxパターンと冪等性 ― 分散の必須装備

Outboxパターンは、「DBへの書き込み」「メッセージキューへの送信」を整合させる手法です。ビジネスデータと一緒に outbox テーブルへ送信すべきメッセージを同一トランザクションで記録し、別プロセス(Relay)がそれを読んでキューに送信します。これにより、マイクロサービスで頻発する「DBは成功したがKafka送信に失敗」という事故を防げます。イベント駆動アーキテクチャを組むならほぼ必須です。

冪等性Idempotency)は、同じリクエストが複数回届いても結果が同じであることを保証する性質です。分散システムではネットワーク障害やタイムアウトによる再送が日常的に起き、決済APIが冪等でないと二重課金が発生します。クライアントがリクエストID(Idempotency-Key)を付与し、サーバーがそのIDで重複排除するのが定番で、後から入れるのは難しいため最初から設計します。

どう選べばいいのか ― 整合性レベルの仕分け

判断の軸は「そのデータが一瞬でもズレたら金銭的・法的・信用上の被害が出るか」です。業務担当者に「このデータが数秒ズレるとどう困るか」を具体的に聞いて逆算するのが最も確実です。

※ 2026年4月時点の業界の典型的な仕分けです。

データ種別整合性レベル実装方式
銀行口座残高・決済強整合(SERIALIZABLE)1DB内ACID
注文・在庫引当強整合1DB内ACID or Saga
会員登録・認証情報強整合1DB内ACID
税務・監査対象記録強整合+改竄防止ACID+WORM
いいね・閲覧数結果整合非同期集計
ログ・分析データ結果整合Kafka+DWH

「全部強整合」は性能が崩壊し、「全部結果整合」はビジネスが壊れます。強整合はコストが高い要件なので、本当に必要な業務だけに限定します。

3つのシナリオで考える

個人開発・スタートアップの場合

PostgreSQLの1DB内のACIDトランザクションで全て完結させましょう。モノリスと単一DBの構成であれば、分散トランザクションという難問は最初から存在しないのです。実はこれが、1人開発における最大の設計上のアドバンテージだと私は思っています。

個人・スタートアップ ― 1か月で出せる構成が正解 ― 生成AI時代のアーキテクチャ超入門senkohome.com/arch-intro-case-startup/

中小SaaSの場合

基本は1DB内のACIDを維持しつつ、決済やメールのような外部サービス連携にはTransactional Outboxパターンを導入する段階です。「DBコミットとStripe課金のどちらかだけ成功してしまった」という不整合を、リトライ可能な非同期処理で防げるようになります。

中小SaaS ― マネージドに寄せて少人数で回す ― 生成AI時代のアーキテクチャ超入門senkohome.com/arch-intro-case-saas/

大企業の場合

サービス分割後の整合性はSagaパターンと補償トランザクションで設計することになります。とは言っても、強整合が必要な業務(決済・在庫引当・会計)はそもそも同一サービス・同一DBに置くのが先であって、分散させてからSagaで頑張るのは順序が逆です。監査対象のデータについてはWORM保存も要件に含めてください。

大企業基幹系 ― 新しい技術より組織で成立する設計 ― 生成AI時代のアーキテクチャ超入門senkohome.com/arch-intro-case-enterprise/

AI判断軸 ― 1DB完結がAIの安全圏

AI駆動開発が前提になっても、分散トランザクションの本質的な難しさはAIでも解決できません。AIは表面的に正しく見えるコードを生成しがちで、DB境界を跨ぐ整合性ミスは本番で発覚します。

AIが生成するトランザクションコードの典型的な穴

AIに「注文処理を書いて」と指示すると、1DB内のACIDトランザクション(BEGIN→INSERT→UPDATE→COMMIT)は正確に書きます。問題は複数サービスを跨ぐ場面です。「決済に課金→在庫を減らす→注文を書く」という3ステップを指示すると、AIは各ステップを順番に呼ぶコードは書きますが、2番目が失敗した時の返金ロジック(補償処理)を書かないことが多い。ほかにも、メッセージ送信とDB書き込みの原子性(Outboxが必要な場面)、リトライ時の冪等性キー、タイムアウト後にサーバ側で成功していた場合のリカバリを見落とします。これらは「正常系は動くが異常系で壊れる」コードの典型で、単体テストでは見つかりません。

1DB完結 vs 分散トランザクションの複雑度比較 なるべく1DB完結にすると、AIが安全に書ける範囲が最大化される 1DB完結(ACID) BEGIN INSERT/UPDATE COMMIT 1 DB ACID保証 外部キー制約 楽観ロック AIが安全に書ける。定番パターンで精度◎ 分散トランザクション サービスA サービスB サービスC 必要な追加設計 Sagaパターン Outboxパターン 冪等性キー 補償処理 分散トレース 結果整合性の許容判断 → 人間でも難しい → AIにはさらに困難 → 異常系が複雑 AIが正しく書くのは極めて困難 1DB完結を最優先。分散が必要なら Saga + Outbox + 冪等性の三点セットで

冪等性キーの設計をAIに任せる場合の注意

AIに「このAPIに冪等性を追加して」と指示すると、Stripe APIの影響で学習データが豊富なため、Idempotency-Key ヘッダの基本パターンは正確に書いてくれます。ただし、キーの有効期限(24時間か7日か)、同一キーで異なるリクエストボディが来た場合の挙動(409を返すか無視するか)、キーの保管先(Redis / DB)といった設計判断はAIが自動で行わないため、ADR等で明文化してから実装を任せるのが現実的です。

やってはいけないこと

マイクロサービス横断・複数DB横断のトランザクションで事故る典型を、特に危険な6つに絞ります。

禁じ手なぜダメか → どうするか
2PCをクラウドで本番運用ネットワーク障害で全サービスがブロックする → Saga+結果整合性で設計する
冪等性キーなしでリトライ実装二重決済・在庫二重減算・通知の複数送信が起きる → リトライより先に冪等性を設計する
Sagaの補償処理を設計しない中途半端な状態(決済OK・在庫NG)が残る → 各ステップに冪等な補償処理を用意する
DB書き込みとキュー送信を別トランザクションで実行「DB成功・送信失敗」で整合性が破綻する → Outboxパターンを使う
全データにSERIALIZABLE並行性能が数倍遅くなる → 在庫・金融だけ個別に昇格させる
分散TXを自前実装Saga / Outboxを知らずに組むと必ず破綻する → 実績あるライブラリ(Temporal等)に任せる

Knight Capital 2012年事件は厳密には分散TXの事故ではありませんが、「古いコードが残った1台+リトライ暴走」で45分・4.4億ドル損失・会社消滅というシナリオは、冪等性と整合性の設計を怠った時の恐ろしさを示しています(詳細は付録「重大インシデント事例集」)。

筆者メモ ― 「タイムアウト=失敗」と決めつけた日

決済APIのタイムアウト対策として、クライアント側に3回リトライを入れたプロジェクトで、少数のユーザーに同じ金額が重複請求される事故が起きた、という事例があります。サーバー側の処理は成功していたのに応答が詰まっただけで、リトライが全部通ってしまったのです。

冪等性キーを入れていない限り、「タイムアウト=失敗」と決めつけて良い場面はほぼありません。ネットワーク越しの「成功したかどうか分からない」状態は分散システムでは日常的に発生します。リトライを入れる前に冪等性キーを先に設計するのが鉄則です。

決めるべきこと — 自分のプロジェクトでの答えは?

以下の項目について、自分のプロジェクトの答えを1〜2文で言語化してみてください。曖昧なまま着手すると、必ず後から「なぜそう決めたんだっけ」が問われます。

  • データごとの整合性要件(強整合 / 結果整合)
  • 分離レベル(業務が許容する最低ライン)
  • 分散トランザクションの扱い(Saga / Outbox / そもそも避ける)
  • リトライ方針と冪等性の確保
  • ロック戦略(楽観ロック / 悲観ロック)
  • CAPのどちら(CP / AP)を優先するか

言語化した答えはADRとして残します。書き方の具体例は以下の記事で解説しています。

ドキュメンテーション ― README+ADR+OpenAPIをGitに寄せる ― 生成AI時代のアーキテクチャ超入門senkohome.com/arch-intro-devops-docs/

この記事に関連する記事

まとめ

本記事はトランザクション設計について、ACID・分離レベル・Saga・Outbox・CAP定理・冪等性まで含めて解説しました。如何だったでしょうか。

データごとに整合性レベルを仕分け、1DB完結を最優先、分散が必要ならSaga+Outbox+冪等性の三点セット。これがAI時代も含めた2026年の現実解です。

次回はソフトウェアアーキテクチャカテゴリの最終記事、「認証・セッション」(サーバセッション/JWT/OAuth)について解説します。

シリーズ目次に戻る → 『生成AI時代のアーキテクチャ超入門』の歩き方

本記事で扱った内容の詳細は PostgreSQL 公式ドキュメント も合わせて参考にしてください。

それでは次の記事も閲覧いただけると幸いです。