Шардирование и выбор ключа шардирования
↑ 6.1 Масштабирование и надёжность данных · ← Предыдущая · → Следующая
Зачем это на собесе
Шардирование — крайняя мера масштабирования записи; нужно знать стоимость и правила выбора ключа.
Что это
Шардирование — горизонтальное разделение данных между независимыми серверами (шардами): каждый хранит подмножество строк. В отличие от партиционирования (внутри одной БД) и репликации (копии одних данных).
Когда нужно
Сначала исчерпайте: оптимизацию запросов и индексов, кэш, read replicas, партиционирование, вертикальное масштабирование, архивацию. Шардирование нужно, когда:
- запись или объём данных не помещаются на одном сервере;
- требуется изоляция арендаторов (tenant) или территориальное размещение (data residency).
Цена: сложность, отсутствие JOIN и транзакций между шардами, решардинг, операционные затраты.
Стратегии
| Стратегия | Как | Плюсы | Минусы |
|---|---|---|---|
| Range (диапазоны) | по диапазонам ключа (даты, id) | эффективные range-запросы | горячие шарды (последний диапазон принимает всю запись) |
| Hash | hash(key) mod N | равномерное распределение | range-запросы по всем шардам; изменение N перемещает почти всё |
| Consistent hashing | кольцо, виртуальные узлы | при смене N переезжает малая доля | сложнее |
| Directory / lookup | таблица «ключ → шард» | гибкость, ручное размещение | узкое место и сложность справочника |
| Geo | по региону пользователя | низкая задержка, соответствие законам | перекосы |
| Tenant-based | один арендатор на шарде | изоляция, простота | неравномерность (крупные арендаторы) |
Выбор ключа шардирования
Критерии хорошего ключа:
- Высокая кардинальность и равномерное распределение (нет «горячих» значений);
- Соответствие основным запросам: большая часть запросов содержит ключ и попадает на один шард (избегаем scatter-gather);
- Отсутствие монотонности при hash-подходе (последовательные id дают перекос при range);
- Неизменность: смена значения ключа = перенос строки между шардами;
- Данные, которые часто соединяются, хранятся на одном шарде (colocation):
customer_idдля клиентов, заказов, платежей.
Примеры: tenant_id (SaaS), user_id (пользовательские данные), region. Плохие: created_at (горячий последний шард), низкокардинальный status, country (перекосы).
Проблемы
- Кросс-шардовые запросы: запрос без ключа идёт на все шарды (scatter-gather), агрегации требуют слияния;
- Кросс-шардовые транзакции: 2PC или саги; ограничения целостности между шардами не работают;
- Глобальные уникальные id: Snowflake, UUID v7, ULID, диапазоны выделяются каждому шарду; автоинкремент сломан;
- Вторичные индексы: локальные (быстро пишутся, но запрос по не-ключу идёт на все шарды) или глобальные (сложнее);
- Решардинг: перенос данных при росте; нужна онлайн-миграция (двойная запись, бэкфилл, переключение) и заранее заложенные логические шарды (например 256 логических на 4 физических сервера — легко переселять);
- Горячие ключи (знаменитость, крупный арендатор): дополнительный суффикс, выделенный шард;
- Справочники реплицируются на все шарды (reference tables).
Реализации
| Решение | Комментарий |
|---|---|
| Citus (расширение PostgreSQL) | распределённые таблицы, colocation, reference tables |
| Vitess | шардирование MySQL (YouTube) |
| CockroachDB, YugabyteDB, TiDB, Spanner | распределённые SQL «из коробки», автоматический решардинг |
| MongoDB sharding | shard key, mongos, балансировщик |
| Cassandra, ScyllaDB, DynamoDB | hash-партиционирование по partition key |
| Redis Cluster | 16384 слота |
| ClickHouse Distributed | шардирование таблиц |
| Application-level | маршрутизация в коде приложения/прокси |
Подход в приложении
public sealed class ShardRouter(IReadOnlyList<string> connectionStrings)
{
public string For(long tenantId) => connectionStrings[(int)(Hash(tenantId) % connectionStrings.Count)];
}Лучше — логические шарды и справочник размещения: tenant_id → logical_shard → physical_server, чтобы перенос был сменой записи, а не пересчётом хэша.
Альтернативы шардированию
- вертикальное масштабирование и оптимизация;
- партиционирование + холодное хранилище;
- CQRS: отдельное хранилище для чтения (ClickHouse, Elasticsearch);
- архивирование старых данных;
- управляемые распределённые БД.
Вопросы с ответами
Чем шардирование отличается от репликации и партиционирования?
Репликация копирует одни и те же данные. Партиционирование делит таблицу внутри одного сервера. Шардирование распределяет разные данные по разным серверам для масштабирования записи и объёма.
Как выбрать ключ шардирования?
Высокая кардинальность, равномерность, соответствие основным запросам (чтобы они шли на один шард), неизменность значения и colocation связанных данных.
Что делать с глобальной уникальностью id?
Генерировать id без центрального счётчика: Snowflake, UUID v7/ULID, либо выделять диапазоны каждому шарду.