Когда пора шардировать: три признака и цена решения
Вопрос «пора ли нам шардировать» в растущей команде обычно поднимают не тогда, когда база реально упёрлась в предел, а тогда, когда кто-то прочитал статью про распределённую архитектуру условного крупного стартапа и решил, что пора готовиться заранее. Проблема в том, что цена шардирования — постоянно возросшая сложность кода и эксплуатации — платится с первого дня внедрения, а выгода от него ощущается только тогда, когда предел одного сервера действительно достигнут. В этой статье — три конкретных признака, что откладывать больше нельзя, честный разбор того, что вы покупаете вместе с масштабируемостью, и чек-лист, который стоит пройти, прежде чем открывать задачу «спроектировать шардирование».
Содержание
- Почему «шардировать на всякий случай» — плохая стратегия
- Признак 1: вертикальное масштабирование упёрлось в физический потолок
- Признак 2: обслуживание не укладывается в допустимое время
- Признак 3: нагрузка на запись превышает возможности одного сервера
- Цена решения: что вы покупаете вместе с масштабируемостью
- Чек-лист перед решением
Почему «шардировать на всякий случай» — плохая стратегия
Шардирование часто обсуждают как естественный следующий шаг после вертикального масштабирования — будто это просто более продвинутая версия «взять сервер помощнее». На практике это качественно другое архитектурное решение: вы меняете модель данных, часть гарантий согласованности и то, как выглядит почти каждый нетривиальный запрос в кодовой базе.
Возьмите решение заранее, «про запас», — и вы получаете постоянную сложность немедленно, а нагрузку, ради которой она затевалась, возможно, не получите никогда: бизнес может расти не так быстро, как ожидалось, паттерн данных — измениться, а конкретная задача — решиться дешевле индексом или репликой для чтения. Похожая логика разбирается в статье про антипаттерн масштабирования до поиска узкого места — там же принцип: сначала измерить, где реально упирается система, и только потом усложнять архитектуру под измеренное узкое место, а не под гипотезу о будущем росте.
Здесь тот же принцип применительно конкретно к шардированию: у него есть три практических признака, что решение назрело, и если ни один не проявился — вы почти наверняка платите за сложность раньше времени.
Признак 1: вертикальное масштабирование упёрлось в физический потолок
Первый и самый однозначный признак — вы физически не можете добавить ресурсов на одну машину, а не просто «дороговато стало». Это разные ситуации, и их легко перепутать.
Проверить это стоит прямыми вопросами к текущей конфигурации:
- Вы уже используете старшую доступную на рынке конфигурацию по памяти и CPU для этого класса серверов, и следующий шаг вверх у поставщика попросту не предлагается — не «дорого», а физически нет позиции в прайсе крупнее.
- Рабочий набор (индексы плюс часто читаемые данные) продолжает расти быстрее, чем можно нарастить RAM в одном сервере на разумном горизонте — полгода-год.
- NVMe-диски упираются не в объём, а в пропускную способность и IOPS:
iostat -x 1стабильно показывает%utilоколо 100 на дисках с данными или WAL/binlog именно в часы пиковой нагрузки, а не разово. - CPU steal или постоянная утилизация всех ядер под 90%+ держится не в момент отчётов раз в месяц, а как фон рабочего дня, и профилирование запросов не находит явного «одного плохого запроса», который можно оптимизировать точечно.
Прежде чем фиксировать этот признак, стоит убедиться, что вы действительно упёрлись в железо, а не в конфигурацию: устаревшую статистику планировщика, отсутствующий индекс, неоптимальный shared_buffers/innodb_buffer_pool_size. Диагностика на порядок дешевле смены архитектуры. Если после честного пересчёта окажется, что нужная конфигурация физически существует на рынке и не запредельна по цене — вертикальный запас ещё не исчерпан, и это самый дешёвый путь вперёд. Более подробный разбор именно того, что физически упирается в потолок одного сервера баз данных — память, обслуживание, время восстановления — есть в статье про предел размера базы данных для одного сервера.
Признак засчитывается только тогда, когда следующий шаг вверх по железу не существует или экономически абсурден (кратный рост стоимости ради процентов дополнительной ёмкости), а не когда он просто требует согласования бюджета.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПризнак 2: обслуживание не укладывается в допустимое время
Второй признак реже обсуждают, но он бьёт по бизнесу не менее болезненно: операции обслуживания — бэкап, восстановление, миграция схемы — начинают занимать время, которое компания не может себе позволить.
Практический способ проверить — не гадать, а замерить фактическое время на реальном объёме:
# логический бэкап PostgreSQL — засекаем реальное время
time pg_dump -Fc -j 4 mydb -f backup.dump
# и обязательно время восстановления, а не только бэкапа
time pg_restore -j 4 -d mydb_test backup.dump
Если пять лет назад ночной бэкап укладывался в 40 минут, а сейчас на том же классе сервера идёт четыре часа — время растёт вместе с объёмом данных, и рано или поздно упрётся в длину окна между пиками нагрузки, когда бэкап можно безопасно снимать.
Второй практический пример — миграции схемы. ALTER TABLE на PostgreSQL, добавляющий колонку без значения по умолчанию, обычно быстрый на любом объёме, но многие частые операции — добавление колонки со значением по умолчанию в старых версиях, построение индекса без CONCURRENTLY, VACUUM FULL — берут ACCESS EXCLUSIVE блокировку на всё время выполнения. На таблице в 500 ГБ операция, занимающая на тестовой базе в 5 ГБ пару секунд, может растянуться на десятки минут блокировки продакшена. Разбор того, как один такой случай ломает продакшн, есть в статье про миграцию, добавившую колонку и заблокировавшую таблицу.
Признак стоит фиксировать, если верно хотя бы одно:
- регулярный полный бэкап или тестовое восстановление превышают допустимое по SLA время простоя, и это не решается сменой формата бэкапа (переход с логического на физический, инкрементальные снапшоты);
- рутинные миграции схемы на крупных таблицах требуют отдельного окна обслуживания и координации с бизнесом, а не проходят прозрачно в фоне;
- время операций растёт линейно или быстрее вместе с объёмом данных, и экстраполяция на полгода-год вперёд показывает выход за пределы допустимого окна.
Здесь тоже есть более дешёвые промежуточные шаги — партиционирование по дате, вынос архивных данных в отдельное холодное хранилище, CREATE INDEX CONCURRENTLY, инкрементальные физические бэкапы вместо логических дампов. Если после них время обслуживания снова укладывается в норму — признак не подтверждён, шардирование пока не нужно.
Признак 3: нагрузка на запись превышает возможности одного сервера
Третий признак — самый прямой аргумент именно за шардирование, потому что читающую нагрузку почти всегда можно разгрузить репликами и кэшем, а вот запись у большинства СУБД по умолчанию идёт через один master с одним WAL (PostgreSQL) или binlog (MySQL). Реплики читают асинхронно или синхронно, но пишет всегда один сервер — и это узкое место репликация не снимает.
Признаки того, что дело именно в объёме записи, а не в неоптимальных запросах:
- диск под WAL/binlog стабильно показывает высокую утилизацию по записи (
iostat -x 1, колонка%utilиw/s) именно в момент пиковой записи, а не чтения; - после оптимизации batch-вставок (группировка insert'ов, отключение лишних индексов на время массовой загрузки,
UNLOGGED-таблицы там, где это допустимо) задержка записи всё равно растёт вместе с нагрузкой; - реплики стабильно отстают от master именно из-за объёма изменений, которые нужно применить, а не из-за одной медленной транзакции;
- очередь на запись (например,
pg_stat_activityс большим числом активныхINSERT/UPDATE, ожидающих блокировки или ресурсов ввода-вывода) не рассасывается даже вне пиковых часов.
Важная оговорка из самого признака: «даже с оптимизацией». Прежде чем засчитывать этот пункт, стоит закрыть более дешёвые пути — batching вместо построчных вставок, асинхронная запись там, где бизнес это допускает, вынесение тяжёлых агрегатов из пути записи в фоновые джобы, партиционирование горячей таблицы по диапазону дат, чтобы индексы под неё оставались компактными. Если после этого нагрузка на запись всё ещё выше, чем тянет один сервер на разумной конфигурации — признак подтверждён, и это единственный из трёх, который партиционирование и вертикальный апгрейд снимают лишь временно, потому что суммарная пропускная способность записи в конечном счёте ограничена одной машиной.
Цена решения: что вы покупаете вместе с масштабируемостью
Если хотя бы один признак подтверждён — это ещё не значит «шардируйте немедленно». Прежде чем решать, стоит явно проговорить, чем вы за это платите, и оценить, готова ли команда нести эту цену постоянно, а не разово.
| Что было на одном сервере | Что становится после шардирования |
|---|---|
JOIN между связанными таблицами | Работает только если таблицы шардированы по одному ключу (co-location); иначе — отдельные запросы к нескольким серверам и склейка в коде приложения |
| Одна ACID-транзакция на несколько таблиц | Транзакция, затрагивающая больше одного шарда, требует двухфазного коммита или паттерна saga — оба медленнее и сложнее локальной транзакции |
COUNT/SUM по всей таблице | Scatter-gather запрос на все шарды параллельно с объединением результата на координаторе — работает, но добавляет задержку и нагружает сразу все серверы |
| Один бэкап, одно окно обслуживания | Бэкап и ALTER TABLE нужно проводить на каждом шарде — либо синхронно везде, либо с продуманной стратегией поэтапного раскатывания |
| Один сервер в мониторинге | N серверов с независимыми метриками, независимыми инцидентами, независимым патчингом и апгрейдом версий СУБД |
| Автоинкремент ID | ID, генерируемые независимо на разных шардах, начинают конфликтовать — нужен глобальный генератор (например, по схеме Snowflake) или UUID |
Отдельная, менее очевидная статья расходов — ресеардинг. Когда один из шардов перерастает остальные (а это происходит почти всегда, потому что данные редко распределяются идеально равномерно навсегда), перераспределение данных между шардами — это отдельный проект с рисками простоя и потери консистентности, а не рутинная операция. Более подробный разбор того, как устроено шардирование изнутри — выбор ключа, архитектурные схемы маршрутизации запросов, конкретные инструменты вроде Citus и Vitess — есть в статье «Шардирование базы данных: основы»; здесь важно зафиксировать главное: эта сложность не разовая инвестиция, а постоянная операционная нагрузка на команду, которая остаётся навсегда, даже если исходная проблема с записью давно решена ростом бизнеса в другую сторону.
Честно отвечать стоит и на менее техничный вопрос: если сейчас на одного инженера, который понимает базу данных целиком, приходится один сервер, то после перехода к пяти-десяти шардам сложность диагностики инцидента (на каком шарде проблема, влияет ли она на остальные, как быстро откатить конкретный шард) растёт не линейно, а быстрее — потому что добавляется ещё и распределённая природа самих сбоев.
Чек-лист перед решением
Прежде чем открывать задачу на проектирование шардирования, стоит пройтись по списку и честно ответить на каждый пункт:
- Хотя бы один из трёх признаков подтверждён измерением, а не ощущением «скоро будет тесно». Если признаков нет — вопрос закрыт, возвращайтесь к нему через полгода-год вместе с пересмотром метрик роста.
- Вертикальное масштабирование действительно исчерпано, а не просто не согласован бюджет на апгрейд. Разница между этими двумя ситуациями стоит команде очень разных денег.
- Партиционирование, архивация холодных данных и оптимизация индексов уже применены и не решили проблему, а не просто ещё не пробовались.
- Чтение уже разгружено репликами и кэшем, и узкое место именно в записи или в объёме, который физически не помещается на один сервер — а не в том, что реплики не настроены.
- Есть естественный ключ шардирования, по которому данные делятся на независимые группы почти без
JOIN-ов между шардами (клиент, тенант, регион). Без такого ключа шардирование гарантированно превращается в постоянную борьбу с межшардовыми запросами вместо решения проблемы. - Команда готова нести операционную сложность постоянно, а не только спроектировать систему один раз: мониторинг нескольких серверов, ресеардинг при неравномерном росте, миграции схемы на каждом шарде, инциденты с распределённой природой.
- Посчитана экономика: сравнили ли вы стоимость перехода на топовую конфигурацию выделенного сервера (если она ещё доступна) со стоимостью проектирования, внедрения и постоянной эксплуатации распределённой схемы — включая время инженеров, а не только счёт за железо.
Если хотя бы два-три пункта не выполняются — вероятно, вы ещё не в точке, где шардирование окупается, и более простые шаги дадут больше времени и меньше риска за меньшие деньги.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли зашардировать «частично» — только самую нагруженную таблицу?
Да, и это распространённый практический компромисс: горячая таблица с большим объёмом записи выносится в распределённую схему (например, через Citus поверх PostgreSQL), а остальная база остаётся на одном сервере. Это снижает сложность по сравнению с полным шардированием всей базы, но требует того же честного анализа ключа и того же понимания, что JOIN этой таблицы с остальными усложнится.
Что если признак 3 (нагрузка на запись) уже наступил, а признаков 1 и 2 ещё нет?
Такое бывает — например, при пиковой сезонной нагрузке на запись при в целом небольшом объёме данных. В этом случае стоит сначала проверить более узкие решения именно под запись: очередь перед базой (буферизация всплесков), батчинг вставок, вынесение части записи в отдельную специализированную систему (time-series базу для метрик, например). Шардирование всей схемы ради одной проблемной точки записи — избыточно.
Сколько времени обычно занимает переход от решения до работающего шардирования?
Однозначного ответа нет — сильно зависит от объёма данных, наличия подходящего ключа и того, встроено ли шардирование в СУБД или требует прокси-слоя и переписывания запросов в приложении. Закладывать стоит не недели, а месяцы, включая тестирование ресеардинга и отработку инцидентов на копии до переключения прода.
Стоит ли готовить код «под будущее шардирование» заранее, даже если признаков ещё нет?
Разумный минимум — не делать вещей, которые точно помешают потом: избегать глобальных автоинкрементов там, где легко заменить на UUID без вреда, не завязываться жёстко на кросс-табличные транзакции там, где без них можно обойтись. Но полноценно проектировать схему шардирования и ключ заранее не стоит — требования к паттерну запросов почти всегда меняются к моменту, когда решение реально понадобится.
Может ли шардирование понадобиться раньше, чем исчерпан вертикальный рост, если данные по закону должны физически лежать в разных странах?
Да, это отдельная причина, не связанная с производительностью — требования резидентности данных иногда вынуждают разносить данные по серверам в разных юрисдикциях независимо от нагрузки. Это не отменяет всей операционной цены шардирования, описанной выше, но признаком «упёрлись в предел» в привычном смысле не является — здесь решение принимается не по нагрузке, а по требованиям комплаенса.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →