Шардирование

Материал из llmdocs — справочник разработчика
Портал: Базы данных
Шардирование
Сутьразные строки — на разных серверах
Отличие от репликациирепликация копирует данные, шардирование делит
Стратегиипо диапазону, по хешу, по справочнику (directory), географическое
Ключевое решениевыбор ключа шардирования
Решения для PostgreSQLCitus, партиционирование + FDW
Решения для MySQLVitess, ProxySQL
ВстроенноеMongoDB, Cassandra, CockroachDB, YDB, ClickHouse

Шардирование (англ. sharding) — способ горизонтального масштабирования базы данных, при котором строки одной логической таблицы распределяются между несколькими независимыми серверами — шардами. Каждый шард хранит только часть данных и обслуживает только запросы к ней. Шардирование позволяет выйти за пределы одной машины по объёму данных и, главное, по нагрузке на запись — но делает систему заметно сложнее.[1]

Шардирование, репликация и партиционирование

Эти понятия часто путают:

  • Репликация хранит копии одних и тех же данных на нескольких серверах. Она повышает доступность и масштабирует чтение, но каждая реплика должна принимать все записи.
  • Партиционирование делит таблицу на части внутри одного сервера (например, по месяцам) — в PostgreSQL это декларативное партиционирование.[6] Оно упрощает удаление старых данных и ускоряет запросы, но не добавляет ресурсов.
  • Шардирование — это партиционирование между серверами. На практике его сочетают с репликацией: у каждого шарда есть свои реплики.

Прежде чем шардировать, обычно исчерпывают более простые средства: вертикальное масштабирование (больше процессоров, памяти, быстрые диски), индексы и оптимизацию запросов, реплики для чтения, кэширование, архивирование старых данных. Современный сервер с терабайтами NVMe и сотнями ядер выдерживает очень многое.

Ключ шардирования

Каждая строка попадает на шард в зависимости от значения ключа шардирования — одного или нескольких столбцов. Выбор ключа — самое важное и труднообратимое решение:[2]

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

Стратегии распределения

Стратегия Как работает Плюсы Минусы
По диапазону id 1–1 млн → шард 1, 1–2 млн → шард 2 эффективные запросы по диапазону «горячий» последний шард при монотонных ключах
По хешу hash(key) mod N равномерное распределение запросы по диапазону идут на все шарды
По справочнику таблица «ключ → шард» гибкость, ручной перенос клиентов справочник — отдельная точка отказа
Географическая данные региона — в регионе задержки, требования законов о локализации неравномерность нагрузки

Наивное hash(key) mod N плохо переносит изменение числа шардов: при переходе с 4 на 5 почти все ключи меняют шард. Поэтому используют согласованное хеширование (consistent hashing),[5] при котором при добавлении узла переезжает лишь ~1/N данных, — как в Amazon Dynamo и Cassandra,[7] — или заранее создают много виртуальных шардов (например, 1024), которые затем распределяют и перемещают между физическими серверами целиком.

Маршрутизация запросов

Кто-то должен знать, куда отправить запрос:

  • само приложение — библиотека вычисляет шард по ключу;
  • прокси или координатор — Vitess (vtgate) для MySQL,[3] координатор Citus для PostgreSQL, mongos в MongoDB;[4]
  • встроенная распределённая СУБД — CockroachDB, YugabyteDB, YDB, TiDB, Cassandra, ClickHouse сами шардируют и перемещают данные, предоставляя единый SQL-интерфейс.

Сложности

  • Запросы между шардами (scatter-gather) — запрос без ключа шардирования рассылается на все шарды, а результаты собираются и сортируются координатором. Медленно и дорого.
  • Соединения (JOIN) между данными на разных шардах. Решения — совместное размещение (co-location) связанных таблиц по одному ключу, дублирование небольших справочных таблиц на все шарды.
  • Распределённые транзакции требуют двухфазной фиксации и медленнее локальных; многие системы их ограничивают.
  • Уникальность и идентификаторы — автоинкремент не работает глобально; используют UUID (лучше UUIDv7), Snowflake-подобные ID или диапазоны на шард.
  • Перебалансировка — перенос данных при добавлении шарда без простоя и с согласованностью.
  • Эксплуатация — резервное копирование, миграции схемы, мониторинг умножаются на число шардов.

Эти компромиссы — частный случай общих проблем распределённых систем (см. CAP-теорема).

Пример: Citus

CREATE EXTENSION citus;
CREATE TABLE events (tenant_id bigint, id bigserial, payload jsonb,
                     PRIMARY KEY (tenant_id, id));
SELECT create_distributed_table('events', 'tenant_id');   -- шардирование по клиенту
SELECT create_reference_table('countries');               -- копия на каждом узле

После этого запросы с WHERE tenant_id = 42 идут на один узел, а агрегаты по всем клиентам выполняются параллельно на всех.

См. также

Источники

  1. Kleppmann — Designing Data-Intensive Applications, ch. 6 Partitioning (O'Reilly)
  2. Citus documentation — Choosing the distribution column
  3. Vitess documentation — Sharding
  4. MongoDB Manual — Sharding
  5. Karger et al. — Consistent Hashing and Random Trees (STOC 1997)
  6. PostgreSQL — Table Partitioning
  7. DeCandia et al. — Dynamo: Amazon's Highly Available Key-value Store (SOSP 2007)