Нагрузка и масштабирование
Шардирование и кэширование: когда и как применять
1. Паттерн и его назначение
База данных — самое частое узкое место в нагрузке. Когда запросов становится много, один сервер БД упрется в один из трёх потолков: производительность дисков (IOPS), объём памяти под рабочий набор или время на партиционирование больших таблиц. Двумя инструментами этот потолок сдвигают — шардирование и кэширование.
- Шардирование — разбиение данных на независимые части, каждая из которых живёт на своём узле. Записи распределяются, а не складываются в одну очередь.
- Кэширование — хранение часто запрашиваемых данных в быстрой памяти, чтобы не ходить к диску каждый раз.
Различают и третий способ — вертикальное масштабирование. Это не всегда «более мощный сервер». Для БД вертикаль означает, что данные остаются в одном узле, а ресурсы просто увеличиваются: ядра, RAM, диск. Простое и понятное, но ограниченное. Когда вертикаль дорожает, переходят к распределению — шардированию.
Паттерн не универсален. Для 40 пользователей с реляционной БД шардирование не нужно — оно добавит сложности и лишних расходов. Включать его стоит, когда есть конкретный признак перегрузки.
2. Как работает (схема)
Шардирование. Данные разбиваются по шард-ключу — колонке, которая определяет, на какой узел упадёт запись. Логика:
*запрос → shard-функция (ключ) → таблица ключ→шард → конкретный узел БД*
Ключ выбирается по характеру обращений. Для пользовательских данных обычно берут user_id или tenant_id, для заказов — customer_id. Важно, чтобы ключ совпадал с тем, как вы чаще ищете данные, иначе большая часть запросов будет веером по всем шардам.
Типы шардирования:
- По диапазону — данные режутся по значению (например, датам). Просто, но диапазоны часто неравномерны: свежие пишутся в один шард.
- По хэшу — ключ хэшируется и раскладывается по шардам. Равномерно по ключу, но сложнее запросы по диапазону.
- По каталогу — отдельная таблица-справочник «ключ → шард». Гибко, но добавляет слой и точку потенциального отказа.
Кэширование. Работает проще: *запрос → кэш? Есть → отдать. Нет → сходить в БД → положить в кэш → отдать*. Механизм и стратегия замены (LRU, LFU, TTL) определяют, что остаётся в памяти. Кэш уместен там, где данные читаются часто и меняются редко.
3. Компромиссы
| Решение | Плюс | Минус | Когда применять |
|---|---|---|---|
| Вертикальное | Просто, без рефакторинга | Потолок по железу, дорого | Небольшой и редкий рост |
| Шардирование | Линейный рост, снимает пик записи | Сложно, риск несогласованности, веерные запросы | Записи упираются в один узел |
| Кэш | Большой прирост чтений | Устаревание (инвалидация), расход памяти | Читают много, пишут мало |
| Отказ от кэша | Просто, согласованность | Медленно на чтении | Нагрузка в пределах возможностей БД |
Ключевой риск шардирования — веерные запросы. Если ищете по полю, которое не шард-ключ, запрос уходит во все шарды сразу. Это съедает выигрыш. Поэтому шард-ключ должен совпадать с большинством запросов.
Риск кэша — устаревание данных. Клиент получит данные, которые уже изменились. В финансах и учётных системах это недопустимо, а в каталогах и аналитике — нормально. Выбор зависит от цены ошибки.
4. Реализация на отечественном стеке
Шардирование и кэш разворачиваются на том же реестровом железе, но с другой структурой:
- Узлы БД. Каждый шард — отдельный сервер из реестра (DEPO, Рикор, Гравитон, Nerpa, Yadro) с NVMe-накопителями, так как шард упирается в IOPS. Смотрите российские серверы.
- Кэш-слой. In-memory решения требуют много памяти и низкой задержки. Для отказоустойчивого кэша делают кластер из нескольких узлов с репликацией. Подбор под высокие IOPS — в системах хранения данных.
- Горизонтальный канал между шардами. Веерные запросы и репликация нагружают сеть. Подбираются коммутаторы с 10/25GbE и L3-возможностями.
- СУБД. Postgres Pro, Arenadata, решения с поддержкой партиционирования и репликации. Всё работает на реестровом железе.
Если план по реестру и закупке для госзаказчика — шардируемая СУБД и узлы должны быть в реестре Минпромторга, иначе конфигурацию не согласовать. Процесс разобран в Как перейти на отечественное оборудование.
5. Расчёт
Сначала ответьте на вопрос: вертикаль или шардирование? Формула простая.
Аварийного вертикального выхлопа = (новая мощность одной машины) /
(текущая пропускная способность под пик)
Если выхлоп < 1,5–2 и его хватает на ближайшие 12 месяцев — остаются на вертикали. Если рост кратный и постоянный — включают распределение.
Ёмкость кэша считается так:
Кэш_GB = (число уникальных «горячих» записей) × (размер одной записи) × (запас 30%)
Пример. 2 млн уникальных ключей в сутки × 4 КБ на запись × 1,3:
2 000 000 × 0,004 МБ × 1,3 ≈ 10,4 ГБ
Кэш на 10–12 ГБ памяти снимет заметную часть чтений с БД и даст прирост в разы по латентности.
Число шардов — где полнее видно ошибку: обычно берут «с запасом», но потом мучаются с перераспределением при росте. Рабочий ориентир — отталкиваться от потолка одного узла (IOPS и размер рабочего набора) и закладывать запас ×2. Уточнить емкость хранилища под профиль удобно через расчёт ёмкости СХД.
Полный пример. Заказчик с плотным профилем записи: биллинг на 40 тыс. записей в минуту, 2000 IOPS на узел БД, вертикаль уже упирается.
- Вертикаль исчерпана. Одна машина с NVMe даёт потолок ~2000–3000 IOPS под смешанную нагрузку. Прирост — маленький, цена — большая.
- Делим на шарды по
customer_id. Рассчитываем число шардов:40 000 / 60 = 667транзакций в секунду, при ~5 IOPS на транзакцию — 3335 IOPS на весь поток. - Берём 4 шарда по 3–4 узла. Каждый шард ~1000 IOPS плюс запас на пик. Выходит 12–16 серверов вместо одного «золотого».
- Добавляем кэш на горячие справочники. 2 млн «горячих» ключей × 4 КБ × 1,3 ≈ 10,4 ГБ памяти снимает большую часть чтений.
Итог: нагрузка на запись разложена горизонтально, чтения — ускорены кэшем. Конкретные узлы под такой профиль — российские серверы из реестра.
5.1 Типовые ошибки
| Ошибка | Причина | Решение |
|---|---|---|
| Шард-ключ не совпадает с запросами | Веерные запросы по всем шардам | Выбирать ключ по главной выборке, обычно user_id |
| Партиционирование вместо шардирования | Путаница понятий | Партиционировать на узле, шардировать между узлами |
| Кэш без инвалидации | Клиент получает устаревшие данные | Задавать TTL и стратегию удаления на изменение |
| Лишний шардированный при малой нагрузке | Сложность вместо пользы | Резать только при реальном потолке узла |
| Кэш «на всё» при записи | Запись не ускорится | Кэшировать чтение, не записи |
5.2 Практический чек-лист перед распределением
Прежде чем включать шардирование, пройдите короткий список:
- убедитесь, что упор действительно в БД, а не в приложении или сети, — иначе распределение не даст эффекта;
- проверьте, помещается ли рабочий набор в память узла после вертикального апгрейда;
- выберите шард-ключ по главной выборке и оцените долю веерных запросов;
- посчитайте кэш под «горячие» чтения и задайте TTL до старта;
- заложите число шардов с запасом ×2, но не «на вырост» в разы: перераспределение дороже, чем кажется;
- продумайте репликацию и резервное копирование каждого шарда заранее.
Шардирование — не первая, а последняя мера. Пока нагрузка держится на вертикали и партиционировании, вводить его рано. Распределение окупается там, где рост кратный и постоянный, а не разовый пик. Кэш и правильный ключ решают быстрее и дешевле — их стоит исчерпать до того, как усложнять топологию.
5.3 Пример: кэш решает, шардирование ждёт
Каталог интернет-магазина: 30 тыс. карточек, чтение преобладает над записью в 20 раз, пик — распродажа. База уже не помещается в память узла, и первая реакция — шардировать. Но смотрим профиль обращений: 90% запросов идут в 5% «горячих» карточек. Значит, выгоднее кэш: 5% от 30 тыс. — это 1,5 тыс. записей × 6 КБ × 1,3 ≈ 12 ГБ памяти, и большая часть чтений уходит из БД без изменений в схеме. Шардирование здесь преждевременно: ключ по category_id даст веерные запросы, а ключ по product_id размажет связанные данные по узлам. Вывод — сначала кэш и вертикальный апгрейд памяти, и только когда запись упрётся в потолок одного узла, режем базу на шарды. Такой порядок экономит месяцы работы и деньги на лишние серверы.
5.4 Метрики: как понять, что распределение сработало
Решение «стало быстрее» надо подтверждать числами, а не ощущениями. Снимайте замеры до и после:
- p95 и p99 латентности чтения и записи — распределение не должно ухудшать хвост;
- hit rate кэша и долю промахов, доходящих до БД;
- IOPS и глубину очереди по каждому шарду — перекос видно сразу;
- долю веерных запросов: если растёт, шард-ключ выбран неудачно;
- равномерность распределения данных по шардам и скорость ребалансировки при добавлении узла.
Если после включения кэша hit rate держится ниже 70–80%, кэш настроен мимо горячих данных. Если один шард принимает больше половины записи — ключ перекошен. Эти метрики дешевле любой переделки: они показывают, что именно помогло, а что добавило сложности впустую.
6. Вопросы и ответы
Шардирование и партиционирование — одно и то же? — Нет. Партиционирование режет таблицы внутри одного узла, шардирование — на физически разные узлы. Партиционирование первым приходит в голову и дешевле.
Какой шард-ключ выбрать? — Тот, по которому чаще ищете: user_id для пользовательских данных, customer_id для заказов. Ключ должен совпадать с основной выборкой, иначе будут веерные запросы.
Всегда ли кэш ускоряет? — Нет. Кэш окупается на чтении. Если у вас в основном запись, кэш даст меньше, а инвалидация добавит сложности.
Когда точно пора шардировать? — Когда отдельная таблица выросла и её выборочная операция не помещается в рабочий набор одного узла, а вертикаль дорожает. До этого момента можно жить на партиционировании и вертикали.
Частые вопросы
Шардирование и партиционирование — одно и то же?
Нет. Партиционирование режет таблицы внутри одного узла, шардирование — на физически разные узлы. Партиционирование первым приходит в голову и дешевле.
Какой шард-ключ выбрать?
Тот, по которому чаще ищете: user_id для пользовательских данных, customer_id для заказов. Ключ должен совпадать с основной выборкой, иначе будут веерные запросы.
Всегда ли кэш ускоряет?
Нет. Кэш окупается на чтении. Если у вас в основном запись, кэш даст меньше, а инвалидация добавит сложности.
Когда точно пора шардировать?
Когда отдельная таблица выросла и её выборочная операция не помещается в рабочий набор одного узла, а вертикаль дорожает. До этого момента можно жить на партиционировании и вертикали.