データベース●●●○○

ACID

ACID ・ あしっど

トランザクションが守る4つの約束 — 原子性・一貫性・分離性・永続性の頭文字。

✦ 30秒で試す — 航海「絶対に壊れない」はずの約束に、わざと緩めるつまみがあるデータベースは、この更新をどうする?予想してみる

概要

ACID は、トランザクションが守るべき4つの性質、Atomicity(原子性)、Consistency(一貫性)、Isolation(分離性)、Durability(永続性)の頭文字です。「全部起きるか、何も起きないか」「決めたルールを破ったデータを残さない」「同時に走っても互いの途中経過を見ない」「確定したら消えない」という4つの約束をまとめて指す言葉で、RDBMS の信頼性はこの保証に支えられています。

「このデータベースは ACID に準拠しているか」「この処理では ACID のどれが破れうるか」のように、データの正しさを議論するときの共通語として使われます。障害・同時アクセス・電源断が重なっても業務データが壊れないのは当たり前のことではなく、この4つを実装で守り抜いた結果です。

なぜ生まれたか

1970年代、データベースの信頼性は製品ごとにばらばらの工夫で支えられていて、「トランザクションが正しく動く」とは具体的に何を意味するのかを語る共通の言葉がありませんでした。ジム・グレイが1981年の論文でトランザクションの概念を原子性・一貫性・永続性の観点から整理し、それを受けて1983年にテオ・ヘルダーとアンドレアス・ロイターが、障害からの回復の文脈で4つの性質を並べ、ACID と名付けました。

要件に名前が付いたことで、データベースを作る側は「何を保証すべきか」という明確な目標を持ち、使う側は「何を信頼してよいか」を製品をまたいで判断できるようになりました。仕様と実装を橋渡しする言葉として、ACID は40年以上経った今も現役です。

詳細

4つの性質

Atomicity(原子性) は「全か無か」の保証です。トランザクションの中の複数の操作は、すべて反映されるか、1つも反映されないかのどちらかです。途中で失敗すれば ROLLBACK で開始前の状態に戻ります。振込で「引き落としだけが残る」ことが起きないのはこの性質のおかげです。ここでいう「原子」は「それ以上分けられない」という意味で、並行処理の「アトミック操作」とは少し意味が違います(同時実行からの保護は分離性の役目です)。

Consistency(一貫性) は、トランザクションの前後でデータベースが決められたルール(制約)を満たし続ける保証です。「残高は0以上」「外部キーは実在する行を指す」といった制約に違反する変更は COMMIT できません。ただし、データベースが確かめられるのは宣言された制約だけです。「振込の前後で合計金額が変わらない」のような業務上の正しさは、原子性と分離性の上でアプリケーションが正しい処理を書いて初めて守られます。4つのうち C だけはデータベース単独の性質ではなく、アプリケーションとの共同作業だと言われるのはこのためです。

Isolation(分離性) は、同時に実行されるトランザクション同士が互いの途中経過を見ない保証です。理想は「1本ずつ順番に実行したのと同じ結果」ですが、厳密に守ると並行性が落ちるため、実際にはトランザクション分離レベルとして保証の強さを選べるようになっています。

Durability(永続性) は、COMMIT が成功を返した変更は、電源断やクラッシュが起きても失われない保証です。実装の中心は WAL(Write-Ahead Logging)で、データ本体より先に変更の記録をディスクへ書き切ってから COMMIT に応答します。再起動後はこのログを再生して、COMMIT 済みの状態を取り戻します。

BEGIN更新をWAL(ログ)に先行書き込みCOMMIT要求ログをディスクへ確実に書き切るCOMMIT完了を応答(以後は電源断でも失われない)
永続性を支える WAL の流れ。ログが書けた時点で永続性が確定する

4つとも「つまみ」がある

4つの性質は、いつも最大の強さで守られているわけではありません。分離性は分離レベルで緩められ、PostgreSQL の既定の READ COMMITTED はノンリピータブルリードを許します。永続性も緩められます。PostgreSQL の synchronous_commit = off や MySQL の innodb_flush_log_at_trx_commit = 2 は、ログのディスクへの書き込みを待たずに COMMIT に応答し、停電のときに直近の数百ミリ秒〜1秒ほどの COMMIT を失うことを受け入れて速度を得る設定です。一貫性は、どんな制約を宣言するか(あるいはしないか)で強さが決まります。原子性だけは緩める設定がほぼありませんが、自動コミットのまま複数の文を投げれば、そもそも原子性の外に出てしまいます。「ACID 準拠」という言葉は、設定しだいでどこまで守られるかが変わる、と理解しておくのが安全です。

