← Все статьи

Группы шардов в PlanetScale Neki: начинайте с запросов, не со списка таблиц

Как в шардированном Postgres (Neki) проектировать группы шардов по паттернам запросов: совместное размещение, справочники, Reshard и алгоритмы ключей.

Группы шардов в PlanetScale Neki: начинайте с запросов, не со списка таблиц
Содержание

Коротко

В шардированном Postgres от PlanetScale (Neki) строки одной таблицы разъезжаются по нескольким кластерам — шардам.

Быстрые запросы и транзакции с полной семантикой Postgres чаще всего те, что остаются на одном шарде.

Единица совместного размещения в Neki — группа шардов: таблицы с одинаковым способом хеширования ключа кладут связанные строки рядом.

Проектировать группу нужно от паттернов запросов, а не от списка таблиц в схеме.

Что произошло

В прошлых материалах PlanetScale роутер сам собирал join customers и orders, потому что каждую таблицу шардировали по своему id.

Исправление простое на словах: шардировать orders по customer_id тем же хешем, что и клиентов. В Neki этот приём оформлен как группа шардов.

Представьте склад, где заказ клиента собирают с одной полки: не бегать по всему ангару за каждым артикулом.

Группа шардов — это правило «какие строки жить рядом», чтобы join и транзакция не разъезжались по кластерам.

В топологии данных группа задаёт индекс шардирования (например by_customer на колонке customer_id с типом xxhash) и диапазоны ключей по физическим шардам.

Таблицы orders, items, addresses наследуют этот индекс; у customers колонка ключа — id, но хеш тот же. Заказы, позиции и адреса одного клиента оказываются на одном шарде.

Справочники вроде countries или currencies к клиенту не привязаны. Если положить их на один шард, многие join станут межшардовыми.

Neki объявляет такие таблицы справочными: полная копия на каждом шарде группы. Начальная загрузка — сценарий MoveTables, дальше роутер размножает запись на все копии.

Каталог товаров живёт другой жизнью: естественный ключ — SKU. Добавляют вторую группу (catalog с by_sku), часто на тех же физических шардах, но с другим разрезом пространства ключей.

Отдельная группа main держит метаданные всей базы на одном шарде — последовательности, сведения о схеме и прочее «общее» для топологии.

Индекс шардирования может брать не «сырую» колонку, а SQL-выражение: lower(sku) или left(order_id, 4).

Так смешивают регистр артикулов из разных поставщиков или вытаскивают код магазина из составного идентификатора заказа.

Алгоритм по умолчанию — xxhash: равномерное распределение по 64-битному пространству ключей.

Есть ещё range (целые диапазоны на шарды — удобно для «старого» географического разреза) и modulo (классика учебников: value % N).

Модуль предсказуем, но смена модуля почти всегда двигает почти все строки. Для роста в проде его обычно не советуют.

Запрос «заказ по id», когда таблица шардирована по customer_id, без подсказки разлетается по всем шардам.

Глобальный вторичный индекс (GSI) — отдельная таблица соответствий, которая связывает id заказа с customer_id.

Первое одношардовое чтение достаёт ключ шарда; второе — саму строку в orders. Пока таблица соответствий не заполнена, индекс не включают.

Размещение не навсегда. Сценарий Reshard меняет ключ или число шардов, когда паттерны запросов вырастут иначе, чем схема при миграции.

Тот же контур используют, чтобы впервые разрезать нешардированную таблицу или развести группы по разным физическим кластерам, когда нагрузка перестанет быть симметричной.

Почему это важно

Шардирование часто рисуют как «разрезать большую таблицу».

На деле узкое место — не размер таблицы, а доля запросов, которые трогают несколько шардов.

Каждый такой разлёт — сбор результатов роутером.

Хуже того: транзакция без общей атомарной фиксации и без общего снимка данных. Neki фиксирует каждый шард отдельно.

Пока join клиента с заказами локален, вы получаете привычную семантику Postgres.

Как только связанные строки разъехались — скорость и корректность меняют цену одновременно.

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

Это решение архитектуры данных, а не косметика файла топологии.

Reshard снимает страх «навсегда выбрать неправильный ключ», но не отменяет стоимость переезда.

Лучше начать с запросов, которые реально платят за задержку, чем с красивого списка сущностей из ER-диаграммы.

На практике

Сначала выпишите горячие пути — заказы клиента, корзина, оформление.

Только потом раскладывайте таблицы по группам: иначе схема красиво делится на сущности, а прод платит за межшардовые join.

  1. Найдите ключ, который делят самые частые запросы и транзакции; все таблицы с этим ключом — кандидаты в одну группу.
  2. Для корневой сущности (customers.id) задайте наследование индекса с другой колонкой, чтобы хеш совпадал с customer_id у дочерних таблиц.
  3. Справочники без «владельца» объявите справочными, иначе join с ними размажет нагрузку по шардам.
  4. Отдельный домен (каталог по SKU) — отдельная группа; физические шарды можно делить или развести позже.
  5. Предпочитайте xxhash; range и modulo — если приложение уже так живёт или нужен явный географический разрез; модуль для масштабирования почти всегда ловушка.
  6. Для поиска не по ключу шарда заведите GSI и не включайте его, пока не заполните таблицу соответствий.
  7. Заложите Reshard в план роста: смена ключа или числа шардов — штатный сценарий, не авария.

Итог

В Neki группа шардов — единица совместного размещения: одинаковый хеш ключа кладёт связанные строки на один кластер Postgres.

Проектирование начинается с запросов и транзакций, которые должны остаться локальными, а не со списка таблиц.

Справочники, отдельные группы, выражения в индексе, GSI и Reshard — инструменты подогнать схему под жизнь продукта.

Если join уже «гуляет» по шардам — сначала ищите, какой ключ должен держать горячий путь рядом, а не добавляйте ещё один кластер «на всякий случай».

Комментарии

Загрузка комментариев…