分散データシステム

目次

概要

データを複数台へ分けたとき、何が難しくなるか

データベースを1台で持てなくなると、複製、分割、整合性、障害時の切り替えが問題になります。分散データシステムは、性能を上げるためだけでなく、可用性と地理的分散のためにも必要になります。

要点

分散データシステムの中心課題は、速さよりも「壊れたときにどの性質を守るか」です。複製、分割、整合性、障害回復を一緒に考えます。

この章で重視すること

  • レプリケーションシャーディングの役割の違いを理解する
  • 強整合性と結果整合性を運用上のトレードオフとして捉える
  • 分散合意やログ複製がシステムの土台になることを押さえる

レプリケーション

同じデータを複数ノードに持つことで、読み取り性能や可用性を高めます。代表的な方式はprimary-replicaです。書き込み先を1つに寄せると整合性は扱いやすくなりますが、書き込みボトルネックや切り替えの問題が生じます。

一方でmulti-primaryは書き込み地点を増やせますが、競合解決が難しくなります。

同期複製と非同期複製

同期複製では、複数ノードへの反映を待ってから書き込み成功とします。データ損失を減らしやすい一方、書き込みレイテンシは大きくなります。非同期複製では、primaryが先に成功を返し、replicaへ後から追いつかせます。速くなりますが、障害タイミングによっては最新書き込みが失われる可能性があります。

設計では、次を明示します。

  • どの書き込みまで失ってよいか
  • どの程度の遅延を許すか
  • 切り替え時に二重primaryをどう避けるか
  • replica lagをどう監視するか

シャーディング

データをキー範囲やハッシュで分割し、各ノードが一部だけを持つ方式です。容量と書き込み負荷を横に広げられますが、次が難しくなります。

分割キー

シャーディングでは、分割キーの選び方がほとんど勝負です。均等に散るキーを選ぶと負荷は平準化しやすくなりますが、範囲検索や集計が難しくなることがあります。範囲で分けると検索は扱いやすくなりますが、特定範囲に書き込みが集中することがあります。

よくある判断軸は次です。

  • 書き込みはどこに集中するか
  • 読み取りは単一shardで完結するか
  • 集計はどの範囲で行うか
  • 将来shardを増やすとき、どれくらい移動が必要か

整合性モデル

全ノードが即座に同じ値を返すことを求めるなら強整合性が必要です。しかし可用性やレイテンシを重視する設計では、結果整合性を選ぶことがあります。大事なのは「どれが正しいか」ではなく、業務要件に対してどこまで一貫性を要求するかです。

たとえば残高や在庫のように誤差が困る領域では強い制約が必要です。検索インデックスや分析基盤では、少し遅れてそろう設計でも実用的です。

読み取り整合性

整合性は書き込みだけの話ではありません。読み取りで何を保証するかも重要です。

  • read-your-writes 自分が書いた値は次に読める
  • monotonic reads 古い値へ戻って見えない
  • causal consistency 因果関係のある更新順序を守る
  • linearizability すべての操作が単一の順序で起きたように見える

強い保証ほど実装と運用コストは上がりやすくなります。

分散ログと合意

分散システムでは、状態そのものより「状態を変えた順序」を共有する方が扱いやすいことがあります。そこで重要になるのがログ複製です。RaftやPaxosなどの合意アルゴリズムは、障害があっても多数派で順序を決め続けるための枠組みです。

これにより、設定管理、メタデータ管理、リーダー選出、ストレージ制御の基盤を作れます。

flowchart LR C["client"] --> L["leader"] L --> F1["follower"] L --> F2["follower"] L --> F3["follower"] F1 --> Q["quorum"] F2 --> Q Q --> A["commit"]

多数派に書き込めた時点でcommitとみなす設計では、一部ノードが落ちても進み続けられます。ただしネットワーク分断や遅延が大きい環境では、可用性と整合性のどちらを優先するかが表面化します。

トランザクションと分散トランザクション

単一ノードのトランザクションはACIDを比較的扱いやすいですが、複数ノードをまたぐと難しくなります。2-phase commitは代表的な方式ですが、参加者やcoordinatorの障害で待ちが発生しやすくなります。

そのため実務では、すべてを強い分散トランザクションで包むより、業務単位を小さく分け、イベント、補償処理、冪等性を組み合わせることがよくあります。

データシステムの選び方

選定では、製品名よりもworkloadを先に見ます。

  • 書き込みが多いか
  • 読み取りが多いか
  • 範囲検索が多いか
  • 集計が多いか
  • latencyが厳しいか
  • 障害時にどれくらい止められるか
  • 地理分散が必要か

運用で見る指標

  • replication lag
  • leader electionの頻度
  • write latency / read latency
  • compactionやvacuumの遅れ
  • shardごとの容量とQPS
  • queue depth
  • failed transaction / retry rate

分散データシステムは、設計した時点より運用し始めてからの観測が重要です。

現場でよく見る構成

  • OLTPデータベース primary-replica
  • NoSQLキーバリューストア シャーディング + 結果整合
  • データレイク / 分散分析 ストレージと計算を分離
  • ログ基盤 append-onlyの分散ログ

CAPとPACELC

分散データシステムでは、ネットワーク分断が起きたときに整合性と可用性のどちらを優先するかが問題になります。これがCAP定理の直感です。ただし、分断がない通常時にもレイテンシと整合性のトレードオフがあります。

PACELCは次のように整理します。

Pが起きたら A or C
Else通常時は L or C

つまり、障害時だけでなく平常時にも「速く返すか、強くそろえるか」を選び続けています。

Quorum read / write

複数replicaへ読み書きするシステムでは、quorumを使って整合性を調整することがあります。

  • N: replica数
  • W: 書き込み成功に必要な数
  • R: 読み取りに必要な数

一般に R + W > N なら、読み取り集合と書き込み集合が少なくとも1つ重なるため、最新値に到達しやすくなります。ただし、実際にはclock、競合解決、hinted handoff、repairなどの設計も影響します。

再分散とホットスポット

shardを増やすとき、既存データをどう移すかが問題になります。移動中も読み書きを続けるなら、routing、二重書き、backfill、整合性検査が必要です。

ホットスポットの例:

  • timestampをそのままpartition keyにする
  • 人気ユーザーに書き込みが集中する
  • 特定tenantだけが巨大化する
  • batch jobが一部shardだけを叩く

対策として、hash化、key salting、tenant分割、rate limit、queue化、read replicaの利用を検討します。

バックアップと復旧

分散データシステムでは、replicaがあることとbackupがあることは違います。誤削除やアプリケーションバグは、replicaにも複製されます。

確認すること:

  • point-in-time recoveryができるか
  • backupの復元テストをしているか
  • RPOとRTOが業務要件に合うか
  • schema変更後も復旧できるか
  • 暗号鍵や権限も復元できるか

復旧できないbackupは、backupではなく安心感だけのファイルです。

Change Data Capture

Change Data Capture(CDC)は、DBの変更をstreamとして取り出す仕組みです。検索index、分析基盤、cache更新、イベント連携に使われます。

CDCで見ること:

  • transaction順序が保たれるか
  • schema変更に対応できるか
  • consumerが遅れたとき保持できるか
  • 再処理できるか
  • 個人情報の流出経路にならないか

CDCは便利ですが、DBの内部変更を外部システムへ広げるため、契約と運用を慎重に設計します。

データ配置と地理分散

地理的に離れたregionへデータを置くと、災害耐性や近接性は上がりますが、レイテンシと法規制の問題が出ます。

  • ユーザーに近いregionで読む
  • 書き込みleaderをどこに置くか
  • 個人情報をどの国に置けるか
  • region障害時にどこまで復旧するか
  • multi-regionのコストを許容できるか

グローバル分散では、技術的な整合性だけでなく、データ主権と運用体制も設計対象です。

具体的システム事例

Google Cloud Spanner

Spannerは分散強整合性システムの代表例です。TrueTime APIを使用して、厳密な時刻同期に基づく全順序付けを実現しています。複数リージョンにおいて、「すべての読み取りが最新の書き込みを見る」という線形化可能性を保証します。

Spannerの特徴:

  • トランザクション型の強整合性: ACID保証により、複数テーブルに渡る更新を原子性で保証
  • 外部的一貫性: TrueTime由来のタイムスタンプにより、複数クライアントの操作順序が唯一に決まる
  • 複数リージョン対応: 書き込みは複数のリージョンにレプリケーションされ、読み取りは地域的に最適な場所から可能
  • 設定例: INTERLEAVE IN PARENT で関連テーブルを物理的に配置最適化

“Spanner is Google’s scalable, globally distributed database. It achieves external consistency and strong consistency across regions.” cloud.google.com/spanner

CockroachDB

CockroachDBは、Spannerの設計思想をオープンソースで実装した分散SQLデータベースです。Raftコンセンサスアルゴリズムを採用し、複数ノード間で副本を自動管理します。

特徴的な動作:

  • Raft層: 各レンジ(テーブルの行スライス)に対してRaftグループを作成し、複数ノードに副本を配置
  • MVCC(Multi-Version Concurrency Control): 各トランザクションはタイムスタンプを持ち、読み取りと書き込みの競合を回避
  • 自動リバランシング: ノード追加時にレンジを自動的に他ノードへ移動
  • 読み取りポリシー: FOLLOWER_READ で複数レプリカからの読み取り対応(わずかな遅延を許容)

CockroachDBの初期設定例:

CREATE TABLE accounts (
  account_id INT PRIMARY KEY,
  balance DECIMAL(19,2),
  CONSTRAINT fk_customer FOREIGN KEY (customer_id) 
    REFERENCES customers (id)
);

ALTER TABLE accounts CONFIGURE ZONE USING 
  num_replicas = 3,
  constraints = '[+region=us-east,+region=us-west,+region=eu]';

FoundationDB

FoundationDBはAppleが支援する超低レイテンシのACID分散データベースです。キーバリュー層に設計を集約し、その上に様々なデータモデルをレイヤーで実装できます。

FoundationDBの層構造:

  • Storage層: 順序付きキーバリュー保存、複数ストレージエンジン(SQLite、Redwood)対応
  • Distribution層: データの物理的な配置を管理、キー範囲ごとにレプリカグループ
  • Replication層: Raftに類似の合意アルゴリズムで副本の一貫性維持
  • Transaction層: ACID性を提供、バージョン管理で読み書き競合を回避

FoundationDBではレイヤーをカスタマイズ可能:

import fdb
fdb.api_version(730)
db = fdb.open()

@fdb.transactional
def set_value(tr, key, value):
    tr[key] = value
    return True

Apache Kafka in分散システム

Kafkaはイベントストリーミングプラットフォームとして、分散システムの変更通知基盤として広く使用されます。

Kafkaの分散特性:

  • パーティショニング: トピックを複数パーティションに分割し、複数ブローカーに分散
  • レプリケーション: 各パーティションは複数ブローカーに副本(ISR: In-Sync Replicas)を持つ
  • Zookeeperベースのリーダー選出: パーティションリーダー障害時に自動フェイルオーバー
  • トランザクション対応 (KIP-98): 複数パーティションへの原子的書き込みを v0.11以降で対応

Kafkaプロデューサー設定例:

bootstrap.servers=broker1:9092,broker2:9092,broker3:9092
acks=all
retries=3
min.insync.replicas=2
transactional.id=my-app-v1

Raftアルゴリズムの詳細

Raftは分散合意を実現するアルゴリズムで、Paxosより理解しやすく多くのシステムで採用されています。

Raftの3つの状態

各ノードは次の3つの状態を遷移します:

  1. Follower: 通常状態。リーダーからログエントリを受け取る
  2. Candidate: リーダー選出中。他ノードに投票を求める
  3. Leader: リーダー。クライアントからのコマンドを受け、ログに追加し、複製を管理

ログレプリケーション

Raftログは(term, index, command)の3つ組からなります:

Term: 1 | 2 | 2 | 3 | 3 | 3 | ...
Index: 1 | 2 | 3 | 4 | 5 | 6 | ...
Log:  [cmd1][cmd2][cmd3][cmd4][cmd5][cmd6]...

リーダーは周期的にAppendEntries RPCでフォロワーにログを送ります。内容:

  • 新規ログエントリ
  • リーダーがコミット済みと認識するインデックス(commitIndex)
  • フォロワーが前のRPCでどこまで受け取ったか確認(prevLogIndex, prevLogTerm)

フォロワーはリーダーが示したcommitIndexまでのエントリを適用(apply)します。

リーダー選出メカニズム

  • 各ノードは現在のterm番号を保持
  • リーダーから一定時間(election timeout: 通常150-300ms)応答がないと、フォロワーはCandidate状態へ遷移
  • Candidateはtermを増分し、自分に投票してくれるノードを探す
  • quorum(過半数)から投票を得たらLeaderになり、次のAppendEntries RPCでそれを通知

