分散システムを守る「ACID準拠」とは？

# 導入

こんにちは、もんすんです。

データベースの信頼性を考える上で、トランザクションの管理は極めて重要な要素です。
その中でも、ACID準拠は、リレーショナルデータベースをはじめとする多くのシステムで広く採用されている基本原則です。  
今回は、ACID準拠について、データの整合性に関する問題も合わせて紹介していきます。

# ACID準拠とは？

ACID準拠(Atomicity, Consistency, Isolation, Durability)は、リレーショナルデータベースにおけるトランザクション管理の基本的な原則です。

これらの原則は、データベースが信頼性高く動作し、トランザクション中の**データの一貫性と整合性を確保するための保障**となります。
ACIDは、データベースにおける重要な操作が失敗することなく、正確かつ予測可能に行われることを保証します。

- **原子性 (Atomicity)**: トランザクション内のすべての操作は一括で実行されるか、まったく実行されないかのいずれかです。部分的に処理された場合、その変更はすべて元に戻されます。
- **一貫性 (Consistency)**: トランザクション開始前後で、データベースは常に整合性が取れた状態でなければならないという保証です。
- **隔離性 (Isolation)**: 同時に実行される複数のトランザクションは、互いに干渉することなく処理されるべきです。
- **耐久性 (Durability)**: トランザクションが成功すると、データは永続的に保存され、システムのクラッシュなどがあっても失われることはありません。

これらの原則に基づくACID準拠のデータベースは、ビジネスにおける重要なデータ操作を信頼できる形で実行してくれます。
異常が発生した場合でも一貫性を実現してくれるのです。

## ACIDの各要素の詳細

### 原子性 (Atomicity)

原子性は、トランザクション内のすべての操作が一括で処理されることを保証します。
つまり、トランザクションが成功するためには、**すべての操作が成功する必要**があり、**どれか1つでも失敗した場合、全体の変更が元に戻され**ます(ロールバック)。
これにより、途中でエラーが発生した場合でもデータベースは一貫した状態を維持できます。

**例**
銀行の口座振込処理では、送金元の口座からお金を引き出し、送金先の口座にお金を加算する必要があります。
もし送金元の口座からお金を引き出す操作は成功したが、送金先の口座への加算操作が失敗した場合、両方の操作がロールバックされ、元の状態に戻されます。

### 一貫性 (Consistency)

一貫性は、トランザクションの前後でデータベースが常に整合性の取れた状態に保たれることを保証します。
トランザクションが終了すると、データベースは**整合性チェック**をパスし、ビジネスルールや制約が満たされている状態になります。

**例**
銀行口座の振込処理では、送金元の口座に十分な残高がなければ、振込処理が行われません。
もし振込額が口座残高を超えている場合、トランザクションは一貫性の観点から失敗し、データベースは変更を加えません。

### 隔離性 (Isolation)

隔離性は、同時に実行される複数のトランザクションが互いに干渉しないようにすることを保証します。
異なるトランザクション間でデータの競合が発生しないよう、システムは**各トランザクションを独立**して扱います。

**例**
同時に複数のユーザーが銀行口座の残高を照会している場合、1人のユーザーが残高を変更しても、他のユーザーのトランザクションには影響を与えません。
これにより、データが中途半端な状態で見られることを防ぎます。

### 耐久性 (Durability)

耐久性は、トランザクションが成功した場合、その結果が永続的に保存されることを保証します。
システムの障害が発生しても、トランザクションの変更は失われません。

**例**
銀行の振込処理が成功した場合、その変更は永続的にデータベースに保存されます。
仮にシステムがクラッシュしても、次回起動時に処理が再実行されることはなく、振込金額は確実に反映されています。

# データの不整合に関する問題

ACID準拠を満たすことによって、データベースはトランザクションが整合性を保ちながら実行されることを保証します。
しかし、もしACID原則が守られない場合、データの不整合が発生する可能性があります。
以下は、代表的なデータ不整合の問題です。

## Dirty Reads

