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