Шардирование
| Суть | разные строки — на разных серверах |
|---|---|
| Отличие от репликации | репликация копирует данные, шардирование делит |
| Стратегии | по диапазону, по хешу, по справочнику (directory), географическое |
| Ключевое решение | выбор ключа шардирования |
| Решения для PostgreSQL | Citus, партиционирование + FDW |
| Решения для MySQL | Vitess, 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 идут на один узел, а агрегаты по всем клиентам выполняются параллельно на всех.
См. также
Источники
- Kleppmann — Designing Data-Intensive Applications, ch. 6 Partitioning (O'Reilly)
- Citus documentation — Choosing the distribution column
- Vitess documentation — Sharding
- MongoDB Manual — Sharding
- Karger et al. — Consistent Hashing and Random Trees (STOC 1997)
- PostgreSQL — Table Partitioning
- DeCandia et al. — Dynamo: Amazon's Highly Available Key-value Store (SOSP 2007)