📄
概念 📚 software-design-concepts

整合性とトランザクション

ACID特性・分散トランザクション・最終的整合性とその実装パターン

トランザクションとは、複数のデータ操作を一つの論理単位として扱い、「すべて成功するか・すべて失敗するか」を保証する仕組みである。RDB における ACID 特性(Atomicity: 原子性、Consistency: 一貫性、Isolation: 独立性、Durability: 耐久性)は、単一データベース内でのトランザクション保証の標準的な定義である。一方、NoSQL や分散システムは高可用性とスケールアウトを優先するために ACID の一部を妥協し、BASE(Basically Available, Soft state, Eventually consistent)モデルを採用することが多い。

分散システムで複数サービスにまたがるトランザクションを実現するための古典的手法として 2フェーズコミット(2PC)があるが、コーディネーターの単一障害点・ブロッキング問題・性能限界という根本的な欠点がある。現代のマイクロサービスアーキテクチャでは Saga パターンが主流である。Saga はトランザクションを複数のローカルトランザクションに分解し、各ステップが失敗した場合に補償トランザクション(Compensating Transaction)を実行してロールバックの意味論を実現する。コレオグラフィ型(イベントを使ったサービス間協調)とオーケストレーション型(中央オーケストレーターが指示を出す)の二つの実装スタイルがある。

楽観的ロック(Optimistic Locking)はデータ読み取り時にロックを取得せず、更新時にバージョン番号や ETag で競合を検出する手法で、競合が稀な場合にスループットが高い。悲観的ロック(Pessimistic Locking)は読み取り時にロックを取得して他の書き込みをブロックする手法で、競合が頻繁な場合やビジネス上の整合性保証が絶対条件の場合に適する。冪等性(Idempotency)による補償は、同一操作の重複実行が副作用を持たないように設計することで、リトライと at-least-once 配信の安全性を確保する。

コードレビューで着目するポイント

  • トランザクション境界が集約(Aggregate)境界と一致しているか
  • 長時間トランザクションがデータベースのロック競合を引き起こす可能性があるか
  • 分散トランザクションが Saga パターンで設計されており、補償トランザクションが定義されているか
  • 楽観的ロックの競合検出(バージョン番号チェック)がアプリケーション層で正しく実装されているか
  • 冪等キーを用いて重複リクエストの副作用が防止されているか
  • 最終的整合性(Eventual Consistency)を前提とした UI・UX 設計がなされているか(例: 書き込み直後の読み取りで古いデータが返る可能性)
  • 2PC を使っている箇所でコーディネーター障害時の挙動が考慮されているか

典型的なアンチパターン

分散した2PC の使用: マイクロサービス間で XA トランザクションや2PC を用いる設計。コーディネーターがSPOFとなり、サービス全体の可用性を低下させる。Saga パターンへの移行を検討すべき。

補償トランザクションの未定義: Saga の各ステップに対応する補償処理(ロールバック相当の操作)を定義せずに実装すると、途中失敗時にデータが中途半端な状態に留まり、手動リカバリが必要になる。

楽観的ロックの競合を無視するリトライ: 楽観的ロックの競合例外を無制限にリトライすることで、書き込みが永遠に成功しないライブロック(Livelock)状態に陥る可能性がある。

参考リソース