Нагрузка и масштабирование

Шардирование и кэширование: когда и как применять

шардированиекэшвертикальное масштабирование

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 на узел БД, вертикаль уже упирается.

  1. Вертикаль исчерпана. Одна машина с NVMe даёт потолок ~2000–3000 IOPS под смешанную нагрузку. Прирост — маленький, цена — большая.
  2. Делим на шарды по customer_id. Рассчитываем число шардов: 40 000 / 60 = 667 транзакций в секунду, при ~5 IOPS на транзакцию — 3335 IOPS на весь поток.
  3. Берём 4 шарда по 3–4 узла. Каждый шард ~1000 IOPS плюс запас на пик. Выходит 12–16 серверов вместо одного «золотого».
  4. Добавляем кэш на горячие справочники. 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 для заказов. Ключ должен совпадать с основной выборкой, иначе будут веерные запросы.

Всегда ли кэш ускоряет?

Нет. Кэш окупается на чтении. Если у вас в основном запись, кэш даст меньше, а инвалидация добавит сложности.

Когда точно пора шардировать?

Когда отдельная таблица выросла и её выборочная операция не помещается в рабочий набор одного узла, а вертикаль дорожает. До этого момента можно жить на партиционировании и вертикали.

Нужна помощь с задачей из статьи?

Разберём сценарий и предложим решение на российском стеке.

Обсудить проект

Похожие материалы

Обсудить задачу

Опишите ситуацию — вернёмся с вариантами и ориентиром по бюджету. Обычно отвечаем в течение 1 рабочего дня.