← 一覧へ
SW工学・管理
#TCC#분산트랜잭션#보상트랜잭션#MSA#자원예약
最終更新 · 2026-11-06

TCC(Try-Confirm-Cancel)分散トランザクションパターン

1. 概要

A. 定義

TCC(Try-Confirm-Cancel)とは、一つの分散トランザクションを参加サービスごとにTry(資源予約)・Confirm(予約確定)・Cancel(予約取消)の三つの操作に分解し、全サービスのTryが成功すればConfirmを、いずれか一つでも失敗すればCancelを呼び出すことで、アプリケーションレベルで2相コミットの意味論を実現する補償ベースの分散トランザクションパターンである。

TCCは、2PCの「準備(prepare)-コミット(commit)」構造を業務ロジックへ引き上げたパターンとして理解すると明確である。2PCが資源マネージャ(DB)のロックとundo/redoログに依存して資源を押さえるのに対し、TCCは各サービスが自らの資源を「予約・確定・取消」する三つの冪等APIを自ら公開し、調整者がこの三操作を2相(1相Try、2相Confirm/Cancel)で呼び出す。そのためTCCはしばしば「業務レベルの2PC(application-level 2PC)」あるいは補償型(compensation-based)トランザクションと呼ばれる。Pat HellandがCIDR 2007論文Life beyond Distributed Transactionsで提示した暫定操作(tentative operation)・予約(reservation)の概念が思想的源流であり、AlibabaのSeata TCCモード等を通じて実務パターンとして広く定着した。

B. 登場背景と必要性

マイクロサービスアーキテクチャ(MSA)はDB per Service原則に従うため、一つの業務(例:電子商取引の注文=口座引落 + 在庫減少 + ポイント積立)が互いに異なるサービスと物理DBにまたがって実行される。モノリシック時代のように単一のCOMMITで原子性を保証できないため、分散トランザクション戦略が必要になる。従来の解法である2相コミット(2PC/XA)は強い一貫性を与えるが、1相prepare以降、コミットが終わるまでDBロックを長時間占有して処理量と可用性を下げ、調整者障害時に参加者が資源を握ったまま止まるブロッキング問題がある。これは大規模なインターネット・金融トラフィックと合わない。

Sagaは各ステップを即座にコミットされるローカルトランザクションで処理し、失敗を事後の補償で是正して可用性を得るが、中間ステップの変更が即座に外部に露出(dirty read)し、隔離性がないという弱点がある。例えば決済サービスが実際に金を引き落とした後に在庫ステップが失敗すると、返金(補償)の前まで顧客口座には既に引落が見える。

TCCはこの二つの極端の間の折衷である。核心的な発想は「即座に確定せず、まず予約せよ」である。1相Tryでは実資源を消費する代わりに一部を隔離・凍結(例:利用可能在庫100 → 利用可能90・凍結10)だけしておき、全参加者の予約が成功した後にはじめて2相Confirmで凍結分を実際に確定する。おかげでDBロックを長く握らずに(2PC比)、中間状態の露出を資源凍結で抑制(Saga比)する準(じゅん)隔離性を確保する。すなわちTCCは「資源を物理的にロックする代わりに、業務データに予約状態を置いて論理的に隔離する」戦略である。

2. 全体構造と動作原理

TCCシステムは、グローバルトランザクションを指揮するトランザクション調整者(TM/TC)と、各自try/confirm/cancelの三インタフェースを実装したTCC参加者(Resource)から構成される。調整者は1相で全参加者のTryを呼び出し、その結果に応じて2相で全員Confirmまたは全員Cancelを呼び出す。

flowchart TB
    subgraph APP["アプリケーション(グローバルトランザクション開始)"]
      TM["トランザクション調整者(TM/TC)"]
    end
    subgraph P1["口座サービス"]
      A1["try: 引落額を凍結"]
      A2["confirm: 凍結→実引落"]
      A3["cancel: 凍結解除"]
    end
    subgraph P2["在庫サービス"]
      B1["try: 在庫予約(利用可能↓・凍結↑)"]
      B2["confirm: 予約→実控除"]
      B3["cancel: 予約解除"]
    end
    subgraph P3["ポイントサービス"]
      C1["try: 積立予約"]
      C2["confirm: 積立確定"]
      C3["cancel: 予約取消"]
    end
    TM -- "1相 Try" --> A1 & B1 & C1
    TM -- "2相 Confirm(全て成功時)" --> A2 & B2 & C2
    TM -- "2相 Cancel(いずれか失敗時)" --> A3 & B3 & C3