4つの性質の関係

4つは独立した項目というより、支え合う関係にあります。原子性はログによる取り消しで実現され、そのログが永続性も支えます。分離性はロックや多版管理(MVCC、読み取り用に古い版を残して見せる方式)で実現されます。原子性・分離性・制約の検査がそろって、初めて一貫性が守られます。PostgreSQL や MySQL(InnoDB)は、どちらも WAL と MVCC の組み合わせで ACID を実装しています。

WAL(先行書き込み)ロック・MVCC制約の検査原子性 Atomicity永続性 Durability分離性 Isolation一貫性Consistency
実装の仕組みと4つの性質の支え合い。すべてがそろって一貫性が守られる

原子性・分離性・永続性がそれぞれ実際にどう働くかは、トランザクション(停電の位置を動かす)、トランザクション分離レベル(読む版を動かす)、WAL(電源を抜いて再生する)の各記事の体験で確かめられます。

ACID の C と CAP の C は別物

分散システムの話でよく出る CAP 定理にも C(Consistency)がありますが、ACID の C とは意味がまったく違います。ACID の C は「制約を守った状態から、制約を守った状態へ移る」ことです。CAP の C は、複数のレプリカ(複製)のどれに聞いても、最新の書き込みを反映した同じ値が返ること(線形化可能性)を指します。1台のデータベースでも ACID の C は議論できますが、CAP の C はデータが複数の場所にあって初めて問題になります。同じ「一貫性」という訳語が当てられているので、文脈でどちらの話かを確かめる必要があります。

分散させると何が難しくなるか

ACID の保証は、基本的に1台のデータベースの中で完結する話です。データを複数のノードやデータベースに分けたまま1つのトランザクションとして扱うには、全員が同じ結論(全員 COMMIT か、全員 ROLLBACK か)に達する仕組みが要ります。その古典的な方法が2相コミット(2PC)です。

DB2DB1調整役DB2DB1調整役全員がはいなら COMMIT と決めてログに書く指示が来るまでロックを握って待つPREPARE 確定できるかPREPARE 確定できるかはい ログに書いて約束したはい ログに書いて約束したCOMMITCOMMIT

第1相で調整役が各参加者に「確定できるか」を尋ね、参加者は変更をログに書き切ってから「はい」と約束します。第2相で、全員が「はい」なら調整役は COMMIT を決めて全員に伝え、1人でも「いいえ」なら ROLLBACK を伝えます。弱点は、参加者が「はい」と答えた後に調整役が落ちると、参加者は自分だけで COMMIT も ROLLBACK も決められず、ロックを握ったまま待ち続けることです。遅延と可用性の代償が大きいため、分散システム、とくにサービス間では避けられがちです。

さらに、ネットワークが分断されたとき、各ノードが最新の値を返すこと(CAP の C)と、どのノードも応答し続けること(A)は両立せず、どちらかを選ぶしかない、というのが CAP 定理の主張です。分断が起きていない平常時にも、ノード間で待ち合わせるほど遅くなる、という遅延と一貫性の取引があり、これは PACELC という枠組みで整理されます。

そこで NoSQL の世界では、保証を意図的に緩めた BASE(Basically Available, Soft state, Eventually consistent)という対の考え方が生まれました。一時的に古いデータが見えてもよいが、いずれ全ノードが一致する、という結果整合性を受け入れることで、大規模な分散と水平スケーリングを可能にします。一方で Google Spanner のように、分散しながら ACID トランザクションを提供するデータベースも実用化されています。実務では「注文と決済は ACID な RDBMS で、閲覧数の集計は結果整合性で」のように、データの性質ごとに必要な保証の強さを選ぶのが現代的な設計です。

試問

データベースで起きた8つの事故が並んでいます。トランザクション(まとめて1つとして扱う一連の更新)が守るはずの4つの約束 — 原子性(全部起きるか、何も起きないか)・一貫性(登録したルールを破った状態を残さない)・分離性(同時に走る処理が互いの途中経過を見ない)・永続性(確定した変更は停電でも消えない)— のどれが破れたのかを見分けます。

✦ 命題挑戦中

8つの事故を、破れた約束(原子性・一貫性・分離性・永続性)の箱へ仕分けよ

原子性全部か、何もなしか
一貫性登録したルールを守る
分離性途中経過を見せない
永続性確定したら消えない

項目を選んでから、入れる箱を選びます