Распределение запросов по ключу хеширования: одна реплика перегружена, хотя средняя загрузка кластера низкая. Какой эффект это объясняет?
Это объясняется перекосом распределения нагрузки: небольшой набор часто используемых ключей, или «горячих ключей», направляет непропорционально много запросов на одну реплику. Средняя загрузка кластера скрывает локальное узкое место, поэтому добавление реплик само по себе может почти не улучшить ситуацию.
Горизонтальное масштабирование предполагает, что запросы можно распределить между несколькими экземплярами. Для запросов, связанных с конкретным объектом, часто используют маршрутизацию по ключу: одинаковый ключ направляется на одну и ту же реплику, что упрощает работу с кэшем и локальным состоянием.
Такой подход появился как практический компромисс между равномерностью нагрузки и необходимостью сохранять affinity — направлять связанные запросы в одно место. Однако равномерное распределение ключей не гарантирует равномерное распределение объёма трафика.
Если один ключ или небольшая группа ключей значительно популярнее остальных, соответствующая реплика получает непропорционально большую долю запросов. Она начинает ограничивать задержку и пропускную способность всей системы, даже когда остальные реплики почти простаивают.
Опасно ориентироваться только на средние метрики кластера. Они могут показывать нормальную загрузку CPU и сети в целом, скрывая перегруженную реплику, рост её очереди, ошибок или хвостовой задержки.
Хеширование распределяет ключи, а не обязательно запросы. Если частоты обращений к ключам различаются, то даже идеальное распределение самих ключей по репликам не даст равного распределения нагрузки. Перекос также может усилиться при небольшом количестве ключей, неудачном диапазоне хеширования или фиксированной маршрутизации клиентов.
Сначала нужно сравнить метрики по отдельным репликам: запросы в секунду, активные запросы, задержки, ошибки, длину очередей и распределение наиболее популярных ключей. Средние значения по кластеру следует дополнять квантилями и максимальными значениями по экземплярам.
Варианты устранения зависят от характера нагрузки:
Главный принцип: балансировать нужно не только число ключей или реплик, но и фактический объём работы. При этом перераспределение не должно нарушать требования к согласованности и порядку обработки связанных запросов.
Сервис профилей распределял запросы по идентификатору пользователя. Большинство пользователей генерировало небольшой трафик, но несколько крупных клиентов выполняли массовые операции, поэтому их ключи стали «горячими». Одна реплика регулярно достигала предела обработки, тогда как средняя загрузка кластера оставалась низкой.
Рассматривались три варианта. Отказ от маршрутизации по ключу улучшал балансировку, но увеличивал промахи локального кэша и межрепличный обмен. Простое добавление реплик почти не помогало, поскольку горячие ключи продолжали попадать на прежние сегменты. Разделение крупных клиентов на виртуальные под ключи давало лучший баланс, но требовало изменить чтение и запись данных.
Выбрали виртуальное разбиение только для клиентов, превышающих установленный порог нагрузки, а результаты часто читаемых операций дополнительно кэшировали. После этого нагрузка распределилась между несколькими репликами, а хвостовая задержка снизилась; при этом для обычных клиентов сохранилась прежняя схема маршрутизации.
Нет. Если горячий ключ по-прежнему однозначно сопоставляется с одной репликой, новые экземпляры не получат его запросы. Добавление реплик помогает только тогда, когда механизм маршрутизации способен перераспределить фактическую нагрузку, а не только расширить число доступных получателей.
Количество ключей не равно объёму работы. Десять редко используемых ключей могут создавать меньшую нагрузку, чем один популярный ключ. Поэтому при оценке балансировки нужно анализировать веса ключей: частоту запросов, стоимость операций и объём данных, а не только число ключей на реплику.
Если перенос выполняется часто, запросы начинают конкурировать за миграцию состояния, кэш прежней реплики становится бесполезным, а связанные операции могут временно попадать в разные места. Это способно вызвать дополнительный сетевой обмен, промахи кэша и проблемы с порядком обработки. Перенос следует делать контролируемо, с учётом состояния, согласованности и периода стабилизации маршрутизации.