---
title: ストレージシステムの具体例
url: https://doc.liz6.com/ja/distributed-systems/06-distributed-storage/03-storage-system-instances
locale: ja
area: distributed-systems
tags:
- Dynamo
- Spanner
- leaderless
- leader-based
- consistent hashing
- virtual nodes
- gossip
- quorum
- Merkle tree
- vector clock
- eventual consistency
- TrueTime
- external consistency
- Paxos
- 2PC
- GPS + atomic clock
- commit wait
- ε
- always writeable
- timestamp ordering
- distributed-systems
- distributed-storage
date: 2026-06-30
modified: 2026-07-16
description: Dynamo(リーダーレス + ゴシップ + クォーラム)と Spanner(リーダーベース + TrueTime + Paxos + 2PC)は、分散ストレージの両極を表しています。前五章で学んだコンセンサス、レプリケーション、パーティショニング、メンバー発見を組み合わせて、2つの完全なシステムアーキテクチャを構築します。
---

# ストレージシステムの具体例

> Dynamo(リーダーレス + ゴシップ + クォーラム)と Spanner(リーダーベース + TrueTime + Paxos + 2PC)は、分散ストレージの両極を表しています。前五章で学んだコンセンサス、レプリケーション、パーティショニング、メンバー発見を組み合わせて、2つの完全なシステムアーキテクチャを構築します。

## 概要

前五章ではそれぞれ[コンセンサス](/distributed-systems/02-consensus-protocols/01-Raft.md)、[レプリケーション](/distributed-systems/03-replication-and-consistency/01-replication-strategies.md)、[パーティショニング](/distributed-systems/04-partitioning-and-routing/01-consistent-hashing.md)、[メンバー発見](/distributed-systems/05-members-and-discovery/02-gossip-protocol.md)、[読み書きパスと修復](/distributed-systems/06-distributed-storage/01-distributed-read-write-path.md)について解説しました。これらは孤立した概念ではなく、実際の分散ストレージシステムでは**同時に稼働し、互いに噛み合っています**。ここでは2つの古典的なシステムを取り上げ、これまでの部品を組み立てて完成品を作ります。Dynamo(リーダーレス + ゴシップ + クォーラムの路線)と Spanner(リーダーベース + TrueTime + 外部一貫性の路線)です。これら2つのシステムは分散ストレージの両極を表しており、これらを理解すれば、前五章の各章でなぜそのように解説したのかが理解できます。

## Dynamo: 可用性を優先するパズル

Amazonの2007年のDynamo論文は、リーダーレス + 最終一貫性という一連の路線(Riak、Cassandra、DynamoDBなど)の基礎を築きました。そのアーキテクチャは、いくつかの独立したメカニズムの**組み合わせ**であり、各メカニズムはすでに個別に解説済みです。これらを組み合わせることで、Dynamoが完成します。

