---
title: 実システムにおけるコンセンサスの実践
url: https://doc.liz6.com/ja/distributed-systems/02-consensus-protocols/03-consensus-in-practice
locale: ja
area: distributed-systems
tags:
- distributed-systems
- consensus-protocols
date: 2026-06-30
modified: 2026-07-16
description: etcd、Consul、ZooKeeper——3つの実システムにおける共通のコンセンサス問題に対する3つのエンジニアリング的解決策。Raft+boltdb、Raft+Gossip、ZAB。コンセンサスプロトコルは中核だが全てではない。線形整合性読み取り、リース、watch メカニズム、メンバー管理にはそれぞれ独自のエンジニアリング上のトレードオフがある。
---

# 実システムにおけるコンセンサスの実践

> etcd、Consul、ZooKeeper——3つの実システムにおける共通のコンセンサス問題に対する3つのエンジニアリング的解決策。Raft+boltdb、Raft+Gossip、ZAB。コンセンサスプロトコルは中核だが全てではない。線形整合性読み取り、リース、watch メカニズム、メンバー管理にはそれぞれ独自のエンジニアリング上のトレードオフがある。

## etcd

etcd は最も広く使用されている Raft 実装（Go）であり、Kubernetes の中核依存関係です。すべての K8s オブジェクト（Pods, Services, ConfigMaps）は etcd に保存されます。

### アーキテクチャ

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="etcd 架构:gRPC 请求经 Raft 层共识后写入 boltdb,MVCC 管理历史版本">
  <defs><marker id="arr-etcd" 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="300" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">etcd アーキテクチャ: gRPC リクエストは Raft コンセンサスを経て boltdb に書き込まれ、MVCC が履歴バージョンを管理</text>
  <rect x="40" y="100" width="150" height="48" rx="8" fill="#4f46e5"/>
  <text x="115" y="129" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Client gRPC</text>
  <line x1="190" y1="124" x2="226" y2="124" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-etcd)"/>
  <rect x="230" y="60" width="450" height="28" rx="6" fill="#4f46e5"/>
  <text x="455" y="79" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">etcd server</text>
  <rect x="242" y="100" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="116" font-size="12" font-weight="700" fill="#3730a3">Raft layer</text>
  <text x="256" y="132" font-size="11" fill="#4f46e5">コンセンサス + ログレプリケーション</text>
  <rect x="242" y="146" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="162" font-size="12" font-weight="700" fill="#3730a3">Storage</text>
  <text x="256" y="178" font-size="11" fill="#4f46e5">boltdb (B-tree, 永続化)</text>
  <rect x="242" y="192" width="426" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="256" y="208" font-size="12" font-weight="700" fill="#3730a3">MVCC</text>
  <text x="256" y="224" font-size="11" fill="#4f46e5">マルチバージョン key-value (履歴 revision)</text>
  <rect x="60" y="248" width="600" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="272" font-size="12.5" fill="#3730a3">ライトパス: client → Raft propose → commit → boltdb への apply → response; MVCC により読み取りはバージョンごとに遡ることが可能。</text>
</svg>

各ライトリクエストは以下のフローを経ます：Raft propose → commit → boltdb への apply → response。リードリクエストには2つのモードがあります：

- **Serializable Read**: ローカルの boltdb から直接読み取る（Raft を経由しない）→ 古いデータ（stale）を返す可能性がある
- **Linearizable Read** (デフォルト): ReadIndex メカニズムを使用。1) リーダーは現在の commitIndex を記録する; 2) 過半数のフォロワーにハートビートを送信し、自身がまだリーダーであることを確認する; 3) 状態機械が ≥ その commitIndex まで apply するのを待つ; 4) 読み取り。これにより、読み取りは置き換えられたリーダーのデータを参照しないことが保証されます。

### Watch

etcd は long-poll watch をサポートしています：クライアントは key prefix を指定し、サーバーは変更イベントを返します。Kubernetes のコントローラーパターン（Informer）は etcd watch を基に実装されています。

## Consul