Raftの安全性は次で保証:

  • ログ完全性: より新しい方(term, index)のログを持つノードが選ばれる
  • リーダーコミット性: リーダーは複製されたエントリだけをコミット

強い整合性の実装パターン

Read-Your-Write整合性

ユーザーが書き込んだ値を必ず次の読み取りで見る保証。実装方法:

  1. 書き込み後のタイムスタンプ記録: 書き込み時のサーバータイムスタンプをクライアント側で記録
  2. 読み取り時のタイムスタンプ待機: 読み取り要求に「この時刻以降のデータを返す」指示を付ける
  3. : Spannerのmin_read_timestampパラメータ
// Spannerの例
Timestamp writeTs = client.write(writeOp).getCommitTimestamp();
ReadOptions readOpts = ReadOptions.newBuilder()
    .setReadTimestamp(writeTs)
    .build();
ResultSet results = client.read(table, keys, readOpts);

Causal Consistency

複数の操作間に因果関係がある場合、その順序を保証する方式。

実装例:Version Vector, Lamportクロック

[Node A: 3, Node B: 2, Node C: 1]

この値ベクトルを読み取り要求に付与することで「A でこれ以降の変更」を待機。

Leader-Only Write + Read-After-Write

  • すべての書き込みはリーダーのみで処理
  • 読み取りはフォロワーから可能だが、最新書き込みをcomit indexまで待機
  • 実装複雑度は低いが、読み取り性能がリーダー依存

分散トランザクションの2-Phase Commit詳細

フェーズ1(投票フェーズ)

コーディネータが全参加者に「このトランザクションをコミットできるか?」と問い合わせ。各参加者は:

  • トランザクションを実行してロック獲得
  • 成功なら「YES」で応答(ただしまだコミットしない)
  • 失敗なら「NO」で応答(ロール バック)

フェーズ2(コミットフェーズ)

  • すべてがYESなら、コーディネータが全参加者に「COMMIT」指示
  • 1つ以上がNOなら、コーディネータが全参加者に「ROLLBACK」指示
  • 参加者は指示に従い、ロックを解放

問題点:

  • コーディネータ障害時、参加者は無限待機可能
  • 同期待機でトランザクション時間が増加
  • ロック保有時間が長いため並行性低下

これが実務で Sagas パターン(補償トランザクション)が好まれる理由です。

分散Saga パターン

各マイクロサービスが順序立てて処理を進める方式:

Order Service: create order → [success]
  ↓
Payment Service: charge payment → [success]
  ↓
Inventory Service: decrement stock → [FAIL]
  ↓ [compensation]
Payment Service: refund payment
  ↓ [compensation]
Order Service: cancel order

利点:

  • 長時間ロックを保有しない
  • 各ステップでタイムアウト設定可能
  • 部分的な成功から復旧しやすい

分散データの監視指標(詳細)

Replication Lag

副本がリーダーより何バイト遅れているかを追跡。大きなlagは以下を示唆:

  • ネットワーク遅延
  • 副本ノードのリソース不足
  • ログバックプレッシャー

Prometheus メトリクス例:

mysql_replication_lag_bytes{server="replica-1"} = 1024000

Leader Election Frequency

リーダー選出の頻度が高いと、システム不安定性を示す。

原因:

  • ネットワーク分断/遅延
  • リーダーのリソース枯渇
  • 時刻ずれ(Raft election timeout と関連)

Write Amplification

副本数Nに対して、実際の書き込みがN倍以上になる場合:

Write Amplification = (Network bytes sent) / (Application bytes written)

N=3の副本なら理想的には3に近い値。5以上なら再送処理が多い兆候。

Shardレベルの不均衡検出

Shard 1: 15GB, 50k QPS
Shard 2: 3GB, 8k QPS    ← ホットスポット候補
Shard 3: 12GB, 48k QPS

対策:

  • Shard 2 をさらに分割
  • Shard 2 の読み取り用副本を増加
  • キー設計見直し

分散データで決める順番

分散データシステムでは、最初に製品名を選ぶより、制約を順番に決める方が失敗しにくいです。

  1. どのデータを失ってはいけないか
  2. 読み取り遅延と書き込み遅延のどちらを優先するか
  3. 障害時に読み書きのどちらを止めるか
  4. regionやtenantの境界をどう分けるか
  5. schema変更と再処理をどう扱うか
  6. backupから実際に復旧できるか

