🔵
概念 #データ設計 #コンシステントハッシング #分散システム #Redis #Cassandra #DDIA 📚 データ志向アプリケーション設計(DDIA)

コンシステントハッシング

ノードの追加・削除時のデータ移動を最小化するアルゴリズム。Redis Cluster・Cassandraが採用する仮想ノードの仕組みと、通常のモジュロハッシュとの違いを理解する

定義

コンシステントハッシング(Consistent Hashing):分散システムでキーをノードに割り当てるアルゴリズム。ノードの追加・削除時に移動が必要なキーを最小限(1/N)に抑えることが最大の特徴。

通常のハッシュ分割の問題

3ノード構成:
  hash(key) % 3 でノードを決定

  key "user:1" → hash=10 → 10 % 3 = 1 → Node1
  key "user:2" → hash=20 → 20 % 3 = 2 → Node2
  key "user:3" → hash=30 → 30 % 3 = 0 → Node0

ノードを3→4に増やしたとき:
  hash(key) % 4 で全キーの割り当てが変わる

  key "user:1" → hash=10 → 10 % 4 = 2 → Node2(変わった!)
  key "user:2" → hash=20 → 20 % 4 = 0 → Node0(変わった!)
  key "user:3" → hash=30 → 30 % 4 = 2 → Node2(変わった!)

→ ほぼ全キーの再配置が必要(キャッシュなら全キャッシュが無効に)

コンシステントハッシングの仕組み

アイデア: ノードとキーを同じ「ハッシュリング(0〜2^32の円)」上に配置する

1. ノードをリング上に配置(ノードIDをハッシュして位置を決める)
2. キーをリング上に配置(キーをハッシュして位置を決める)
3. キーの担当ノード = 時計回りに最初に出会うノード

リングのイメージ:
       0
      / \
  Node2  Node0
    |      |
  Node1---+
     
key "user:1" → リング上の位置 → 時計回りに最初のノードが担当

ノード追加時の動作

4番目のノード(Node3)をNode0とNode1の間に追加:

変更前: Node0とNode1の間のキーはNode1が担当
変更後: Node0とNode3の間のキーはNode3が担当
       Node3とNode1の間のキーは引き続きNode1が担当

→ 移動が必要なキーは全体の約1/N(ノード数が4なら25%)
→ 通常のモジュロハッシュは約75%が移動

仮想ノード(Virtual Nodes / vnodes)

シンプルなコンシステントハッシングでは、ノードの分布が偏ることがある(ハッシュ関数の偏り)。

問題:
  3ノードがリング上に均等に配置されるとは限らない
  偏りが大きいとホットスポットが発生

解決: 各物理ノードを複数の仮想ノードとしてリング上に配置

Node0 → vnode0-1, vnode0-2, vnode0-3, ...(100個)
Node1 → vnode1-1, vnode1-2, vnode1-3, ...(100個)
Node2 → vnode2-1, vnode2-2, vnode2-3, ...(100個)

→ 300個の仮想ノードが均等にリング上に散らばる
→ 各物理ノードの担当範囲が均等になる
→ ノード追加時も自動的に均等になる

Redis Clusterでの実装

Redis Clusterはコンシステントハッシングではなく「ハッシュスロット」を使うが、思想は同じ。

16384個のハッシュスロット(0〜16383)

key → CRC16(key) % 16384 → スロット番号

スロットをノードに割り当て:
  Node0: スロット 0〜5460
  Node1: スロット 5461〜10922
  Node2: スロット 10923〜16383

ノード追加時(4台目):
  各ノードからスロットの一部を移動するだけ
  Node0: 0〜4095(一部Node3へ)
  Node3: 4096〜5460 + 5461〜7280 + 10923〜12287(各ノードから移管)
# Redis Clusterのスロット確認
redis-cli cluster info
redis-cli cluster nodes

# スロットのリシャーディング
redis-cli --cluster reshard 127.0.0.1:7000

Cassandraでの実装

Cassandraはコンシステントハッシングと仮想ノードを組み合わせて使う。

パーティションキーをMurmur3ハッシュでリング上に配置
デフォルトで1ノードあたり256個の仮想ノード

書き込みフロー:
  クライアント → 任意のノード(コーディネーター)
  コーディネーター → ハッシュリングでレプリカノードを特定
  → レプリカノードに書き込み(Consistency Levelで決まる数)

コンシステントハッシングの活用場面

CDNのエッジサーバー選択

コンテンツのURLをハッシュしてエッジサーバーを決定
同じURLは常に同じエッジサーバーにキャッシュされる

エッジサーバーが追加・削除されてもキャッシュのヒット率低下を最小化

ロードバランサーのスティッキーセッション

セッションIDをハッシュして担当バックエンドを決定
同一セッションは常に同じバックエンドに向く(セッション情報の共有不要)

コンシステントハッシングならバックエンドの増減でも影響最小

分散キャッシュのシャーディング

// Node.jsでコンシステントハッシングを実装するライブラリ例
import ConsistentHashing from 'consistent-hashing';

const ring = new ConsistentHashing(['cache1:6379', 'cache2:6379', 'cache3:6379']);

function getCache(key: string) {
  const node = ring.getNode(key);  // 担当ノードを取得
  return createRedisClient(node);
}

// ノード追加
ring.addNode('cache4:6379');
// → 約25%のキーだけ移動先が変わる

通常のハッシュ vs コンシステントハッシング

観点通常のモジュロハッシュコンシステントハッシング
ノード追加時の移動量ほぼ全キー((N-1)/N)1/N のキーのみ
実装の複雑さシンプルやや複雑
均等分散均等仮想ノードなしでは偏りあり
ノード間の偏り対策不要仮想ノードで解決
採用例固定台数のシステムRedis Cluster, Cassandra, CDN

関連概念

出典・参考文献

  • David Karger et al., “Consistent Hashing and Random Trees” (1997) — MIT
  • Martin Kleppmann, Designing Data-Intensive Applications (2017) Chapter 6
  • Redis Cluster Specification — redis.io/docs/reference/cluster-spec
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー
  34. 34. 🔵コンシステントハッシング
  35. 35. 📋マテリアライズドビュー
  36. 36. 📡DBモニタリングとオブザーバビリティ
  37. 37. 🔐データプライバシーとCrypto-Shredding
  38. 38. ✂️垂直分割(Vertical Partitioning)
  39. 39. 🪟ウィンドウ関数
  40. 40. 🧲ベクトルDBとpgvector
  41. 41. 🔧dbtとデータ変換パイプライン
  42. 42. 📬ジョブキューの設計
  43. 43. 📐正規化理論(1NF〜BCNF)
  44. 44. ☁️クラウドDBサービスの設計思想と選択基準
  45. 45. 🗺️地理空間データとPostGIS
  46. 46. 🔑DBセキュリティと権限管理
  47. 47. 🏔️Lakehouse(Apache Iceberg / Delta Lake)
  48. 48. 📜データコントラクト
  49. 49. 🔭OpenTelemetryとDBトレーシング

出典: Martin Kleppmann, 'Designing Data-Intensive Applications' (2017) Chapter 6