上記構造の要点は、各資源が自らの三つの状態遷移を責任を持って担うことである。調整者は「何を予約・確定・取消するか」の業務意味を知らないまま二相の呼び出し順序のみを保証し、実際のデータ操作は各サービスが担う。この関心の分離のおかげで、調整者は汎用(フレームワーク)として、資源ロジックはドメイン別に独立して進化できる。

正常フローと失敗フローを時間軸で見ると次の通りである。1相でいずれか一つでもTryが失敗すると、調整者は直ちに2相をCancelへ転換し、全員成功時にのみConfirmへ進む。

sequenceDiagram
    participant TM as 調整者(TC)
    participant Acc as 口座
    participant Inv as 在庫
    participant Pt as ポイント
    Note over TM,Pt: 1相 — Try(予約)
    TM->>Acc: try(引落1万ウォン凍結)
    Acc-->>TM: OK
    TM->>Inv: try(商品1個予約)
    Inv-->>TM: OK
    TM->>Pt: try(100p積立予約)
    Pt-->>TM: FAIL(限度超過)
    Note over TM,Pt: 2相 — いずれか失敗 → 全員Cancel
    TM->>Acc: cancel(凍結解除)
    TM->>Inv: cancel(予約解除)
    TM->>Pt: cancel(空ロールバック)
    Note right of TM: 全Tryが成功していれば<br/>代わりにconfirmを一括呼出

A. 1相 — Try(資源予約・検証)

Tryは業務可能性検査 + 資源隔離を遂行するステップである。単に「できるか」を確認するにとどまらず、後続のConfirmが必ず成功できるよう、必要な資源をあらかじめ凍結(freeze)しておく。例えば口座サービスのTryは残高から1万ウォンを即座に引く代わりに、利用可能残高 -= 1万, 凍結残高 += 1万で状態のみを移す。こうすれば同時に入ってきた別のトランザクションが同じ1万ウォンを重複使用できないので隔離性が確保され、同時に実際の引落はまだ起きていないので巻き戻し(Cancel)が安価である。したがってTry設計の品質がTCC全体の整合性を左右し、「Confirmが失敗し得ないほどTryで十分に検証・予約する」ことが核心原則である。

B. 2相 — Confirm(予約確定)

ConfirmはTryで凍結しておいた資源を実際に消費するステップである。口座サービスなら凍結残高 -= 1万で引落を確定し、在庫サービスなら予約分を実在庫から控除する。Confirmの重要な設計前提は「決して失敗してはならない」ことである。既にTryで全ての検証と予約が終わっているので、Confirmは追加検査をせず予約された資源を確定のみする。もしネットワーク障害等でConfirmが一時失敗すると、調整者は成功するまで再試行(at-least-once)するので、Confirmは冪等(idempotent)でなければならない。この「Confirmは再試行で必ず成功させる」という前提が、Sagaの補償と異なるTCC固有の性格である。

C. 2相 — Cancel(予約取消)

Cancelは1相でいずれか一つでもTryが失敗したとき、既に成功した参加者の凍結資源を原状復旧するステップである。口座のCancelは凍結残高 -= 1万, 利用可能残高 += 1万で巻き戻す。Tryで実消費ではなく凍結のみしたので、CancelはSagaの意味的補償(返金のような新たな順方向取引)より単純で安全である。Cancelも再試行され得るので冪等でなければならない。

3. TCCの3大整合性課題(Seata基準)

TCCを実際に実装する際、必ず解決すべき三つの異常状況があり、Apache SeataのTCC設計原則文書がこれを標準的に整理する。この三課題を扱えないと、凍結資源が永久に縛られるかデータが壊れる。