高可用性、低レイテンシ、強い整合性、低コストを同時に最大化することはできません。どの失敗を受け入れるかを明文化すると、複製、sharding、quorum、CDCの選び方が説明しやすくなります。

分散トランザクションと ACID 特性

分散データベースで ACID を達成することは、単一ノードより複雑です。

CAP定理と取捨選択

CAP定理: 3つの特性のうち、同時に満たすのは最大2つ

  • Consistency: 全ノードが同じデータを見る
  • Availability: システムが常に応答可能
  • Partition tolerance: ネットワーク分断に耐性

例:

  • CP(一貫性重視): CockroachDB, etcd
  • AP(可用性重視): Cassandra, DynamoDB
  • 最近のシステムは「弱い一貫性」で両立を目指す

CockroachDB の分散ACID

CockroachDB は「分散ACID」を実現するために、Multi-Version Concurrency Control (MVCC) と Raft コンセンサスを組み合わせます:

-- トランザクション例
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- CockroachDB は複数ノードでの一貫性を保証
-- Raftで大多数ノードの合意をとる
-- MVCCで並行トランザクション間の干渉を排除

Raft の動作:

  1. Leaderはクライアント要求を受け取る
  2. Followerに複製ログを送信
  3. 大多数(n/2+1)ノードが同意 → Commit
  4. Leaderが Follower にコミット通知

99%のレイテンシは ~100ms(複数地域間)

Kafka によるイベントストリーミング

Kafka は分散、耐障害性、スケーラビリティを備えたメッセージング:

from kafka import KafkaProducer, KafkaConsumer
import json