| 層 | Dynamoの手法 | 対応する前章 |
|----|--------------|-------------|
| パーティショニング | 一貫性ハッシュ。各ノードが特定のトークン範囲を担当。負荷をより均一にするため**仮想ノード**(物理ノードあたり約100個のvnode)を導入 | [一貫性ハッシュ](/distributed-systems/04-partitioning-and-routing/01-consistent-hashing.md) |
| レプリケーション | 各データはコーディネーターに書き込まれ、時計回りにN個の successor(vnodeが存在する物理ノード)に書き込まれ、レプリカが異なる物理ノードに分散されることを保証 | [レプリケーション戦略 リーダーレス](/distributed-systems/03-replication-and-consistency/01-replication-strategies.md) |
| 書き込み | コーディネーターがN個のレプリカに並列書き込みを行い、W個のACKを待てば返答。W < Nの場合、一部のレプリカは一時的に不整合になる | [分散読み書きパス](/distributed-systems/06-distributed-storage/01-distributed-read-write-path.md) |
| 読み取り | コーディネーターがN個のレプリカに並列読み込みを行い、R個の応答を待ち、最も新しいバージョン(タイムスタンプではなくベクトルクロックで比較)を取得。read repairをトリガーして、非同期で最新バージョンを遅れたレプリカにプッシュ | 同上 |
| メンバー発見 | ゴシッププロトコルでノードの参加/離脱/ハートビート情報を伝播し、最終的に各ノードが完全なルーティングテーブルを持つ | [Gossipプロトコル](/distributed-systems/05-members-and-discovery/02-gossip-protocol.md) |
| 整合性修復 | バックグラウンドでMerkle treeによる比較、ゴシップ駆動での修復 | [整合性修復とデータ修復](/distributed-systems/06-distributed-storage/02-anti-entropy-and-data-repair.md) |
| 競合解決 | ベクトルクロック(vector clock)で因果関係を追跡。アプリケーション側で読み取り時に競合をマージ(ショッピングカート: 異なるレプリカの項目をマージし、消失させない) | [競合解決](/distributed-systems/03-replication-and-consistency/02-conflict-resolution.md) |
| hinted handoff | 対象ノードに到達できない場合、コーディネーターがその分のデータを自機のhints領域に一時的に保存し、対象ノードが復旧後に再生する | [分散読み書きパス hintセクション](/distributed-systems/06-distributed-storage/01-distributed-read-write-path.md) |

これらの層が噛み合う際には、スタック全体を通じて2つのグローバルな制約が貫かれています。

- **常に書き込み可能(always writeable)**——一部のノードに到達できなくても、コーディネーターは書き込みを受け付けます(hinted handoff + sloppy quorumによりWを確保)。その代償として、競合がより頻繁に発生する可能性があります。
- **最終一貫性**——データは最終的に収束しますが、収束前のウィンドウ内では、異なる読み取りで異なるバージョンが見えることがあります。アプリケーション層でこれを処理する必要があります(Dynamoは競合をアプリケーション側に委ねて解決します。これが設計思想の中核です: ストレージ層はアプリケーションのために競合の決定を行いません)。

### Dynamoでの書き込みの完全な旅路(全層を繋ぐ)

