Настроить hot shard detection

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить hot shard detection

1. Цель задачи

Научиться проектировать и внедрять систему мониторинга распределения ключей в шардированном кеше (например, Redis Cluster или Memcached). Реализовать детекцию «горячих» шардов (hot shards), utilisation которых существенно превышает среднюю, и автоматический решардинг для выравнивания нагрузки. В результате все шарды должны иметь utilisation не более 120% от среднего значения.

Ключевой результат Работающий pipeline мониторинга key distribution + триггер решардинга, поддерживающий utilisation каждого шарда в пределах <120% от среднего.


2. Исходные данные

Перед началом необходимо иметь:

Что нужноОткуда взять
Шардированный кеш (Redis Cluster / Memcached с хэшированием по ключу)Тестовый кластер из 3–6 нод (локально Docker или облачный сервис)
Генератор трафика с неравномерным распределением ключейНаписать скрипт на Python (например, 80% запросов к 20% ключей)
Метрики шардов (CPU, memory, ops/sec, latency per shard)Redis INFO, CLUSTER SLOTS, Memcached stats, Prometheus exporter
Инструмент хранения временных рядовPrometheus (или VictoriaMetrics) + Grafana для дашбордов
Средство решардингаRedis CLUSTER SETSLOT …, или скрипты перемещения ключей (для Memcached — перехэширование с новым consistency hash)

Если нет реального инструмента — симулируем:

  1. Развернуть Redis Cluster из 3 нод в Docker Compose (каждая нода — отдельный контейнер).
  2. Написать Python-скрипт data_generator.py, который пулит пул ключей из 10 000, но для 20% ключей делает в 4 раза больше запросов (hot_keys.txt).
  3. Использовать redis-cli --cluster check для просмотра распределения слотов/ключей.
  4. Установить Prometheus node_exporter на каждый контейнер и Prometheus для сбора метрик.
  5. Для решардинга использовать redis-cli --cluster rebalance (симуляция вручную) или написать скрипт с CLUSTER SETSLOT ... MIGRATING/IMPORTING.

3. Технологический стек

КомпонентИнструментыНазначение
Шардированный кешRedis Cluster (7.x) или Memcached + consistent hashingХранение данных с распределением по шардам
Мониторинг метрикPrometheus + redis_exporter / memcached_exporterСбор key distribution, ops, latency
ВизуализацияGrafanaДашборды с utilisation per shard
Генератор трафикаPython (redis-py, random, time)Имитация нагрузки с перекосом
ОркестрацияDocker ComposeРазвёртывание тестового кластера
Решардингredis-cli / Python-скрипт с CLUSTER SETSLOTПеремещение слотов для выравнивания

4. Этапы выполнения

Этап 1: Развёртывание шардированного кеша и мониторинга (оценка 2 часа)

Действия

  1. Создать docker-compose.yml для Redis Cluster из 3 мастер-нод и 3 реплик (опционально). Использовать образ redis:7-alpine.
  2. Инициализировать кластер: redis-cli --cluster create <node1>:6379 <node2>:6379 <node3>:6379 --cluster-replicas 0.
  3. Установить и настроить redis_exporter для каждой ноды (через sidecar контейнеры).
  4. Запустить Prometheus (prometheus.yml) с job-ами для каждого exporter.
  5. Запустить Grafana, импортировать дашборд для Redis Cluster (ID: 14888 или создать свой).
  6. Убедиться, что в Grafana видны метрики redis_db_keys, redis_commands_processes_per_second, redis_cpu_usage per shard.

Ожидаемый результат этапа Рабочий Redis Cluster из 3 шардов, все метрики доступны в Grafana.


Этап 2: Генерация неравномерной нагрузки и снятие baseline (оценка 1 час)

Действия

  1. Создать файл hot_keys.txt со списком 2000 ключей (20% от 10 000).
  2. Написать скрипт load_generator.py:
import redis
import random

r = redis.RedisCluster(host='localhost', port=6379)
keys = [f"key:{i}" for i in range(10000)]
hot_keys = random.sample(keys, 2000)  # 20% hot keys

while True:
    for _ in range(1000):
        key = random.choice(keys)
        if key in hot_keys:
            for _ in range(4):  # 4x больше запросов
                r.get(key)
        else:
            r.get(key)
  1. Запустить генератор на 5–10 минут.
  2. В Grafana построить график rate(redis_commands_processes_per_second{instance=~".*"}) by (instance) — увидеть перекос.

Ожидаемый результат этапа Один шард (содержащий большинство hot keys) показывает utilisation (по ops/sec) в 2–3 раза выше, чем другие.


Этап 3: Реализация hot shard detection (оценка 2 часа)

Действия

  1. Определить метрику utilisation: ops_per_shard / average_ops_per_shard. Использовать PromQL: avg by (instance) (rate(redis_commands_processes_per_second[1m])).
  2. Написать Python-скрипт detector.py, который периодически запрашивает Prometheus API:
import requests
import json

prom_url = "http://localhost:9090"
query = 'avg by (instance) (rate(redis_commands_processes_per_second[1m]))'
response = requests.get(f"{prom_url}/api/v1/query", params={'query': query})
results = response.json()['data']['result']
avg_util = sum(float(r['value'][1]) for r in results) / len(results)
hot_shards = []
for r in results:
    util = float(r['value'][1])
    if util > 1.2 * avg_util:
        hot_shards.append({'instance': r['metric']['instance'], 'util_ratio': util/avg_util})
  1. Добавить логирование: print(f"Hot shards detected: {hot_shards}").
  2. Настроить запуск скрипта каждые 30 секунд через crontab или systemd timer.
  3. Проверить: при неравномерной нагрузке скрипт выводит ID горячего шарда.

