0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[学習メモ]CAP定理とは

Posted at

CAP定理を完全に理解する - 分散システムの基礎

はじめに

この記事では、分散データベースシステムの設計において避けて通れない「CAP定理」について、具体例を交えながら徹底的に解説します。

CAP定理とは

分散データベースシステムでは、以下の3つの特性を同時に満たすことは不可能という定理です。

C: Consistency (一貫性)
A: Availability (可用性)
P: Partition tolerance (分断耐性)

重要: どれか2つしか選べません!

前提知識:分散データベースシステムとは

通常のデータベース(単一サーバー)

[1台のサーバー]
┌─────────────┐
│ データベース  │
│  全データ    │
└─────────────┘
      ↑
   ユーザー
  • すべてのデータが1台のマシンに保存
  • シンプルだが、故障したら全て停止
  • 処理能力に限界がある

分散データベース(複数サーバー)

[東京]          [大阪]          [福岡]
┌───────┐      ┌───────┐      ┌───────┐
│データA│◄─────►│データA│◄─────►│データA│
│データB│  同期  │データB│  同期  │データB│
└───────┘      └───────┘      └───────┘
   ↑             ↑             ↑
 ユーザー       ユーザー       ユーザー
  • データを複数のサーバーにコピー(レプリケーション)
  • 1台壊れても他が動く
  • 大量のアクセスを処理できる

CAPの3つの特性を詳しく解説

C: Consistency(一貫性)

定義: 全てのノードで、同じタイミングで同じデータが見える

: 銀行口座から1,000円引き出したら、どのATMで確認しても即座に残高が減っている

// 一貫性がある場合
await db.update({ balance: 9000 }); // サーバー1で更新

// 直後に別のサーバーで読み取り
const balance = await db.read(); // → 9000円(最新データ)

A: Availability(可用性)

定義: システムの一部が故障しても、必ず応答が返ってくる

: データセンターの1つがダウンしても、ユーザーは必ず何らかの応答を受け取れる

// 可用性が高い場合
try {
  const result = await db.query();
  // サーバーの一部が死んでいても、
  // 生きているサーバーから応答が返る
} catch (e) {
  // エラーにならない(タイムアウトしない)
}

P: Partition tolerance(分断耐性)

定義: ネットワーク障害でサーバー間の通信が切れても、システムが動き続ける

: 東京と大阪のデータセンター間の回線が切れても、両方のセンターが独立して動作し続ける

[正常時]
東京 ◄─────► 大阪
  ↓          ↓
ユーザー    ユーザー

[ネットワーク分断時]
東京    X    大阪  (通信不可)
  ↓          ↓
ユーザー    ユーザー
(両方とも動作し続ける)

具体例で理解する:チャットアプリのケース

シナリオ設定

世界規模のチャットアプリで、サーバーを2箇所に配置している状況を考えます。

[アメリカ]          [日本]
 サーバーA  ◄─────►  サーバーB
   ↑                  ↑
 アリス              太郎

正常時(分断なし)

// アリスがメッセージ送信(アメリカサーバー)
alice.send("Hello!");

// ↓ 即座に同期 ↓

// 太郎が受信(日本サーバー)
taro.receive("Hello!"); // ✓ 届く

ネットワーク分断が発生!

海底ケーブルが切断されました。

[アメリカ]          [日本]
 サーバーA    X  X    サーバーB
   ↑          通信不可    ↑
 アリス                 太郎

ここで**Partition(分断)**が発生しました。

Pがある場合(分断耐性あり)

// アメリカサーバーAの処理
alice.send("Hello!");
// → 日本サーバーと同期できないけど、ローカルに保存
// → "送信成功" と応答(システムは動き続ける)

// 日本サーバーBの処理
taro.send("こんにちは!");
// → アメリカサーバーと同期できないけど、ローカルに保存
// → "送信成功" と応答(システムは動き続ける)

// 結果:
// アメリカサーバー: ["Hello!"]
// 日本サーバー: ["こんにちは!"]
// ↑ 両方とも動作している(P がある)
// ↑ でもデータが違う...(一貫性は失われている)

結果:

  • ✓ アリスはアメリカの友達とチャット可能
  • ✓ 太郎は日本の友達とチャット可能
  • ✗ お互いのメッセージは見えない(後で同期される)

Pがない場合(分断耐性なし)

// アメリカサーバーAの処理
alice.send("Hello!");
// → 日本サーバーと同期できない...
// → エラー!システム停止!
throw new Error("Cannot sync with Japan server");

// 日本サーバーBの処理
taro.send("こんにちは!");
// → アメリカサーバーと同期できない...
// → エラー!システム停止!
throw new Error("Cannot sync with USA server");

// 結果:全世界でサービスが使えなくなる

なぜ3つ同時は不可能なのか?

シナリオ:ネットワーク分断が発生した場合

[状況]
東京サーバー: 残高 10,000円
大阪サーバー: 残高 10,000円
↓
ネットワーク切断!
↓
東京で 5,000円 引き出し

ここで3つの選択肢があります。

選択肢1: CP(一貫性 + 分断耐性)を選ぶ → Aを諦める

// 東京で引き出し操作
await tokyoDB.withdraw(5000);
// → 大阪と同期できない!
// → エラーを返す(可用性を犠牲)

// ユーザー体験:
"申し訳ございません。現在サービスを利用できません"

