Шардинг
Шардинг (Sharding) — разбиение на фрагменты, осколки
TL;DR
Шардинг — горизонтальное разбиение данных на независимые части (шарды), распределённые по разным узлам базы данных. Позволяет масштабироваться горизонтально, но усложняет запросы и транзакции между шардами.
Краткое определение
Шардинг — метод горизонтального масштабирования базы данных, при котором данные разделяются на независимые части (шарды) по определённому ключу, и каждый шард размещается на отдельном сервере.
Оригинал и перевод
- Язык: английский
- Оригинал: Sharding (от shard — осколок, фрагмент)
- Буквальный перевод: разбиение на фрагменты, осколки
- Русские аналоги: шардирование, горизонтальное разбиение, секционирование
Синонимы и варианты написания
- Шардирование
- Горизонтальное партиционирование
- Database sharding
- Horizontal partitioning
Где используется
- Крупные БД: события, логи, пользовательские данные (миллиарды записей)
- Высоконагруженные OLTP-системы: социальные сети, мессенджеры, e-commerce
- Распределённые СУБД: MongoDB, Cassandra, CockroachDB
- SaaS платформы: данные клиентов по шардам (tenant-per-shard)
Когда это важно
Шардинг критичен при следующих условиях:
- Объём данных: таблица не помещается на один сервер (100GB+)
- Нагрузка на запись: один сервер не справляется с количеством INSERT/UPDATE
- Географическое распределение: данные пользователей в разных регионах
- Мультитенантность: изоляция данных клиентов по шардам
Как работает шардинг
Принцип
Данные → Выбор шард-ключа → Маршрутизация → Шард 1, 2, 3... Пример: user_id = 123 → hash(123) % 3 = 0 → Шард 1 user_id = 456 → hash(456) % 3 = 1 → Шард 2 user_id = 789 → hash(789) % 3 = 2 → Шард 3
Стратегии шардирования
1. Шардирование по диапазону (Range-based)
Данные распределяются по диапазонам ключа:
Шард 1: user_id 1-100000 Шард 2: user_id 100001-200000 Шард 3: user_id 200001-300000
Плюсы:
- Простая маршрутизация
- Легко добавить новый шард
Минусы:
- Неравномерное распределение (hot spots)
- Сложно изменить диапазоны
2. Хэш-шардирование (Hash-based)
Ключ хэшируется и распределяется по шардам:
shard_id = hash(user_id) % N hash(123) = 456789 → 456789 % 3 = 0 → Шард 1 hash(456) = 789012 → 789012 % 3 = 1 → Шард 2
Плюсы:
- Равномерное распределение
- Нет hot spots
Минусы:
- Сложно изменить количество шардов (пересчёт всех ключей)
- Нельзя делать range-запросы между шардами
3. Шардирование по списку (List-based)
Явное указание, какие значения к какому шарду относятся:
Шард 1: Россия, Беларусь, Казахстан Шард 2: США, Канада Шард 3: Европа
Плюсы:
- Гибкое управление
- Легко добавить новый шард
Минусы:
- Ручное управление маппингом
- Неравномерное распределение
4. Гео-шардирование (Geo-based)
Данные распределяются по географическому признаку:
Шард 1 (Москва): пользователи из RU Шард 2 (Франкфурт): пользователи из EU Шард 3 (Нью-Йорк): пользователи из US
Плюсы:
- Данные ближе к пользователям (меньше задержка)
- Соответствие регуляториям (GDPR, 152-ФЗ)
Минусы:
- Сложная маршрутизация
- Глобальные запросы между шардами
Выбор shard-ключа
Хороший shard-ключ
- ✅ Высокая кардинальность: много уникальных значений
- ✅ Равномерное распределение: нет перекоса по шардам
- ✅ Частое использование в WHERE: запросы идут сразу к нужному шарду
- ✅ Стабильность: ключ не меняется со временем
Примеры:
user_id— для пользовательских данныхtenant_id— для SaaS (мультитенантность)country_code— для гео-шардированияcreated_at(по диапазону дат) — для временных рядов
Плохой shard-ключ
- ❌ Низкая кардинальность:
is_active,gender - ❌ Неравномерное распределение:
created_at(все новые записи в один шард) - ❌ Редко используется в запросах: данные придётся искать по всем шардам
Проблемы шардирования
1. Кросс-шардовые запросы
JOIN между шардами невозможен или очень дорог:
-- ❌ Нельзя выполнить напрямую
SELECT u.*, o.*
FROM users u -- Шард 1
JOIN orders o -- Шард 2
ON u.id = o.user_id;
-- ✅ Решение: делать в приложении
$users = $shard1->query("SELECT * FROM users WHERE ...");
$userIds = array_column($users, 'id');
$orders = $shard2->query("SELECT * FROM orders WHERE user_id IN (...)");
2. Транзакции между шардами
ACID транзакции невозможны без 2PC (two-phase commit):
-- ❌ Нельзя атомарно выполнить BEGIN; UPDATE shard1.users SET balance = balance - 100 WHERE id = 1; UPDATE shard2.users SET balance = balance + 100 WHERE id = 2; COMMIT; -- ✅ Решение: eventual consistency, сага-паттерн
3. Ребалансировка
При изменении количества шардов нужно перераспределить данные:
Было: 3 шарда → Стало: 4 шарда Перемещение 25% данных на новый шард
Решение:
- Consistent hashing (минимизирует перемещение)
- Виртуальные шарды (легко перераспределять между физическими узлами)
4. Глобальные данные
Некоторые данные нужны на всех шардах:
Справочники: страны, валюты, настройки Пользователи: некоторые пользователи нужны на всех шардах
Решение:
- Репликация глобальных данных на все шарды
- Отдельный шард для глобальных данных
Шардинг vs Партиционирование
| Параметр | Партиционирование | Шардинг |
|---|---|---|
| Уровень | Внутри одного инстанса БД | Распределение по разным узлам |
| Масштабирование | Вертикальное (больше ресурсов) | Горизонтальное (больше серверов) |
| Сложность | Проще (встроенная функция БД) | Сложнее (приложение управляет) |
| Пример | PostgreSQL table partitioning | MongoDB sharded cluster |
Реализация шардирования
MongoDB (встроенный шардинг)
// Включение шардирования для БД
sh.enableSharding("mydb");
// Шардирование коллекции по ключу
sh.shardCollection("mydb.users", { user_id: "hashed" });
// Добавление шарда
sh.addShard("rs1/10.0.0.1:27017");
PostgreSQL (ручное шардирование)
-- Партиционирование таблицы (внутри одного инстанса)
CREATE TABLE users (
id INT,
name TEXT
) PARTITION BY HASH (id);
CREATE TABLE users_0 PARTITION OF users
FOR VALUES WITH (modulus 3, remainder 0);
CREATE TABLE users_1 PARTITION OF users
FOR VALUES WITH (modulus 3, remainder 1);
CREATE TABLE users_2 PARTITION OF users
FOR VALUES WITH (modulus 3, remainder 2);
Приложение (PHP + Redis для маршрутизации)
class ShardRouter {
private array $shards = [
0 => 'mysql://shard1.example.com/mydb',
1 => 'mysql://shard2.example.com/mydb',
2 => 'mysql://shard3.example.com/mydb',
];
public function getShard(int $userId): PDO {
$shardId = $userId % 3; // hash-based
$dsn = $this->shards[$shardId];
return new PDO($dsn);
}
}
// Использование
$router = new ShardRouter();
$shard = $router->getShard(123); // Шард 1
$user = $shard->query("SELECT * FROM users WHERE id = 123");
Аналоги и связанные термины
- Partitioning — партиционирование (внутри одного инстанса)
- Consistent hashing — хэширование для минимизации перемещения данных
- Distributed SQL — распределённые СУБД (CockroachDB, Spanner)
- Rebalancing — перераспределение данных между шардами
- Replication — репликация (вертикальное масштабирование чтения)
Смотри также (статьи на сайте)
- PostgreSQL для веб-разработчиков — миграция с MySQL, JSON, оптимизация
- Репликация БД — масштабирование чтения
Смотри также (сниппеты)
- PostgreSQL: JSONB и GIN-индекс — индексация для больших таблиц
- Идемпотентный агент с блокировкой — защита от параллельных операций
Смотри также (термины)
- Replication — репликация данных
- ACID — атомарность транзакций
- Partitioning — партиционирование
- Consistent hashing — хэширование для шардирования
Мини-FAQ
Шардинг и партиционирование — одно и то же?
Ответ: Нет. Партиционирование — внутри одного инстанса БД (таблица делится на части). Шардинг — распределение по разным серверам. Шардинг сложнее, но масштабируется лучше.
Когда начинать шардирование?
Ответ: Когда исчерпаны другие методы:
- ✅ Индексы и оптимизация запросов
- ✅ Репликация для чтения
- ✅ Партиционирование таблиц
- ✅ Вертикальное масштабирование (больше CPU/RAM)
- ⚠️ Шардинг — когда всё выше не помогает
Сколько шардов делать?
Ответ: Начните с 3-5 шардов. Лучше иметь запас, чем добавлять шарды слишком часто. Но не делайте 100+ шардов — усложнится управление.
Можно ли откатить шардирование?
Ответ: Технически да, но сложно и дорого. Шардирование — это архитектурное решение, которое принимается надолго. Тщательно тестируйте перед production.