Получить доступ

Что делать с горячими ключами, на которые приходится непропорционально большая нагрузка?

SeniorШардирование и распределение данных
Ещё спрашивают так:
  • Как справиться с проблемой знаменитостей в соцсети?
  • Один шард перегружен, остальные простаивают — что делать?
  • Как распределить нагрузку на популярный товар на распродаже?
Коротко

Горячий ключ не делится ни хешем, ни consistent hashing: все запросы к нему идут в один узел. Нужно размножить ключ для чтения или разбить его для записи.

  • Чтение: локальный кэш на серверах приложений, реплики ключа под разными суффиксами, CDN
  • Запись: шардированный счётчик (key#1…key#N), агрегирование пачками, очередь
  • Сначала обнаружить: топ ключей по нагрузке (count-min, мониторинг Redis hotkeys)

А ещё Знаменитости в ленте: их посты не раскладываем всем подписчикам, а подмешиваем при чтении

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Хеширование делит ключи равномерно, но не нагрузку: если один ключ получает 30% запросов, его узел получает минимум 30%. Примеры: популярный товар на распродаже, пост знаменитости, глобальный счётчик, конфигурация, которую читает каждый запрос, крупный клиент в мультитенантной базе.

Горячее чтение:

  • локальный кэш в памяти сервиса с коротким TTL (секунды) — запросы к горячему ключу вообще не доходят до общего кэша. Caffeine, словарь с TTL; согласованность — в пределах TTL;
  • реплики ключа — хранить копии под key#1 … key#N на разных узлах кэша и читать случайную. Запись обновляет все копии;
  • CDN и кэш на краю для публичных данных;
  • объединение запросов (request coalescing, single flight) — при промахе один запрос идёт в базу, остальные ждут его результат.

Горячая запись:

  • шардированный счётчик — лайки поста храним в N подсчётчиках likes:post#k, инкремент — в случайный, чтение — сумма. N выбирают по нагрузке, можно увеличивать динамически для горячих;
  • агрегирование — копить инкременты в памяти или очереди и писать пачками раз в секунду;
  • очередь вместо прямой записи — сглаживает пики, если результат не нужен мгновенно;
  • остаток товара — разбить остаток на части по узлам (как бакеты токенов) или резервировать пачками.

Перекос в шардировании:

  • вынести крупного клиента в отдельный шард (справочник);
  • составной ключ: (tenant_id, user_id) вместо tenant_id;
  • для временных рядов — добавить к ключу случайную «соль» из небольшого числа значений, а при чтении опрашивать все варианты.

Лента и знаменитости (celebrity problem). Обычно пост раскладывают в ленты подписчиков при публикации (fan-out on write). Для аккаунта с десятками миллионов подписчиков это миллионы записей на один пост. Гибрид: посты знаменитостей не раскладываются, а подмешиваются в ленту при чтении (fan-out on read).

Обнаружение: метрики нагрузки по шардам, сэмплирование ключей, count-min sketch для топа ключей, redis-cli --hotkeys, логи медленных запросов.

Пример

import random
N = 16
def incr_like(r, post_id):
    r.incr(f"likes:{post_id}#{random.randrange(N)}")     # запись размазана по 16 ключам

def likes(r, post_id):
    return sum(int(v or 0) for v in r.mget([f"likes:{post_id}#{i}" for i in range(N)]))

Что хотят услышать

  • Почему хеширование не помогает против горячего ключа.
  • Разные решения для чтения и для записи.
  • Шардированные счётчики и локальный кэш как основные приёмы.
  • Fan-out on write/read для знаменитостей и как находить горячие ключи.

Частые ошибки

  • Предлагать «добавить шардов» — горячий ключ всё равно в одном.
  • Забыть, что реплики ключа для чтения усложняют запись.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем