Выбор первичного ключа для максимальной производительности
Выбор колонок для первичного ключа строковой таблицы оказывает определяющее влияние на возможности YDB по масштабированию нагрузки и повышению производительности.
Общие рекомендации по выбору первичного ключа:
- Следует избегать ситуаций, когда основная часть нагрузки приходится на одну партицию строковой таблицы. Чем равномернее нагрузка распределяется по партициям, тем большая производительность может быть достигнута.
- Следует уменьшать количество партиций, которые могут быть затронуты в одном запросе. Более того, если запрос затрагивает не более одной партиции, то он выполняется по специальному упрощенному протоколу, что существенно увеличивает скорость и экономит ресурсы.
Все строковые таблицы в YDB отсортированы по возрастанию первичного ключа. Это означает, что запись в строковую таблицу данных с монотонно возрастающим первичным ключом приведет к добавлению новых данные в конец таблицы. Так как YDB разделяет строковые таблицы на партиции по диапазонам ключей, вставки будут обрабатываться одним конкретным сервером, отвечающим за "последнюю" партицию. Сосредоточение нагрузки на одном сервере приведет к медленной загрузке данных и неэффективному использованию распределенной системы.
В качестве примера рассмотрим запись лога пользовательских событий в строковую таблицу со схемой ( timestamp, userid, userevent, PRIMARY KEY (timestamp, userid) )
.
Значения колонки timestamp
монотонно возрастают; как следствие, все новые записи будут добавляться в конец строковой таблицы, и "последняя" партиция, которая отвечает за данный диапазон ключей, будет обслуживать все операции вставки в таблицу. Это приведет к невозможности масштабирования нагрузки на вставку, производительность будет ограничена одним процессом обслуживания этой партиции, и не будет расти с добавлением серверов в кластер.
В YDB поддерживается автоматическое разделение партиции при достижении порогового размера или нагрузки. Но в рассматриваемом случае после разделения новая партиция начнет опять принимать всю нагрузку на вставку, и ситуация повторится.
Приемы, позволяющие равномерно распределить нагрузку по партициям строковой таблицы
Изменение порядка следования компонент ключа
Запись данных в строковую таблицу со схемой ( timestamp, userid, userevent, PRIMARY KEY (timestamp, userid) )
приводит к неравномерной нагрузке на партиции таблицы из-за монотонно возрастающего первичного ключа. Изменение порядка следования компонент ключа таким образом, чтобы монотонно возрастающая часть не была первой компонентой, может помочь более равномерно распределить нагрузку. Если изменить определение первичного ключа строковой таблицы на PRIMARY KEY (userid, timestamp)
, то при достаточном количестве пользователей, генерирующих события, запись в БД будет распределена по партициям более равномерно.
Использование хеша от значений ключевых колонок в качестве первичного ключа
Для получения более равномерного распределения операций между партициями строковой таблицы, а также уменьшения размеров внутренних структур данных, необходимо увеличить разнообразие значений "префикса" (начальной части) первичного ключа. Для этого можно включить в первичный ключ значение хеш-кода от всего первичного ключа или его части.
Например, в рассматриваемую строковую таблицу со схемой ( timestamp, userid, userevent, PRIMARY KEY (userid, timestamp) )
можно включить дополнительное поле, рассчитываемое как хеш-код: userhash = HASH(userid)
. В результате схема таблицы преобразуется к следующему виду:
( userhash, userid, timestamp, userevent, PRIMARY KEY (userhash, userid, timestamp) )
При правильном выборе функции хеширования строки будут распределены достаточно равномерно по всему пространству ключей, что приведет к более равномерной нагрузке на систему. При этом наличие полей userid, timestamp
в составе ключа после поля userhash
сохраняет локальность и сортировку данных по времени для конкретного пользователя.
Расчёт значения поля userhash
в описанном выше примере должен осуществляться приложением, и явно указываться как при вставке новых записей таблицы, так и при доступе к записям по первичному ключу.
Уменьшение количества партиций, затрагиваемых в одном запросе
Предположим, что основной сценарий работы с данными строковой таблицы — прочитать все события по конкретному userid
. Тогда при использовании схемы строковой таблицы ( timestamp, userid, userevent, PRIMARY KEY (timestamp, userid) )
каждое чтение будет затрагивать все партиции таблицы. При этом, каждая партиция будет просканирована полностью, так как строки, относящиеся к конкретному userid
, расположены в заранее неизвестном порядке. Изменение порядка следования компонент ключа ( timestamp, userid, userevent, PRIMARY KEY (userid, timestamp) )
приведет к тому, что все строки, относящиеся к конкретному userid
, будут следовать друг за другом. Такое расположение строк положительно повлияет на скорость чтения информации по конкретному userid
, и сократит нагрузку.
Значение NULL в ключевой колонке
В YDB все колонки, включая ключевые, могут содержать значение NULL. Использование NULL в качестве значений в ключевых колонках не рекомендуется. По стандарту языка SQL (ISO/IEC 9075) значение NULL нельзя сравнивать с другими значениями, вследствие чего использование лаконичных конструкции SQL с простыми операторами сравнения может приводить, например, к тому что, строки, содержащие значение NULL, могут быть пропущены при фильтрации.
Ограничение размера строки
Для достижения хорошей производительности не рекомендуется записывать в БД строки размером более 8 МБ и ключевые колонки размером более 2 КБ.