課題 発生状況 標準処理方法
冪等性(Idempotence) ネットワーク再試行でTry/Confirm/Cancelが2回以上呼ばれる トランザクション状態をログテーブルに記録し、既に処理済みの要求は読み飛ばす(1回と複数回の実行結果が同一)
空ロールバック(Empty Rollback) Tryが到達していないのにCancelが先に呼ばれる Try実行有無を記録し、Try記録がなければCancelは何もせず成功を返す
宙吊り(Hanging) 遅延したTryがCancelより遅く到着し凍結が永久残存 Cancelがログを残し、Tryは当該Cancel記録があれば実行を拒否する

冪等性が必要な理由は、分散環境の通信が本質的にat-least-onceだからである。調整者は応答を受け取れないと成功するまで再呼出するが、実際には前の呼出が成功したのに応答だけ失われた場合が多い。このときConfirmが冪等でないと引落が二度確定される二重控除が発生する。実務ではトランザクションID別の状態フィールドを置き「状態が既にCONFIRMEDなら再確定しない」方式で、またはUPDATEの影響行数を検査して0件なら読み飛ばす方式で実装する。

空ロールバックは、調整者がTry呼出の結果を受け取れず(Tryがネットワーク問題で参加者にそもそも到達しない)、トランザクションをロールバック決定してCancelを呼ぶときに生じる。参加者の立場では予約したことのない資源を取り消せという要求なので、Try記録がなければCancelを空(くう)処理し成功を返さねばならない。そうせず無条件に資源を復旧すると、存在しない凍結を解いて残高が増える誤りが起きる。

宙吊り(懸垂、suspension)は空ロールバックの後続リスクである。Tryがネットワーク遅延でCancel処理の後に遅れて到着すると、このTryが資源を凍結するが、既にグローバルトランザクションは終わっているのでその凍結を解いてくれるConfirm・Cancelが二度と来ない。結果的に凍結資源が永久にロックされる。解決策は、Cancelが「このトランザクションは取消済み」をログに残し、遅れたTryはその記録を確認して自ら実行を拒否するようにすることである。この三課題は互いに絡んでいるので、通常は一つのトランザクション状態ログテーブルで一括管理する。

4. 2PC・Sagaとの比較

三つのパターンはいずれも分散トランザクションを扱うが、一貫性・隔離性・資源の押さえ方で根本的に異なる。単純な項目列挙ではなく「なぜその差が生じるか」を見れば選択基準が明確になる。

区分 2PC(XA) TCC Saga
一貫性 強い一貫性 準強(事実上短い窓の結果的) 結果的一貫性
隔離性 DBロックで保証 資源凍結で準隔離 なし(中間露出)
ロールバック方式 DB undoログ Cancel(予約解除) 意味的補償取引
資源占有 長い(コミットまでロック) 短い(凍結のみ) ほぼなし
侵襲性 低い(DBが処理) 高い(3 API実装) 中間(補償ロジック)
適合領域 少数・同質DB 金融・決済等の強一貫要求 長期・緩い結合のフロー

2PCはDB資源マネージャがロックとログで隔離・復旧を代行するのでアプリケーション侵襲性が低いが、その代価としてロックを長く占有し拡張性が悪い。TCCは隔離・復旧の責任をアプリケーションへ移転しロック占有を凍結水準に減らしたが、全参加サービスが三つのAPIを直接実装せねばならないので開発費用と侵襲性が高い。Sagaは隔離性をそもそも放棄する代わりに実装が最も緩く長期実行フローに適するが、中間状態が露出し隔離性が重要な金融取引には不適である。したがって「強い一貫性と短い資源占有を同時に」要求する決済・口座振替・在庫控除のような領域がTCCの典型的適用先であり、緩いワークフロー(旅行予約全体フロー等)にはSagaがより適する。

5. 深化 — 実務適用とフレームワーク動向

TCCは中国の大型インターネット企業の高トラフィック金融トランザクションで特に広く使われる。代表的にAlibaba/Ant系列の決済・口座システムは毎秒数万件の振替を処理しながら2PCのロック占有に耐えられず、資源凍結ベースのTCCを核心パターンとして採択した。この経験がオープンソース化されたものがApache SeataのTCCモードで、開発者は@TwoPhaseBusinessActionでTryメソッドを宣言しcommit/rollbackメソッド名を指定すれば、Seataのトランザクションコーディネータ(TC)がグローバルトランザクションログを管理し、2相呼出・再試行・冪等/空ロールバック/宙吊り防御を支援する。その他にHmily、ByteTCC、TCC-Transaction等のJava陣営のTCCフレームワークが類似モデルを提供する。