# Producer: イベント発行
producer = KafkaProducer(
    bootstrap_servers=['localhost:9092'],
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

event = {"user_id": 123, "action": "click", "timestamp": 1234567890}
producer.send("events", value=event)

# Consumer: イベント処理
consumer = KafkaConsumer(
    'events',
    bootstrap_servers=['localhost:9092'],
    group_id='analytics',
    value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)

for message in consumer:
    print(f"Received: {message.value}")

Kafka の特徴:

  • Replication factor: 各パーティションを複数ノードに複製
  • In-Sync Replicas (ISR): 同期中のレプリカ
  • Partition: トピックを複数パーティションに分割、スケール

3つのレプリケーションで、1ノード障害時も可用性維持

分散 JOIN とシャーディング

テーブルが複数ノードに分割される場合、JOIN は複雑になります:

Table A:                Table B:
id | name               id | salary
1  | Alice              1  | 100k  (Node 1)
2  | Bob                (Node 2)
(Node 1)

Query: SELECT A.name, B.salary FROM A JOIN B ON A.id = B.id

解決策:

  1. Co-location: JOIN キーで同じシャーディング
  2. Broadcast join: 小テーブルを全ノードにコピー
  3. Shuffle join: 両テーブルを再シャッフル

エンジニアリングの観点では、シャーディング戦略がアーキテクチャを左右します。

イベントソーシングと CQRS

イベントソーシングはデータベースの状態を「イベントのシーケンス」として保存します:

State evolution:
Initial: balance = 1000
Event 1: Deposit(200) → balance = 1200
Event 2: Withdrawal(300) → balance = 900
Event 3: Deposit(50) → balance = 950

Benefit: 全ての過去状態を復元可能

CQRS (Command Query Responsibility Segregation):

Write side (Command):
  Event Store に純粋に追記(Insert onlyRead side (Query):
  Materialized View を非同期で更新
  読取スピードに最適化

複雑なドメインロジックでは、この分離が大きな恩恵をもたらします。

Consensus アルゴリズムと分散システム

分散システムの基盤は Consensus(合意形成)です。Raft は 3つの状態(Follower、Candidate、Leader)で構成。タイムアウト後に投票を開始し、大多数のノードの投票を獲得するとLeader になります。

Paxos はより複雑で強力。4フェーズ(Prepare、Promise、Accept、Accepted)で値に合意。Multi-Paxos は複数スロットで並列実行。

ZooKeeper は分散協調作業(ロック、設定管理)の実装例。ビザンチン将軍問題では PBFT が n ノードで (n-1)/3 個の悪意あるノード許容。

スケーラビリティパターン

Sharding でテーブルを複数ノードに分割。Consistent Hashing で Shard を計算。Read Replicas は Leader 書き込み、Followers 読み取り。最終的一貫性に。

キャッシング戦略:Cache-Aside パターンで Cache Miss 時にデータベースから取得。TTL で自動削除。Redis で 1ms の遅延を実現。

実装例:CockroachDB の分散ACID トランザクション

CockroachDB は PostgreSQL 互換で、Raft コンセンサスにより複数ノードで ACID を保証。

-- クラスタ起動(3ノード)
cockroach start --insecure --host=localhost --port=26257
cockroach start --insecure --host=localhost --port=26258 --join=localhost:26257
cockroach start --insecure --host=localhost --port=26259 --join=localhost:26257

-- アプリケーション(Python)
import psycopg2

conn = psycopg2.connect(
    dbname='defaultdb',
    user='root',
    host='localhost',
    port=26257
)

cursor = conn.cursor()

# トランザクション
cursor.execute("BEGIN TRANSACTION")
try:
    cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
    cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
    cursor.execute("COMMIT")
    print("Transaction committed")
except Exception as e:
    cursor.execute("ROLLBACK")
    print(f"Transaction rolled back: {e}")

Raft により複数ノードでの一貫性を保証。1ノード障害時も可用性維持。

Kafka による Event Streaming

from kafka import KafkaProducer, KafkaConsumer
import json
import time

# Producer: イベント発行
producer = KafkaProducer(
    bootstrap_servers=['localhost:9092'],
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

for i in range(1000):
    event = {"user_id": i % 100, "action": "click", "timestamp": int(time.time())}
    producer.send("events", value=event)
    producer.flush()

# Consumer: イベント処理
consumer = KafkaConsumer(
    'events',
    bootstrap_servers=['localhost:9092'],
    group_id='analytics',
    auto_offset_reset='earliest',
    value_deserializer=lambda m: json.loads(m.decode('utf-8'))
)

event_count = 0
for message in consumer:
    event_count += 1
    if event_count % 100 == 0:
        print(f"Processed {event_count} events")

Partition により並列処理。Replication factor で耐障害性。Consumer group で負荷分散。

イベントソーシング と CQRS パターン

状態変化を「イベントのシーケンス」として保存。完全な履歴が復元可能。

from dataclasses import dataclass
from typing import List

@dataclass
class Event:
    timestamp: int
    event_type: str
    data: dict

class BankAccount:
    def __init__(self, account_id: str):
        self.account_id = account_id
        self.balance = 0
        self.events: List[Event] = []
    
    def deposit(self, amount: float):
        self.balance += amount
        self.events.append(Event(
            timestamp=int(time.time()),
            event_type="deposit",
            data={"amount": amount}
        ))
    
    def withdraw(self, amount: float):
        if self.balance >= amount:
            self.balance -= amount
            self.events.append(Event(
                timestamp=int(time.time()),
                event_type="withdrawal",
                data={"amount": amount}
            ))
    
    def replay_from_events(self):
        # イベントから状態を再構築
        self.balance = 0
        for event in self.events:
            if event.event_type == "deposit":
                self.balance += event.data["amount"]
            elif event.event_type == "withdrawal":
                self.balance -= event.data["amount"]

account = BankAccount("acc_001")
account.deposit(100)
account.withdraw(30)
account.deposit(50)

print(account.balance)  # 120
account.replay_from_events()
print(account.balance)  # 120(イベントから再構築)

過去状態への遡り、監査ログの完全性確保。

Apache Kafka の詳細アーキテクチャ

Broker と Partition の構造

Kafka は複数の Broker(サーバー)から構成。Topic は複数の Partition に分割され、各 Partition は 1 つの Broker に割り当てられる(リーダー)。複製(replica)を複数の Broker に保持(リーダー + フォロワー)。

リーダー選出: ISR(In-Sync Replicas)の中から自動選出。Broker 障害時にフェイルオーバー。min.insync.replicas=2 で信頼性向上。

Consumer Group と オフセット管理

Consumer Group は複数の Consumer で構成。各 Consumer は複数の Partition を担当(並列処理)。オフセットは Zookeeper(昔)または Kafka 内部 Topic(新)で管理。

自動オフセット提出: enable.auto.commit=true で自動提出。ただし “at-most-once” か “at-least-once” のトレードオフ。アプリケーションで明示的なコミットを推奨。

パフォーマンス特性

スループット: ~1M messages/sec(単一Broker)。レイテンシ: ~10ms(平均)。ディスク I/O 最適化により高速。

圧縮: snappy、lz4、zstd により帯域幅削減。batch.size、linger.ms でバッチサイズ制御。

Google Cloud Dataflow / Apache Beam

ストリーム処理と バッチ処理を統一フレームワークで実装。パイプライン定義は Java、Python、Go で実装。

スケーラビリティ: 自動スケーリングで並列度制御。Windowing で時間単位の集約(Fixed、Sliding、Session Window)。

分散トランザクションと一貫性

2 フェーズコミット(2PC): コーディネーターが Prepare / Commit フェーズで分散トランザクション実現。ただし遅延 + スケーラビリティ問題。

Saga パターン: マイクロサービス環境で各サービスが局所トランザクション実行。補償トランザクション(rollback)で一貫性維持。

最終一貫性(Eventual Consistency): 強い一貫性は求めず、時間経過で収束。Cassandra、DynamoDB で採用。

クエストが複数ノードをまたいだときのレイテンシ分析

  • Kubernetes Persistent

CAP 定理と一貫性トレードオフ

Consistency: すべてのノードが同じ値を見る。Availability: システムが常に応答。Partition tolerance: ネットワーク分割に対応。3 つすべてを同時に満たせない。

実装では CA(強い一貫性 + 高可用性)vs CP(一貫性 + 分割耐性)を選択。Amazon DynamoDB は EC(最終一貫性 + 分割耐性)。

Replication と同期戦略

Synchronous replication: 全レプリカに書き込み確認。遅延増加、耐障害性向上。

Asynchronous replication: リーダーへの確認後すぐ返答。低遅延、一貫性リスク。

Multi-master replication: 複数ノードでの並行更新。conflcit resolution メカニズム必要。

Quorum-based: 多数派の合意で確定。中間特性。

シャーディング(Partitioning)戦略

Range-based: キー範囲でパーティション。順序付きスキャンが効率的。ホットスポット risk。

Hash-based: Hash 関数でパーティション。均等分散。順序情報喪失。

Directory-based: キー空間を管理サーバーで追跡。柔軟だが SPOF risk。

Consistent hashing: ノード追加時の再分配最小化。DynamoDB、Cassandra で採用。

Google Cloud Spanner / CockroachDB

グローバル分散トランザクション DB。複数地域でのREAD COMMITTED 一貫性。

TrueTime(Google Spanner)による高精度タイムスタンプ。バージョニング管理。

Event Sourcing

状態変化をイベント列で記録。再生で現在状態復元。監査ログ兼用。

CQRS(Command Query Responsibility Segregation)との組み合わせで高性能化。

Time Series Database

InfluxDB、Prometheus など時系列データ特化。高圧縮率。時刻范囲クエリ最適化。

IoT、Monitoring での標準。

Blockchain / Distributed Ledger

Immutable record による高信頼性。Proof of Work(電力多用)vs Proof of Stake(環境負荷低)。

金融取引・supply chain traceability で活用。

Volumes + StatefulSets

  • 分散データベースの本番環境デプロイメント実践

関連技術とエコシステム

ここで紹介した各技術には、活発なコミュニティ・エコシステムが存在。 継続的な学習とアップデートを推奨。実装言語・フレームワークの選択は プロジェクト要件に基づいて判断。性能、保守性、開発速度のバランスが重要。

まとめ

分散データシステムでは、複製、分割、整合性、障害回復を一体として設計します。単に「複数台で動かすDB」ではなく、何を優先してどの制約を受け入れるかを決める章だと捉えると理解しやすくなります。

設計の判断軸:

  • CAP の選択: ネットワーク分断時にどちらを切るか — Spanner/CockroachDB は CP寄り、Dynamo/Cassandra は AP寄り
  • 複製戦略: Synchronous (RPO=0、レイテンシ犠牲) vs Asynchronous (RPO>0、可用性優先)
  • シャーディング: Hash (均等性◎、レンジクエリ✕) vs Range (時系列クエリ◎、ホットスポット注意)
  • コンセンサス選択: Raft (理解しやすさ重視) / Paxos (Google Chubby) / Zab (ZooKeeper) / EPaxos (低レイテンシ)
  • 障害モデル: Crash-stop / Crash-recovery / Byzantine — 想定するモデルでアルゴリズムが変わる
  • 再起動コスト: スナップショット + WAL replay の所要時間を計測しておく
  • 観測性: Tracing (Jaeger/Zipkin)、複製ラグメトリクス、quorum disagreement を必ず可視化

代表的な分散DBの選択指針

プロダクト 整合性モデル コンセンサス 適性ワークロード
Google Spanner External consistency (TrueTime API) Paxos グローバル分散OLTP
CockroachDB Serializable Raft PostgreSQL互換のOLTP
Cassandra Tunable (ONE/QUORUM/ALL) Gossip + Hinted Handoff 書込み重視/時系列
DynamoDB Eventual / Strongly Consistent option 内部Paxosベース キーバリュー、低レイテンシ
FoundationDB Serializable Paxos変種 レイヤアーキ、ACID
etcd Linearizable Raft 構成情報、サービスディスカバリ
TiDB Snapshot Isolation Raft + Percolator MySQL互換のHTAP

障害シナリオ別の対応

  • ネットワーク分断 (Split Brain): Quorum 過半数で動作 (Raft では (N/2)+1 ノードが必要)
  • ノード永続喪失: スナップショット + WAL を別ノードへ復元、再 join
  • クロックずれ: Spanner は GPS+原子時計の TrueTime で uncertainty bound を計算、commit-wait で整合性保証
  • 時系列の逆転: Lamport timestamp や Vector Clock で因果関係を保持

参考文献

文・仕様

  • Ongaro, D. & Ousterhout, J. (2014). “In Search of an Understandable Consensus Algorithm”. USENIX ATC.
  • Corbett, J. C., et al. (2013). “Spanner: Google’s Globally-Distributed Database”. OSDI 2012.
    • TrueTime, 外部的一貫性, 複数リージョン強整合性の実装
  • Chang, F., et al. (2008). “Bigtable: A Distributed Storage System for Structured Data”. OSDI 2006.
  • CAP Theorem: Brewer, E.A. (2000). “Towards Robust Distributed Systems”.
    • 可用性, 整合性, 分断耐性の理論的基礎

実装システムドキュメント

  • Google Cloud Spanner Documentation
    • Spanner の外部的一貫性, トランザクション分離レベル(SERIALIZABLE, SNAPSHOT)
    • 複数リージョンレプリケーション設定例
  • CockroachDB Architecture Overview
    • Raft 副本管理, MVCC, Range/Shard の自動リバランシング
    • 強整合性読み取り vs FOLLOWER_READ(結果整合性)の選択
  • FoundationDB Documentation
    • キーバリュー API、アプリケーション層でのデータモデル実装
    • Redwood ストレージエンジン, トランザクション分離(Serializable, Snapshot)
  • Apache Kafka Documentation
    • パーティショニング戦略, ISR(In-Sync Replicas)管理
    • トランザクション API (KIP-98), 順序保証
    • Consumer Group と offset management

書籍

  • “Designing Data-Intensive Applications” (Martin Kleppmann, 2017)
  • “Google SRE Book” (O’Reilly, 2016)
    • 本書の “Data Integrity” 章で、Spanner と Bigtable の設計思想を解説
  • “Site Reliability Engineering” (Engineering for Reliability, Chapter 15)
    • 分散トレーシング, メトリクス監視, インシデント対応

パターン・最適化

  • Saga Pattern: Chris Richardson - “Microservices Patterns”
    • 分散トランザクションの補償ベースの実装, Eventually Consistent オーケストレーション
  • Quorum-based Consistency: “Dynamo: Amazon’s Highly Available Key-Value Store” (SOSP 2007)
    • Quorum write (W), read ®, N の設定による整合性レベル調整
    • hinted handoff, Merkle tree による repair
  • Two-Phase Commit (2PC) と その制限: “The End of an Architectural Era” (Jim Gray, 2003)
    • 2PC の ブロッキング問題, 現代分散システムで避けられる理由

ツール・監視

  • Prometheus メトリクス定義
    • mysql_replication_lag_bytes, raft_leader_elections_total, write_amplification_ratio
  • OpenTelemetry / Jaeger
    • 分散トレース、リ