👻

HTTPとメッセージキューで学ぶ!今さら聞けないACIDトランザクションの基本

2025/01/23に公開

データベースのトランザクションにおける「ACID の4つの性質」を、HTTPプロトコルやメッセージキュー(MQ)を使ったアプリケーションのケースでわかりやすく解説します。


ACID のおさらい

ACID は以下の4つの性質を表します。

  1. Atomicity(原子性/不可分性)

    • 処理が「全部成功」か「全部失敗」か、どちらかしかないことを保証する。
    • 途中で失敗したら、何事も無かったかのように元の状態へ戻す。
  2. Consistency(一貫性/整合性)

    • システムやデータベースが定めるルールを常に守り、矛盾しないようにする。
    • 在庫数がマイナスになったり、整合性が壊れた状態を生じさせない。
  3. Isolation(独立性/隔離性)

    • 複数の処理が同時に走っても互いに干渉しない(かのように見える)ことを保証する。
    • 同時実行でもデータが不整合にならないよう、レベルに応じて分離する。
  4. Durability(耐久性/永続性)

    • コミット(確定)した結果は障害が起きても消えない。
    • データが正しくストレージに書き込まれていることを保証する。

1. HTTPプロトコルを使った場合のACID

1-1. Atomicity(原子性)

例:ECサイトの購入処理 (HTTP Request/Response)

  • ユーザーが「購入」ボタンを押すと、サーバーに POST /purchase などのリクエストが飛びます。
  • サーバーサイドではデータベース内で以下の処理が一つのトランザクションとして行われるとします。
    1. 注文テーブルに「注文情報」をINSERT
    2. 在庫テーブルの在庫数を更新(1つ減らす)
    3. 支払い情報のチェック・確定処理
  • どれか1つでも失敗したら、データベースのトランザクションをロールバックし、すべての処理を取り消します。
    • これが原子性です。
    • 「支払いだけ完了して在庫更新がされない」といった中途半端は残りません。

1-2. Consistency(一貫性)

例:在庫更新時のルール

  • 在庫数がマイナスになってはいけないなど、データベースやアプリケーションに設定された制約を守る必要があります。
  • HTTPリクエストが来たら、まず「在庫数 >= 1」であるかチェックし、不整合を起こさないようにします。
  • 一貫性が保たれない処理はそもそもデータベースへのコミット(確定)が失敗する設計にします。
    • これによって「マイナス在庫」といったおかしな状態を防ぎます。

1-3. Isolation(独立性)

例:同時購入リクエスト

  • 複数ユーザーが同じ商品を同時に購入しようとすると、HTTPリクエストがサーバーへ同時に飛んできます。
  • トランザクションの分離レベル(例えば READ COMMITTED など)を設定し、同じ在庫更新で競合が起こる場合、データベースは適切にロックをかけたりして同時実行でも整合性を壊さないようにします。
    • “同時アクセスが起きてもちゃんと正しい結果になる”というのがIsolationによるメリットです。

1-4. Durability(耐久性)

例:注文完了後の障害

  • ユーザーの注文が確定した瞬間にサーバーが落ちても、注文データが消えていたら問題です。
  • データベースがDurabilityを担保していれば、コミット後のデータは安定したストレージに保存されるため、再起動後も消えません。
    • だからこそユーザーは「確実に注文できる」と安心してHTTPリクエストを送れます。

2. メッセージキュー(MQ)を使った場合のACID

メッセージキュー(MQ)を使うと、非同期処理やマイクロサービス間の連携がスムーズになります。ただし、トランザクションをどう設計するかは少し工夫が必要です。

2-1. Atomicity(原子性)

例:受注処理 + MQ へのメッセージ送信

  • 受注処理(注文確定)と同時に、配送システムや在庫管理システムへ「注文確定メッセージ」を送るケースがあります。
  • このとき「DBの更新が成功したのにメッセージ送信が失敗する」とか「メッセージは送られたがDBの更新はロールバックされた」状態が起こると困ります。
  • そこで以下のようなやり方をすることが多いです:
    1. トランザクション内で“アウトボックス”テーブルへメッセージ情報をINSERT
    2. DBのコミットが成功したら、別のプロセスがアウトボックスのレコードを確認し、MQへ送信する。
    3. MQ送信が完了したら、アウトボックスのレコードを削除する。

この方式によって「DB更新」と「メッセージの整合性」をほぼ同期的に保つことができます。二重送信や不一致を防ぐ仕組みとしてよく使われています。

2-2. Consistency(一貫性)

  • アウトボックスを使う場合でも、データの不整合を引き起こさないようにアプリケーションとDBのルールを明確にしておきます。
  • たとえば「注文テーブル」と「アウトボックステーブル」の整合性を保つ制約を設けるなどして、メッセージと実際のDB状態が矛盾しないようにします。

2-3. Isolation(独立性)

  • メッセージを送る処理とDBの更新が同時に走ることもあるため、必要に応じてトランザクション分離レベルやロックを適切に設定します。
  • 同時に別のワーカーがアウトボックスを処理しようとしても、テーブルにロックをかける仕組みで競合を防ぐなどのアプローチを取ります。

2-4. Durability(耐久性)

  • MQへの送信が成功したら、MQ自体が「メッセージを永続化」する設定がある場合(例:RabbitMQのdurable queueやKafkaのログ)には、それ以降はメッセージが失われにくくなります。
  • 逆に、MQの設定を適当にすると障害発生時にメッセージが消えてしまうリスクもあるので、Durabilityを意識した設定(永続化モードの有効化など)が重要です。

まとめ

HTTPとMQを使う実装シーンでも、ACIDの考え方はベースとなります。

  • Atomicity(全部成功・全部失敗)
    • HTTPならサーバーサイドのDBトランザクションで担保。
    • MQならアウトボックスパターンなどを採用して整合を保つ。
  • Consistency(ルールを破らない、一貫した状態を守る)
    • 在庫がマイナスにならないなど、アプリ側とDB側の両面から整合性を保つ。
  • Isolation(同時実行でも干渉しないように見せる)
    • DBの分離レベルやロック機構で担保。
    • MQでは並列実行時のメッセージ処理もロックやワーカー管理を考慮。
  • Durability(コミット後は消えない)
    • DBの永続化機構による保護。
    • MQの耐久設定(durable queueやログストレージ)を活用。

これらを意識しておけば、大規模なECサイトから小規模なWebアプリまで、トランザクション周りの信頼性をしっかり支えることができます。「HTTPリクエストが飛んでくるごとにデータを更新する」「それをMQで他サービスに連携する」という流れの中で、ACIDをどう設計に組み込むかを理解するだけで、システム全体の堅牢性がグッと向上します。

Discussion