: 銀行システム、証券取引システム(PostgreSQL, MySQL)

選択肢2: AP(可用性 + 分断耐性)を選ぶ → Cを諦める

// 東京で引き出し
await tokyoDB.withdraw(5000);
// → OK! 残高 5,000円(一貫性を犠牲)

// 同時に大阪でも引き出し
await osakaDB.withdraw(3000);
// → OK! 残高 7,000円(一貫性を犠牲)

// ネットワーク復旧後...
// 東京: 5,000円、大阪: 7,000円
// → 矛盾!(後で調整が必要)

: SNS、ショッピングカート(DynamoDB, Cassandra, MongoDB)

選択肢3: CA(一貫性 + 可用性) → Pを諦める

分散システムでは実質的に不可能

理由:ネットワーク障害は必ず起きるため、
Pは「選択肢」ではなく「前提条件」

単一サーバーならCA可能だが、
それは「分散」システムではない

実際のデータベースでの適用例

CP系データベース

代表例: PostgreSQL, MySQL, MongoDB(設定次第)

適用シーン:

  • 銀行の残高管理
  • 在庫管理システム
  • 予約システム

特徴:

  • ✓ データ整合性が完璧
  • ✗ 障害時にサービス停止する可能性

実装例:

// トランザクションで厳密に管理
await db.transaction(async (tx) => {
  const account = await tx.account.findUnique({
    where: { id: 1 }
  });

  if (account.balance < 5000) {
    throw new Error('残高不足');
  }

  await tx.account.update({
    where: { id: 1 },
    data: { balance: account.balance - 5000 }
  });

  // ここで他のサーバーと同期できなければ
  // 全体がロールバック(エラーになる)
});

AP系データベース

代表例: DynamoDB, Cassandra, Couchbase

適用シーン:

  • SNSのタイムライン
  • ログ収集
  • ショッピングカート

特徴:

  • ✓ 常にサービスが使える
  • ✗ 一時的にデータが不整合(最終的整合性)

実装例:

// 最終的整合性(Eventually Consistent)
await dynamoDB.updateItem({
  TableName: 'accounts',
  Key: { id: 1 },
  UpdateExpression: 'SET balance = balance - :amount',
  ExpressionAttributeValues: { ':amount': 5000 }
});

// すぐに読み取ると古いデータの可能性
const account = await dynamoDB.getItem({
  TableName: 'accounts',
  Key: { id: 1 }
  // ConsistentRead: false (デフォルト)
});
// → balance: 10,000円 (古い値の可能性)

// 数秒後には整合性が取れる
setTimeout(async () => {
  const account = await dynamoDB.getItem({
    TableName: 'accounts',
    Key: { id: 1 }
  });
  // → balance: 5,000円 (正しい値)
}, 3000);

なぜPは避けられないのか

ネットワーク障害の原因(現実に起きる)

  1. 物理的な切断

    • 海底ケーブルが船の錨で切れる(年間数百件)
    • 工事でケーブルを掘り返す
    • 地震、台風、火災
  2. ソフトウェア障害

    • ルーターのバグ
    • ファイアウォールの誤設定
    • DDoS攻撃
  3. 遅延も「分断」と同じ

// タイムアウト設定: 3秒
await syncWithServer({ timeout: 3000 });
// → 5秒かかる場合
// → タイムアウトエラー = 実質「分断」

コードで見る分断耐性の実装

Pなし(素朴な実装)

async function saveData(data) {
  // 全サーバーに同期するまで待つ
  await server1.save(data);
  await server2.save(data);
  await server3.save(data);
  // ↑ 1つでも失敗したら全体が失敗

  return "成功";
}

// 問題:server2が通信できないと、全体が止まる

Pあり(分断に耐える実装)

async function saveData(data) {
  const results = await Promise.allSettled([
    server1.save(data),
    server2.save(data),
    server3.save(data)
  ]);

  // 過半数が成功すればOK(Quorum方式)
  const successCount = results.filter(
    r => r.status === 'fulfilled'
  ).length;

  if (successCount >= 2) {
    // 2台以上成功 → 処理続行(P がある)
    syncLater(failedServers); // 後で同期
    return "成功";
  } else {
    return "失敗";
  }
}

// 利点:1台が通信不可でも、システムは動く

実際の企業の例

Netflix(AP系)

// あなたが「お気に入り」に追加
await netflix.addFavorite(movieId);
// → すぐに成功(A: 可用性)

// でも他のデバイスでは...
const favorites = await netflix.getFavorites();
// → まだ反映されてない(C を諦めた)

// 数秒後に同期される

銀行システム(CP系)

// 引き出し操作
await bank.withdraw(5000);
// → 全サーバーと同期確認(C: 一貫性)

// もしネットワーク障害なら...
// → エラーになる(A を諦めた)
"システムメンテナンス中です"

AWS(Pあり)

AWSはAvailability Zone(AZ)の概念で分断耐性を実現しています。

[東京リージョン]
  AZ-A: データセンター1
  AZ-B: データセンター2
  AZ-C: データセンター3

1つのAZがダウンしても、
他のAZでサービス継続
→ これがPartition tolerance

まとめ

CAP定理の本質

  • C(一貫性): 全サーバーで同じデータが見える
  • A(可用性): 必ず応答が返ってくる
  • P(分断耐性): 通信障害でも動き続ける
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?