<svg viewBox="0 0 720 410" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Dynamo 書き込みリクエストの全プロセス: クライアントリクエストはコーディネーター経由で3つのレプリカに並列書き込みされ、W=2で基準を満たせば返答され、到達不能ノードは hinted handoff を経由し、バックグラウンドの整合性修復で補完される">
  <defs>
    <marker id="dyah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="410" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Dynamoでの書き込みの完全な旅路: 3つのレプリカに並列書き込み、W=2で基準を満たせば返答</text>

  <rect x="230" y="40" width="260" height="30" rx="6" fill="#e2e8f0"/>
  <text x="360" y="60" text-anchor="middle" font-size="12" fill="#334155">put("cart:alice", {item:"book"})</text>
  <line x1="360" y1="70" x2="360" y2="80" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>

  <rect x="180" y="82" width="360" height="38" rx="8" fill="#4f46e5"/>
  <text x="360" y="97" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">コーディネーター(任意のノード)</text>
  <text x="360" y="113" text-anchor="middle" font-size="10.5" fill="#e0e7ff">クラスター全体のルーティングテーブルを保持(ゴシップ伝播により取得)</text>
  <line x1="360" y1="120" x2="360" y2="130" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>

  <rect x="140" y="132" width="440" height="46" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="150" text-anchor="middle" font-size="11.5" fill="#3730a3">hash("cart:alice") → トークンが範囲 [A, B] に落ちる</text>
  <text x="360" y="167" text-anchor="middle" font-size="11.5" fill="#3730a3">ルーティングテーブルを参照: N=3 の vnode が物理ノード P1、P3、P7 に存在</text>

  <line x1="360" y1="178" x2="215" y2="188" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>
  <line x1="360" y1="178" x2="525" y2="188" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>

  <rect x="60" y="190" width="310" height="56" rx="8" fill="#ccfbf1" stroke="#99f6e4"/>
  <text x="215" y="209" text-anchor="middle" font-size="12" font-weight="700" fill="#0f766e">P1、P3: 並列書き込み成功</text>
  <text x="215" y="226" text-anchor="middle" font-size="10.5" fill="#115e59">WALに追記 → fsync → MemTableに書き込み</text>
  <text x="215" y="240" text-anchor="middle" font-size="10.5" fill="#115e59">→ ACK(version v5)</text>

  <rect x="390" y="190" width="270" height="56" rx="8" fill="#ffedd5"/>
  <text x="525" y="209" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">P7: 到達不能</text>
  <text x="525" y="226" text-anchor="middle" font-size="10.5" fill="#c2410c">コーディネーターはこのデータを自機の</text>
  <text x="525" y="240" text-anchor="middle" font-size="10.5" fill="#c2410c">hinted handoff領域に一時的に保存</text>

  <line x1="215" y1="246" x2="360" y2="258" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>
  <line x1="525" y1="246" x2="360" y2="258" stroke="#475569" stroke-width="1.6" marker-end="url(#dyah)"/>

  <rect x="220" y="260" width="280" height="30" rx="6" fill="#22c55e"/>
  <text x="360" y="280" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">W=2のACKを受信 → SUCCESSを返答</text>

  <line x1="360" y1="290" x2="360" y2="300" stroke="#475569" stroke-width="1.6" stroke-dasharray="4 3" marker-end="url(#dyah)"/>

  <rect x="100" y="302" width="520" height="36" rx="8" fill="#e2e8f0" stroke="#cbd5e1" stroke-dasharray="4 3"/>
  <text x="360" y="324" text-anchor="middle" font-size="11" fill="#475569">(バックグラウンド非同期) 次のゴシップ整合性修復をトリガー → Merkle tree比較でP7の欠落を検出 → 補完</text>

  <rect x="60" y="348" width="600" height="52" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="368" font-size="12" fill="#3730a3">常に書き込み可能: P7が一時的に到達不能でも書き込みは影響を受けず、hinted handoff + sloppy quorumによりWを確保;</text>
  <text x="76" y="387" font-size="12" fill="#3730a3">代償は短時間の不整合——収束はバックグラウンドの整合性修復により行われ、この書き込みですべてのレプリカが即時一貫化されるわけではない。</text>
</svg>

## Spanner: 外部一貫性のパズル

Google Spanner(2012)は別の路線を選びました。**強力なリーダー + 外部一貫性**です。Googleの広告やF1データベースを支えています。これもまた一連のメカニズムの組み合わせですが、追求する目標は正反対です——**可用性を犠牲にしても、「単一マシンのように」振る舞うトランザクションセマンティクスを保証します**。

| 層 | Spannerの手法 | 対応する前章 |
|----|---------------|-------------|
| パーティショニング | レンジシャーディング(range sharding)。キーを辞書順に分割してタブレットにし、各タブレットは1つのPaxosグループ | [シャーディング戦略](/distributed-systems/04-partitioning-and-routing/02-sharding-strategies.md) |
| コンセンサス | 各タブレット内部は**Paxosグループ**(複数レプリカ、Paxosでリーダーを選出およびログをレプリケート) | [Paxos](/distributed-systems/02-consensus-protocols/02-Paxos.md) |
| レプリケーション | Paxosログをグループ内の過半数にレプリケート。すべての書き込みはリーダーを経由する必要がある。読み取りはフォロワーから可能(ただし**読み取りトランザクションのタイムスタンプ**が必要) | [レプリケーション戦略 single-leader](/distributed-systems/03-replication-and-consistency/01-replication-strategies.md) |
| トランザクション | タブレットを跨ぐ書き込みは**2PC**(2フェーズコミット)を使用。コーディネーターはPaxosリーダーの1つ。コミットタイムスタンプは**TrueTime**によって割り当てられる | [分散トランザクション](/distributed-systems/03-replication-and-consistency/03-distributed-transactions.md) |
| 時間 | **TrueTime**: 各データセンターにGPSと原子時計による同期があり、グローバルなクロックのオフセットを ≤ ε(約7ms)に保証。各トランザクションに「すべてのコミット済みトランザクションより遅い」タイムスタンプを割り当て、外部一貫性を実現 | [時間とクロック](/distributed-systems/01-basic-theory/02-time-and-clocks.md) |