具体的事例として銀行口座振替を見ると、引落口座のTryは振替額を凍結し、入金口座のTryは「入金予定」レコードを生成する。二つのTryがいずれも成功するとConfirmで引落口座は凍結分を実控除し、入金口座は予定額を実残高に反映する。片方でも失敗するとCancelで凍結・予定レコードを巻き戻す。この過程で顧客が見る利用可能残高はTry即時に減るが、実際の会計上の引落はConfirmで確定するので二重使用が原則遮断される。別の事例である航空券予約では、座席Tryが特定座席を「HOLD」状態で数分間予約し、決済Tryが成功した後にConfirmで「CONFIRMED」に確定する方式で、よく見る「N分以内に決済」座席ホールドのUXがまさにTCCの資源凍結モデルと接している。

最新動向としては、純粋TCCの高い実装費用(全サービスが3倍のAPIを作成)を減らすため、SeataがTCCと共にAT(Automatic Transaction)モード(DBプロキシがundoログを自動生成し開発者作業を最小化)を提供し、状況に応じてTCC/AT/Saga/XAモードを混用する設計が拡散している。また、メッセージベースの信頼性あるイベント(Transactional Outbox)とTCCを組み合わせ、強一貫がどうしても必要な核心ステップのみTCCで、残りはイベントで緩く処理するハイブリッドトランザクション設計が実務の主流になっている。

6. 考慮事項および示唆点(技術士の観点)

TCC導入は単なるライブラリ選択ではなくデータモデル・運用・組織全般のトレードオフを伴うので、次を戦略的に判断せねばならない。

  • 適用範囲の選別(適用戦略): 全てのトランザクションをTCCで包むと開発・運用費用が急増する。強い隔離と短い占有が同時に必要な核心業務(決済・振替・在庫)にのみ選択的に適用し、緩いフローはSaga・イベントで分離する混合戦略が現実的である。「一貫性要求水準に応じてパターンを差別適用する」設計力が鍵である。

  • 実装侵襲性と設計難度(トレードオフ): TCCは参加サービスごとにTry・Confirm・Cancelの三操作と冪等性・空ロールバック・宙吊り防御を全て実装せねばならないので、サービス当たりのコード量とテスト負担が大きく増える。特にConfirmを「決して失敗しないように」作るにはTryでの十分な予約が前提となり、ドメインデータモデルに凍結・予約状態フィールドを追加する侵襲的設計が不可避である。レガシー・外部サービスのように3つのAPIを強制できない場合は適用が制限される。

  • 隔離性の副作用と観測性(運用): TCCの準隔離は「しばらくの資源凍結」の窓を前提とするので、Try後Confirmまでの時間が長くなると凍結資源が積もり利用可能量枯渇・デッドロックが来得る。調整者障害や参加者無応答で未完結トランザクションが残ると凍結が解消されないので、グローバルトランザクションログ監視、タイムアウトベースの自動Cancel、未決トランザクション手動介入コンソール等の運用観測性が必須である。

  • データ整合性検証と補完(展望・連携): at-least-once通信の上で冪等キー・状態ログで正確性を守らねばならないので、定期的な突合(reconciliation)バッチで凍結-確定状態を交差検証する安全網を置くべきである。連携技術としてはSaga・Transactional Outbox・CDCが自然に結合し、今後トランザクションメッシュフレームワーク(Seata等)のマルチモード発展とサーバーレス/イベントアーキテクチャ拡散の中でTCCは「強一貫が必要な地点の精密ツール」として位置づけられる展望である。

参考資料


一言まとめ: TCCは分散トランザクションを参加サービス別のTry(予約)・Confirm(確定)・Cancel(取消)操作に分解し、2PCの長いロック占有なしにSagaより強い準隔離性を得るアプリケーションレベルの2相コミットパターンであり、冪等性・空ロールバック・宙吊り防御が実装の成否を左右する。