あるトランザクションが他のトランザクションが変更したデータを読み取ってしまう状況を指します。
トランザクション中の情報は、ロールバックされるかコミットされるかわからない不安定な状態にあります。
そんな不安定でクリーンではない情報(Dirty)を他のトランザクションから読み取れてしまう(Reads)というものです。
この場合、最初のトランザクションがロールバックされると、読み取ったデータが無効になり、データの整合性が失われます。

**例**
トランザクションAがアカウントからお金を引き出し、その変更をまだコミットしていない状態で、トランザクションBがその変更を読み取る。
この後、トランザクションAがロールバックされると、トランザクションBは無効なデータに基づいて処理を続けることになります。

## Non-repeatable Reads

同一のトランザクション内で同じデータを2度読み取ると、データが異なっている場合に発生します。
同じデータの読み取りをした(Reads)にもかかわらず、データが再現されない(Non-repeatable)というものです。
この現象は、別のトランザクションがそのデータを変更した場合に起こります。

**例**
トランザクションAが銀行口座の残高を2回読み取るが、トランザクションBがその間に残高を変更する。
　トランザクションAで読み取り→トランザクションBで残高変更→トランザクションBでコミット→トランザクションAで読み取り
このため、Aが最初に読み取った残高と2回目に読み取った残高が異なります。

## Phantom Reads

トランザクションがデータを読み取った後に、他のトランザクションが新しい行を挿入したり、削除したりすることで、最初の読み取り結果が変わる場合に発生します。
無かった(あった)はずのデータが、あった(無かった)ことになってしまうというものです。
このような変更が発生すると、トランザクションの結果に不一致が生じます。

**例**
トランザクションAが特定の条件で顧客リストを読み取ると、顧客Aと顧客Bがリストに含まれます。
その後、トランザクションBが顧客Cをリストに追加した後、トランザクションAが再度リストを読み取ると、顧客Cが追加されていることが分かります。
　トランザクションAで読み取り→トランザクションBで顧客Cを追加→トランザクションBでコミット→トランザクションAで読み取り
このような変更は、Aが期待していたデータに影響を与えます。

# ACID準拠の選択と実装

ACID準拠をどのように選択し、実装するかは、システムの要件や規模に大きく依存します。
適切な実装方法を選ぶことで、システムの安定性や信頼性を大きく向上させることができます。

## ACID準拠のデータベース選択

ACID準拠を満たすためのデータベース選択肢は多岐にわたります。
システムの規模や分散環境に応じて、以下のような選択肢が考えられます。

- **リレーショナルデータベース(RDBMS)**: MySQL、PostgreSQL、Oracleなど、一般的なリレーショナルデータベースは、ACID準拠を標準でサポートしています。これらは、データが単一のサーバーに保存されているシステムに最適です。
- **分散型データベース**: NoSQLデータベースの中でも、ACID準拠を提供するものがあります。例えば、`Fauna`のような分散型のACID準拠を実現するデータベースがあります。これらのシステムは、クラウドベースのシステムや地理的に分散した環境での使用に適しています。

## ACID準拠実装について

ACID準拠を実装する際には、以下のポイントを考慮することが重要です。

1. **トランザクションの粒度**: トランザクションの範囲や実行順序を適切に設計することが重要です。大きなトランザクションを小さく分けることで、システムのパフォーマンスを向上させることができます。
2. **エラーハンドリング**: すべてのエラーに対する適切なロールバックの機構を設けることが、データベースの一貫性を保つために必要です。
3. **スケーラビリティの確保**: 特に分散システムでは、ACID準拠を維持しながらスケーラビリティを確保するための工夫が必要です。負荷分散やレプリケーション、シャーディング技術を適切に組み合わせることが求められます。

# 終わりに

ACID準拠は、データベースシステムの信頼性を支える重要な概念です。
適切にACID原則を実装することで、システムの安定性を高めるだけでなく、データの整合性を保ち、ビジネスの信頼性を向上させることができます。

本記事が、システム設計やデータベース選定における参考になれば幸いです。