Когда база данных перестаёт справляться с ростом нагрузки, первое желание — увеличить сервер: добавить CPU, память, быстрые диски. Иногда этого достаточно. Но если объём данных и количество запросов продолжают расти, вертикальное масштабирование упирается в технические и финансовые ограничения.
Один из способов перейти к горизонтальному масштабированию — шардинг, то есть разделение данных между несколькими независимыми узлами. Вместо одной огромной базы мы получаем несколько шардов, каждый из которых хранит только часть данных.
Шардинг способен существенно увеличить пропускную способность системы, но добавляет сложности почти на всех уровнях: от проектирования схемы до транзакций, резервного копирования и мониторинга. Поэтому применять его стоит не «на всякий случай», а когда есть конкретная проблема, которую он решает.
Что такое шардинг
Представим таблицу users с сотнями миллионов записей:
users
--------------------------------
id | name | email | created_at
--------------------------------
1 | ...
2 | ...
...
500 000 000 | ...
При обычной архитектуре все записи находятся на одном сервере.
При шардинге данные распределяются между несколькими серверами:
users
|
+----------+----------+
| | |
Shard 1 Shard 2 Shard 3
users users users
Каждый шард содержит только свою часть данных.
Например, пользователей можно распределить по user_id:
Shard 1: id 0–99 999 999
Shard 2: id 100–199 999 999
Shard 3: id 200–299 999 999
На практике диапазоны используются не всегда. Гораздо чаще применяют hash-шардинг, когда шард определяется хешем ключа:
shard = hash(user_id) % N
Таким образом, запрос к конкретному пользователю можно направить сразу на нужный сервер.
Чем шардинг отличается от партиционирования
Эти понятия часто путают.
Партиционирование разделяет таблицу на части внутри одной СУБД, обычно в рамках одного кластера или экземпляра базы.
Например:
orders
├── orders_2024
├── orders_2025
└── orders_2026
Для приложения это всё ещё одна логическая таблица orders.
Шардинг распределяет данные между независимыми узлами:
DB Server 1 → часть orders
DB Server 2 → часть orders
DB Server 3 → часть orders
Граница между ними важна архитектурно: при шардинге приложение или специальный routing-слой должен понимать, где находятся нужные данные.
Когда шардинг действительно нужен
Главный вопрос — не «можно ли зашардировать базу?», а какое ограничение текущей архитектуры мы пытаемся снять.
1. Один сервер перестал масштабироваться
Если CPU, RAM, IOPS или пропускная способность дисковой подсистемы постоянно находятся около предела, вертикальное масштабирование может стать неэффективным.
Например:
CPU: 90–95%
Disk I/O: 95%
RAM: 90%
Если переход на более мощный сервер уже слишком дорог или технически невозможен, несколько базовых узлов могут дать больше ресурсов суммарно.
2. Таблица стала слишком большой
Размер сам по себе ещё не является достаточной причиной для шардинга. Современные СУБД способны работать с очень большими таблицами.
Проблема возникает, когда размер данных начинает ухудшать реальные рабочие характеристики:
- индексы перестают эффективно помещаться в память;
- операции обслуживания становятся слишком долгими;
- резервное копирование занимает неприемлемое время;
- восстановление после сбоя становится слишком медленным;
- запросы конкурируют за одни и те же ресурсы.
3. Нагрузка естественным образом разделяется
Это один из лучших сценариев.
Допустим, сервис работает в нескольких регионах, а пользователи почти всегда обращаются только к данным своего региона:
Europe → Shard EU
USA → Shard US
Asia → Shard ASIA
В таком случае граница данных очевидна, а большая часть запросов остаётся локальной для одного шарда.
4. Нагрузка растёт вместе с количеством клиентов
Для SaaS-платформы естественным ключом шардинга может быть tenant_id.
hash(tenant_id) → shard
Все данные одного клиента можно направлять на один шард. Это позволяет масштабировать инфраструктуру по мере роста числа клиентов.
Когда шардинг — плохая идея
Шардинг не является универсальным лекарством от медленной базы.
Если запрос:
SELECT *
FROM orders
WHERE user_id = 123;
стал медленным из-за отсутствия индекса, шардинг проблему не решит. Сначала нужно исправить индекс.
То же касается:
- неэффективных JOIN;
- неоптимальных запросов;
- чрезмерного количества запросов;
- неправильного кеширования;
- блокировок;
- плохой настройки connection pool;
- недостатка памяти;
- неоптимальной схемы данных.
Хорошее правило: сначала оптимизировать то, что можно оптимизировать внутри одного узла, и только потом переходить к распределению данных.
Как выбрать ключ шардинга
Это самое важное архитектурное решение.
Ключ шардинга должен позволять быстро определить, на каком шарде находятся нужные данные.
Хорошими кандидатами могут быть:
user_id;tenant_id;account_id;region;- другой стабильный идентификатор владельца данных.
Например:
tenant_id = 742
↓
hash(742)
↓
Shard 5
Запрос к данным клиента сразу направляется на Shard 5.
Хороший ключ должен обеспечивать равномерное распределение
Если один шард получает 80% запросов, а остальные простаивают, формально база зашардирована, но масштабирование не работает.
Плохо:
Shard 1: 10%
Shard 2: 10%
Shard 3: 10%
Shard 4: 70% ← hot shard
Лучше:
Shard 1: 24%
Shard 2: 25%
Shard 3: 26%
Shard 4: 25%
При этом важно распределять не только объём данных, но и нагрузку.
Hash-шардинг
Один из распространённых вариантов:
shard = hash(key) % N
Например, четыре шарда:
hash(user_id) % 4
Плюс такого подхода — относительно равномерное распределение.
Минус — изменение количества шардов.
Если было четыре шарда:
hash(id) % 4
а затем стало восемь:
hash(id) % 8
большая часть ключей начнёт указывать на другие узлы. Это может потребовать масштабной перебалансировки.
Поэтому в больших системах часто используют consistent hashing, виртуальные шарды или другие схемы, позволяющие добавлять узлы с меньшим объёмом перемещения данных.
Шардинг по диапазонам
Другой вариант — делить данные по диапазонам:
Shard 1 → id < 100 млн
Shard 2 → 100–200 млн
Shard 3 → 200–300 млн
Преимущество — диапазонные запросы могут быть очень эффективными.
Например:
SELECT *
FROM events
WHERE created_at >= '2026-01-01'
AND created_at < '2026-02-01';
Если данные распределены по времени, запрос можно направить только на соответствующий шард.
Однако есть риск hot shard. Если новые записи постоянно попадают в последний диапазон, именно последний шард получает основную нагрузку.
Что делать с JOIN
Одна из главных сложностей шардинга — запросы между шардами.
Допустим, есть:
users
orders
Если обе таблицы шардированы по user_id, то данные пользователя и его заказы могут находиться на одном узле:
Shard 1
├── users
└── orders
Такой дизайн значительно упрощает запросы.
Если же users распределена по user_id, а orders — по order_id, простой JOIN может превратиться в распределённый запрос между множеством серверов.
Именно поэтому при проектировании важно смотреть не только на отдельные таблицы, но и на паттерны доступа к данным.
Транзакции становятся сложнее
В одной базе транзакция может выглядеть привычно:
BEGIN;
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;
COMMIT;
Если аккаунты 1 и 2 находятся на разных шардах, транзакция становится распределённой.
Тут появляются дополнительные сложности:
- координация нескольких узлов;
- частичные сбои;
- distributed transactions;
- задержки сети;
- необходимость идемпотентности;
- восстановление после ошибок.
Поэтому хороший дизайн часто старается сделать так, чтобы наиболее важные транзакции выполнялись в пределах одного шарда.
Что делать с глобальными данными
Не все таблицы имеет смысл шардировать.
Например, небольшую таблицу:
countries
currencies
settings
можно хранить на каждом шарде целиком.
Это называют подходом с репликацией справочных данных.
Условно:
Shard 1 → users + orders + countries
Shard 2 → users + orders + countries
Shard 3 → users + orders + countries
Если countries занимает несколько мегабайт, дублирование намного проще, чем организация распределённых JOIN.
Где должна находиться логика маршрутизации
Есть несколько архитектурных подходов.
В приложении
Приложение само определяет шард:
request
↓
application
↓
hash(user_id)
↓
Shard 3
Преимущество — максимальный контроль.
Недостаток — логика распределения данных становится частью приложения и должна корректно работать во всех сервисах.
В прокси или middleware
Приложение подключается к единой точке входа:
Application
↓
Shard Router
↙ ↓ ↘
DB1 DB2 DB3
Router определяет, куда отправить запрос.
Это позволяет скрыть часть сложности от приложения, но добавляет отдельный компонент, который также необходимо масштабировать и мониторить.
На уровне самой СУБД
Некоторые современные системы предоставляют собственные механизмы распределения данных и запросов.
Это может значительно упростить эксплуатацию, но выбор конкретного решения зависит от используемой СУБД и характера нагрузки.
Как переносить существующую базу
Шардинг существующей production-базы — гораздо более сложная задача, чем создание шардированной системы с нуля.
Обычно процесс выглядит примерно так:
- определить ключ шардинга;
- проанализировать распределение данных;
- создать новые шарды;
- настроить маршрутизацию;
- перенести данные;
- проверить консистентность;
- перевести чтение;
- перевести запись;
- постепенно вывести старую схему из эксплуатации.
Особое внимание нужно уделить перебалансировке.
Предположим, один шард заполнен на 80%, а остальные — на 30%. Просто добавить новый сервер недостаточно. Нужно переместить часть данных, причём желательно без длительного простоя.
Метрики, которые стоит отслеживать
После внедрения шардинга недостаточно смотреть только на среднюю нагрузку.
Для каждого шарда полезно мониторить:
- CPU;
- RAM;
- disk I/O;
- размер данных;
- размер индексов;
- количество соединений;
- latency запросов;
- QPS;
- ошибки;
- replication lag, если используется репликация;
- долю запросов, уходящих на каждый шард.
Особенно важна неравномерность нагрузки.
Если среднее значение выглядит хорошо:
CPU average = 45%
это ещё ничего не говорит.
Возможно:
Shard 1 → 20%
Shard 2 → 25%
Shard 3 → 30%
Shard 4 → 85%
Среднее значение скрывает проблему.
Практический алгоритм
Перед внедрением шардинга полезно пройти несколько этапов.
Шаг 1. Найти реальное узкое место
Не начинайте с вопроса «как зашардировать таблицу?».
Начните с:
Почему текущая база не справляется?
Шаг 2. Определить паттерны запросов
Нужно понять:
- какие данные обычно читаются вместе;
- какие поля используются для поиска;
- какие JOIN наиболее частые;
- где выполняются транзакции;
- какие запросы создают основную нагрузку.
Шаг 3. Выбрать естественную границу данных
Если почти все запросы относятся к пользователю — хорошим кандидатом будет user_id.
Если к организации — tenant_id.
Если система разделена по регионам — возможно, region.
Шаг 4. Проверить распределение
Нужно убедиться, что ключ не создаёт hot spots.
Шаг 5. Спроектировать операции, которые пересекают шарды
Заранее ответьте:
- как выполнять глобальный поиск;
- как считать агрегаты;
- как делать JOIN;
- как выполнять транзакции между шардами;
- как генерировать уникальные ID;
- как выполнять backup и restore.
Шаг 6. Продумать рост
Система должна быть способна перейти, например:
4 шарда
↓
8 шардов
↓
16 шардов
без полной перестройки архитектуры.
Типичные ошибки
Шардировать слишком рано.
Если проблему решает индекс или кеш, распределённая база только добавит сложности.
Выбрать плохой shard key.
Неправильный ключ способен создать горячие шарды и дорогостоящие межшардовые запросы.
Шардировать каждую таблицу.
Маленькие справочники часто проще реплицировать целиком.
Забыть про перебалансировку.
Нужно заранее понимать, что произойдёт при добавлении нового шарда.
Игнорировать операционные расходы.
Вместо одной базы появляется множество узлов, резервных копий, соединений, метрик и сценариев отказа.
Считать шардинг заменой оптимизации.
Распределённая архитектура не исправляет плохие запросы автоматически.
Итог
Шардинг — это не просто способ «разделить большую таблицу на несколько серверов». Это изменение архитектуры хранения данных.
Он особенно полезен, когда:
- вертикальное масштабирование достигло предела;
- данные естественным образом разделяются по пользователям, клиентам или регионам;
- нагрузку можно распределить между независимыми узлами;
- большая часть запросов может выполняться внутри одного шарда.
Но цена масштабирования — сложность. Появляются распределённые запросы, сложные транзакции, перебалансировка, новые сценарии отказа и дополнительные требования к мониторингу.
Поэтому лучший подход обычно выглядит так:
профилирование → оптимизация → кеширование/репликация → партиционирование → и только затем шардинг, если предыдущих уровней уже недостаточно.
Хорошо спроектированный шардинг позволяет практически линейно наращивать ресурсы системы. Плохо выбранный shard key, наоборот, способен превратить относительно простую базу данных в распределённую систему, которую трудно развивать и ещё труднее отлаживать.



