Розподілені системи · Алгоритми · Інформатика
📅 Квітень 2026 ⏱ ≈ 12 хв читання 🎯 Середній · Останнє оновлення: 28 травня 2026 р.

Узгоджене гешування — розподілене балансування навантаження з віртуальними вузлами

Коли ви додаєте або вилучаєте сервер із розподіленого кешу, скільки кешованих елементів доведеться перемістити? За наївного гешування за модулем — майже все. За узгодженого гешування мігрувати потрібно лише приблизно K/N елементів — де K — кількість ключів, а N — кількість вузлів. Цей витончений алгоритм лежить в основі Amazon DynamoDB, Apache Cassandra, CDN від Akamai та сотень розподілених баз даних по всьому світу.

1. Проблема гешування за модулем

Простий підхід до розподілу N елементів між M серверами — обчислити server = hash(key) % M. Це добре працює, доки M ніколи не змінюється. Але в реальній розподіленій системі сервери падають, а нову потужність додають постійно.

Сценарій: 4 сервери, мільйони ключів, розподілених як hash(key) % 4 Додається сервер → M змінюється з 4 на 5 hash(key) % 5 перетасовує ~80% усіх ключів на інші сервери ↓ Масштабний шторм промахів кешу; база даних перевантажена; стрибок затримки Вилучення сервера: hash(key) % 3 перетасовує ~75% ключів

Узгоджене гешування розв’язує це, будуючи простір, де додавання чи вилучення одного вузла зміщує лише близько 1/N частки ключів — мінімум можливого.

2. Геш-кільце

Уявіть вихід геш-функції (наприклад, SHA-1 з 232 можливими значеннями), розташований як коло — геш-кільце — від 0 до 232−1. І сервери, і ключі відображаються на це кільце за допомогою тієї самої геш-функції:

Позиції зберігаються у відсортованій структурі даних (відсортований масив або збалансоване двійкове дерево пошуку). Кільцева структура виникає з концептуального «згортання»: після максимального значення гешу позиції продовжуються з 0.

Вибір геш-функції: геш-функція має давати рівномірний розподіл, щоб рівномірно розкидати сервери по кільцю. Поширений вибір — SHA-256 (з подальшим обрізанням), xxHash або MurmurHash3 — два останні значно швидші за криптографічні геші для цієї мети. Криптографічна стійкість тут непотрібна; важлива лише рівномірність.

3. Пошук вузла за ключем

Щоб знайти, який сервер відповідає за ключ, обчисліть його геш-позицію, а потім знайдіть перший сервер за годинниковою стрілкою від цієї позиції:

// Геш-кільце узгодженого гешування на JavaScript (спрощено)
class ConsistentHashRing {
  constructor() {
    this.ring = new Map();   // позиція → сервер
    this.sorted = [];          // відсортовані позиції
  }

  addServer(name) {
    const pos = hash32(name);
    this.ring.set(pos, name);
    this.sorted = [...this.ring.keys()].sort((a, b) => a - b);
  }

  getServer(key) {
    const pos = hash32(key);
    // двійковий пошук першої позиції >= pos
    let lo = 0, hi = this.sorted.length - 1;
    while (lo < hi) {
      const mid = (lo + hi) >> 1;
      if (this.sorted[mid] < pos) lo = mid + 1;
      else hi = mid;
    }
    // згорнути до першого сервера, якщо вийшли за кінець кільця
    const idx = lo >= this.sorted.length ? 0 : lo;
    return this.ring.get(this.sorted[idx]);
  }
}

Пошук має складність O(log N) із двійковим пошуком, де N — кількість позицій серверів. Для N = 1000 позицій потрібно лише 10 порівнянь — незначні накладні витрати.

4. Приєднання та вихід вузла

Критична перевага: коли вузол додається або вилучається, мігрувати на інші сервери має лише частка 1/N усіх ключів.

Вузол приєднується (позиція P вставлена між A та B на кільці): До: ключі між A та B → призначені B Після: ключі між A та P → призначені новому вузлу (реплікуються туди) ключі між P та B → досі призначені B Передати потрібно лише ключі в дузі (A, P). Очікувана частка: 1/N усіх ключів. Вузол виходить (вилучено вузол у позиції P між A та B): До: ключі між A та P → призначені P Після: ключі між A та P → перепризначені B (наступнику P) Та сама частка: мігрує ~1/N ключів.

Порівняйте з гешуванням за модулем, яке перепризначає приблизно (N−1)/N ≈ 80% ключів при додаванні одного сервера до пулу з 5 — узгоджене гешування зменшує міграцію в N−1 разів.

5. Віртуальні вузли

За небагатьох фізичних серверів позиції на кільці можуть бути згрупованими — одні сервери відповідають за великі дуги, інші — за малі, що спричиняє нерівномірне навантаження. Віртуальні вузли (vnodes) виправляють це: кожен фізичний сервер розміщується в кількох позиціях на кільці.

addServer(name, replicas = 150) {
  for (let i = 0; i < replicas; i++) {
    const pos = hash32(`${name}#${i}`);
    this.ring.set(pos, name);
  }
  this.sorted = [...this.ring.keys()].sort((a, b) => a - b);
}

За 150 віртуальних вузлів на сервер розподіл навантаження наближається до теоретичного рівномірного розподілу. Серверам із більшою апаратною потужністю можна призначити більше vnodes, щоб вони отримували пропорційно більше навантаження — простий механізм для гетерогенних кластерів.

Віртуальні вузли Cassandra (vnodes): Apache Cassandra призначає кожному вузлу налаштовуваний параметр num_tokens (за замовчуванням 256 у сучасних версіях). Кільце токенів — це простір гешу; токен відповідає одній позиції vnode. Це дозволяє новому вузлу, що приєднується до кластера, негайно взяти на себе відповідальність за розкидані діапазони, а не за одну суцільну дугу, забезпечуючи кращу збалансованість і швидше завантаження.

6. Реплікація та списки переваг

Для відмовостійкості кожен ключ зберігається на кількох серверах — зазвичай на N послідовних вузлах за годинниковою стрілкою на кільці (N — коефіцієнт реплікації, наприклад, 3). Цей набір із N вузлів є списком переваг ключа.

Коли використовуються vnodes, список переваг пропускає віртуальні вузли, що належать тому самому фізичному серверу, щоб репліки потрапили на різні стійки чи домени відмов.

7. Системи в реальному світі

Система Використання узгодженого гешування ────────────────────────────────────────────────────────────── Amazon DynamoDB Кільцеве партиціювання, списки переваг, кворум R+W Apache Cassandra Кільце токенів із налаштовуваними vnodes num_tokens Amazon S3 Внутрішня маршрутизація шардів (деталі пропрієтарні) Akamai CDN Вибір кеш-сервера для маршрутизації URL клієнти memcached бібліотека libketama; кільце на боці клієнта Redis Cluster Геш-кільце на 16384 слоти (на основі модуля, але з міграцією) HAProxy алгоритм балансування навантаження consistent_hash Chord (P2P) DHT, побудована на кільці узгодженого гешування

Оригінальну статтю про узгоджене гешування опублікував Каргер та ін. у MIT 1997 року. Попри свою простоту, основна ідея — відображення як ідентифікаторів ресурсів, так і ідентифікаторів серверів у той самий абстрактний простір та використання близькості для призначення відповідальності — залишається однією з найвпливовіших ідей у розподілених системах, досі безпосередньо помітною в логіці призначення сховища кожного Kubernetes StatefulSet.

🔗 Дослідіть розподілені системи →