Spannerの中核的な革新は、**TrueTime + 2PC + Paxos**の組み合わせにより、**外部一貫性(external consistency)**を実現した点です——トランザクションはコミットタイムスタンプでグローバルに順序付けられ、どの読み取りでも、それより前の書き込みをすべて見ることができます。これはDynamoの「最終的に収束し、読み取りで古いデータが見える可能性がある」とは正反対です。

### Spannerでの跨テーブルトランザクション(全層を繋ぐ)

```
BEGIN
  UPDATE users SET balance = balance - 100 WHERE id = 42  -- タブレットA (PaxosグループG1)
  UPDATE orders SET status = 'paid' WHERE id = 789        -- タブレットB (PaxosグループG2)
COMMIT

フロー:
  1. クライアント → G1のPaxosリーダー (2PCコーディネーターとなる)
  2. コーディネーター: G1とG2のリーダーにprepareを送信
  3. 各グループのPaxosリーダー: prepareレコードをPaxosログに書き込み → 過半数にレプリケート
  4. 全参加者がACK → コーディネーターがコミットタイムスタンプを選択:
     - ts = max(各参加者のローカル時間) + バックオフ > TrueTime.now() + ε
     - これにより、tsがすでにコミットされたトランザクションのタイムスタンプより遅いことが保証される (外部一貫性)
  5. コーディネーター: commit record + tsを自機のPaxosログに書き込み → レプリケート → committed
  6. 各参加者にコミットを通知 (ts付きPaxosログエントリ)
```

注意: 4番目のステップでTrueTimeの `now() + ε` を保証するのは、εがクロックのずれの上限であるため、すべてのデータセンターの「今」の実際の時間は `now() + ε` を超えることがないからです。したがって、`ts > now() + ε` を選択することで、tsが将来の時間であることが保証され、ts以降の読み取りではこのトランザクションが見えるようになります。これこそが、SpannerがSQLに対して「単一マシンのように」直列化分離を提供できる理由です——タイムスタンプは真の物理的な制約に基づくグローバル順序だからです。

## 両極の比較

| | Dynamo | Spanner |
|---|---|---|
| 一貫性 | 最終一貫性 | 外部一貫性(強力) |
| 書き込み可用性 | 常に(リーダーレス、任意のノードが受け付け可能) | リーダーが必要(リーダーがダウンすると選挙待ち) |
| 競合解決 | アプリケーション側 (ベクトルクロック) | ストレージ層 (タイムスタンプのグローバル順序) |
| トランザクション | キーを跨ぐトランザクションはサポートしない | 完全なACID (2PC + TrueTime) |
| レイテンシ | 低い (複数ラウンドの調整なし) | 高い (Paxos + 2PC + コミット待機) |
| クロック依存 | 緩い (バージョン比較にのみ使用され、同期は要求されない) | 厳しい (TrueTimeが正しさの前提) |
| 運用複雑さ | 低い (リーダーなし、ノードは自由にダウン可能) | 高い (GPS/原子時計による同期、リーダー選挙) |

この2つのシステムの選択は、**分散ストレージシステムの根本的なトレードオフ**です——ストレージの選定を行う際には、常にこの表の2つの列の間で位置を見つけることになります。

## 参考文献

- **Dynamo論文**: "Dynamo: Amazon's Highly Available Key-value Store"(DeCandia 2007)——すべてのリーダーレスシステムの源流
- **Spanner論文**: "Spanner: Google's Globally-Distributed Database"(Corbett 2012)——TrueTime + 外部一貫性

*Keywords: Dynamo, Spanner, leaderless, leader-based, consistent hashing, virtual nodes, gossip, quorum, Merkle tree, vector clock, eventual consistency, TrueTime, external consistency, Paxos, 2PC, GPS + atomic clock, commit wait, ε, always writeable, timestamp ordering*