Consul は、サービスカタログの整合性レプリケーションに Raft を使用し、メンバーの発見とヘルスチェックの伝播に Gossip (SWIM) を使用します。これにより、「整合性が必要なデータ」と「最終整合性でよいデータ」が分離されています。

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Consul 架构:Raft 复制强一致的 service catalog,Gossip 传播最终一致的成员状态">
  <defs><marker id="arr-consul" 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="320" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Consul: Raft が強整合性データを管理し、Gossip が最終整合性データを管理</text>
  <rect x="40" y="100" width="150" height="90" rx="8" fill="#4f46e5"/>
  <text x="115" y="150" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Consul Agent</text>
  <line x1="190" y1="128" x2="314" y2="92" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-consul)"/>
  <text x="252" y="102" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">Raft</text>
  <rect x="320" y="60" width="360" height="64" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="336" y="84" font-size="13" font-weight="700" fill="#3730a3">Consul Server</text>
  <text x="336" y="102" font-size="11" fill="#4f46e5">3〜5ノード、Raft により service catalog をレプリケート</text>
  <text x="336" y="117" font-size="11" fill="#4f46e5">強整合性（どのサービスが提供されているか）</text>
  <line x1="190" y1="162" x2="314" y2="202" stroke="#475569" stroke-width="1.8" marker-end="url(#arr-consul)"/>
  <text x="252" y="218" text-anchor="middle" font-size="11" font-weight="700" fill="#0f766e">Gossip</text>
  <rect x="320" y="170" width="360" height="64" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="336" y="194" font-size="13" font-weight="700" fill="#115e59">全 Consul Agents</text>
  <text x="336" y="212" font-size="11" fill="#0f766e">メンバー発見、ヘルス状態の伝播</text>
  <text x="336" y="227" font-size="11" fill="#0f766e">最終整合性（どのノードが生きているか）</text>
  <rect x="60" y="254" width="600" height="50" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="275" font-size="12.5" fill="#3730a3">設計原則: サービス登録には強整合性が必要 → Raft; メンバーの死活判定には最終整合性で十分 → Gossip。</text>
  <text x="76" y="293" font-size="12.5" fill="#3730a3">「整合性が必要なデータ」と「最終整合性でよいデータ」を分離する。</text>
</svg>

設計原則：サービス登録（どのサービスが提供されているか）には強整合性が必要 → Raft。メンバーの死活判定（どのノードが生きているか）には最終整合性で十分 → Gossip。

## ZooKeeper (ZAB プロトコル)

ZooKeeper の ZAB (ZooKeeper Atomic Broadcast) プロトコルは Raft より8年前に設計されており、設計目標が異なります：

- Raft: すべてのサーバーのログは**完全に同一**である
- ZAB: リーダーのログが権威であり、フォロワーはリーダーよりも多くのエントリを持っている可能性がある（ただし上書き可能）

ZAB の「順序保証」は、Raft のようなグローバルな線形整合性順序ではなく、**FIFO クライアント順序**です。つまり、クライアントからの複数のリクエストは送信順序が保持されますが、異なるクライアント間には順序保証がありません。

### なぜ ZooKeeper はまだ存続しているか

ZooKeeper の先駆的な優位性と Java エコシステムにより、多くのレガシーシステムがそれに依存しています（Kafka の旧バージョン、Hadoop、HBase など）。しかし、新しいシステムでは etcd（よりシンプル、Go ネイティブ、K8s の依存関係）への傾斜が強まっています。

## コンセンサスの適用シーン

コンセンサスは万能ではありません。それは「複数のノードが単一の値について合意する」問題を解決します：

```
コンセンサスを使うべき場面:              コンセンサスを使うべきでない場面:
  - リーダー選挙                      - 大容量データのレプリケーション (gossip を使用)
  - 分散ロック                        - リーダーベースではないレプリケーション
  - 設定管理                          - ヘルスチェック
  - メタデータ管理                    - ログ集約
```

大多数のシステムはコンセンサスを必要としていません。それらが求めているのは、最終整合性によるレプリケーション（Gossip, 非同期レプリケーション）または単一マスター + WAL（大多数の SQL DB のマスター-スレーブレプリケーション）です。

## 参考

- **etcd**: github.com/etcd-io/etcd, raft.github.io
- **Consul**: consul.io/docs/architecture
- **ZooKeeper**: "ZooKeeper: Wait-free coordination for Internet-scale systems" (2010)

*Keywords: etcd, Raft, boltdb, Consul, Gossip, ZooKeeper, ZAB, linearizable read, ReadIndex*