Ожидаемый результат этапа Скрипт корректно идентифицирует шард(-ы) с utilisation >120% от среднего.


Этап 4: Разработка и выполнение решардинга (оценка 2 часа)

Действия

  1. Изучить механизм перемещения слотов в Redis Cluster. Пример команды:
    • redis-cli --cluster reshard <node>:6379 --cluster-from <from_node_id> --cluster-to <to_node_id> --cluster-slots <count> --cluster-yes
  2. Написать функцию rebalance_shard(hot_node_id, target_node_id, slots_count):
import subprocess
def rebalance(hot_node, target_node, slots):
    cmd = f"redis-cli --cluster reshard 127.0.0.1:6379 --cluster-from {hot_node} --cluster-to {target_node} --cluster-slots {slots} --cluster-yes"
    subprocess.run(cmd, shell=True, check=True)
  1. Интегрировать вызов решардинга в detector.py:
    • При обнаружении hot shard с utilisation > 120% более 2 минут подряд (чтобы избежать флаппинга).
    • Выбрать наименее загруженный шард как target.
    • Определить количество слотов для перемещения: (util_ratio - 1.0) * total_slots / (num_shards - 1) (упрощённо).
    • Выполнить решардинг.
  2. После решардинга подождать 60 секунд и проверить метрики — utilisation должен выровняться.

Ожидаемый результат этапа Автоматическое перемещение части слотов с горячего шарда на холодный, utilisation всех шардов стабилизируется ниже 120% от среднего.


Этап 5: Тестирование и документирование (оценка 1 час)

Действия

  1. Остановить генератор трафика, сбросить кластер (удалить данные, пересоздать).
  2. Запустить сценарий: генерация неравномерной нагрузки → срабатывание детектора → автоматический решардинг → стабилизация.
  3. Зафиксировать скриншоты Grafana до и после решардинга.
  4. Написать краткое README: архитектура, как запустить, как проверить.
  5. Сохранить все файлы (docker-compose, скрипты) в репозиторий.

Ожидаемый результат этапа Воспроизводимый тестовый стенд с автоматической детекцией и решардингом; utilisation всех шардов <120% среднего.


5. Критерии приемки (Definition of Done)

  • Развёрнут Redis Cluster из ≥3 нод (локально Docker).
  • Prometheus собирает метрики ops/sec по каждому шарду.
  • Скрипт detector.py запускается каждые 30 секунд и логирует hot shards.
  • При utilisation горячего шарда >120% от среднего в течение 2 минут автоматически запускается решардинг.
  • После решардинга utilisation всех шардов становится <120% среднего (проверено на 3 запусках).
  • В Grafana создан дашборд с панелью «Utilisation ratio per shard».
  • README содержит инструкцию по запуску и описание алгоритма.

6. Ожидаемый результат

  • Основной артефакт Папка с проектом, содержащая:
    • docker-compose.yml
    • load_generator.py
    • detector.py (с интегрированным решардингом)
    • prometheus.yml
    • README.md
  • Демонстрация: Скриншоты Grafana до и после решардинга, подтверждающие целевое состояние utilisation.
  • Опционально Запись консоли с логами работы detector.py.

7. Возможные сложности и их решение

СложностьРешение
Redis Cluster требует минимум 3 мастер-ноды, а Docker может не хватить ресурсовИспользовать мини-образ redis:7-alpine и ограничить память 128MB на ноду
Prometheus не видит метрики redis_exporterПроверить network в docker-compose (общая сеть), настроить targets в prometheus.yml
Перемещение слотов вызывает кратковременную недоступность ключейРешардинг только на hot shard, переносить небольшими порциями (например, 10 слотов за раз)
Hot shard detection может срабатывать на всплески (false positive)Добавить стабильность: требовать превышение порога >2 минуты (скользящее окно)
Выбор количества слотов для перемещения нетривиаленИспользовать формулу: slots_to_move = (util_ratio - 1.2) * total_slots / (num_shards * 2) (эмпирическая)

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Развёртывание кластера и мониторинга2 часа
Этап 2: Генерация нагрузки и baseline1 час
Этап 3: Hot shard detection2 часа
Этап 4: Решардинг2 часа
Этап 5: Тестирование и документирование1 час
Итого8 часов

Примечание Для первого раза рекомендуется выделить 10–12 часов, учитывая возможные отладки.


9. Связанные вопросы из базы знаний

ВопросТема
110Redis cluster sharding and resharding basics
115Consistent hashing and virtual nodes
221Hot key detection in distributed cache
315Prometheus monitoring of cache systems
417Auto-scaling and rebalancing strategies
502Handling data skew in sharded systems
638Redis Cluster slot migration commands
745Designing a custom rebalancing algorithm
819Grafana dashboards for cache metrics
891Capacity planning for cache clusters

10. Чек-лист самопроверки

  • Я развернул шардированный кеш (Redis Cluster) и проверил, что все ноды в состоянии cluster_state:ok.
  • Убедился, что load_generator.py действительно создаёт неравномерное распределение нагрузки (через redis-cli --cluster check видно перекос keys per slot).
  • Проверил, что detector.py не падает с ошибками при запросе Prometheus API.
  • Протестировал решардинг сначала вручную, потом автоматически — убедился, что utilisation выравнивается.
  • Сохранил скриншоты и README, чтобы воспроизвести результат без дополнительных объяснений.