Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Capacity planning: оценка нагрузки сервиса

Capacity planning сервиса: от оценки RPS до бюджета ресурсов

Как считать ёмкость сервиса: RPS и latency-математика, закон Литтла, запасы по метрикам, нагрузочные тесты и планирование роста без оверпровижинга.

Прямой ответ

Capacity planning — ответ на три вопроса деньгами: сколько ресурсов нужно сервису под целевую нагрузку, когда их станет не хватать и что будет при пике/отказе части; рабочий метод (без «возьмём сервер побольше»): (1) целевая нагрузка в RPS (не «много пользователей» — пользователи конвертируются: 100k DAU × 20 запросов / 86400 с ≈ 23 RPS средний — но планируем по пику (×3–5 к среднему — утренний пик/распродажа — коэффициент из исторических метрик, не из фантазии); (2) ёмкость одного инстанса — измеренная (нагрузочный тест: поднимаем RPS до нарушения SLO (p95 > X мс или ошибки > 1% — SLO-рамка) — «один pod держит 340 RPS при p95 180 мс» — Locust-практика — цифра с графика, не из головы; закон Литтла связывает: конкурентность = RPS × latency (340 RPS × 0.18 с ≈ 61 одновременный запрос — воркеры/пул соединений должны вмещать — «2 воркера Gunicorn» при 61 конкурентном — очередь — процессы); (3) реплики = пиковый RPS / ёмкость инстанса × запас — запас не произвольный: N+1 (один инстанс умирает — остальные держат пик — минимум для прод-сервиса; N+2 для критичных) + headroom 30–50% (всплески выше исторических, деградация зависимостей — «пик совпал с медленной БД» — каскадные отказы — без запаса — лавина: ретраи клиентов на перегруженный сервис — положительная обратная связь — Google SRE прямо про это); (4) узкое место почти никогда не в веб-слое (цепочка: БД-пул соединений (200 коннектов / 10 реплик приложения × 20 = упрётесь раньше CPU — pgbouncer), IO/диск, лимиты внешних API (rate limits), очередь/воркеры — ёмкость = минимум по цепочке — «веб держит 10k RPS, БД — 300» — ёмкость сервиса 300); (5) тренд и горизонт (текущее потребление × рост — «при +20%/мес лимит БД упрём через 4 месяца» — график потребления в Grafana + линейная экстраполяция — планирование — календарь, а не паника); (6) пересмотр — регулярный (квартал/релиз-цикл — ёмкость дрейфует: код меняется, данные растут — «замерили год назад» — устарело); k8s-специфика: requests/limits из замеров (не на глаз — ресурсы pod'ов — HPA по измеренной утилизации, min-replicas из N+1 — автоскейлер не заменяет планирование (scale-up занимает минуты — пик быстрее — запас обязателен — «HPA спасёт» — миф для резких пиков).

Математика и замеры: от p95 к деньгам

Связка величин (пример с цифрами): SLO — p95 ≤ 200 мс, ошибки ≤ 0.1%; нагрузочная кривая (Locust, ступени 50→500 RPS по 5 минут — методика): 50 RPS — p95 40 мс, 200 — 90 мс, 340 — 180 мс, 400 — 310 мс (SLO нарушен), 450 — ошибки 2% (насыщение — «точка излома» 340–400); ёмкость инстанса = 340 RPS (по SLO, не по «ещё работает»); пик-прогноз: исторический максимум 900 RPS, маркетинговая акция ×1.5 → цель 1400 RPS; реплики = 1400/340 = 4.1 → 5 + N+1 = 6 реплик (одна умрёт — 5×340=1700 ≥ 1400 — выдерживаем); ресурсы: 6 pod'ов × (2 CPU request / 1 ГБ — из профилей под нагрузкой — не «стандартные 512 Мб») + узкое место БД: 6×20 соединений = 120 — в pgbouncer-пул 150 — проходит, но «+2 реплики — упрёмся» — план расширения пула записан; латентность-бюджет (вложенные вызовы складываются: API 200 мс = 20 сеть + 30 авторизация + 100 БД + 50 логика — рост любого съедает бюджет — трейсинг показывает распределение — планирование ёмкости без понимания «куда уходит время» — гадание); стоимость (ёмкость → деньги: 6 pod'ов × $30 + БД $200 = $380/мес — «ёмкость в 2 раза дороже» — иногда правильный ответ — кэш (Redis — 30% трафика в кэш — ёмкость БД-слоя ×1.4 без железа — оптимизация против закупки — техдолг/инвестиции); что-если-анализ (таблица сценариев: пик ×2 — держим (N+1 + кэш), БД лежит 5 минут — деградируем (кэш + circuit breaker), регион недоступен — не держим (мультирегион не строили — осознанное решение с записанным риском — ADR — capacity planning включает «что мы НЕ переживём»).

Практический сценарий: подготовка к ×5 распродаже за месяц

Контекст: e-commerce API, обычный пик 300 RPS, анонсирована распродажа — прогноз ×5 (1500 RPS), месяц срока; текущее: 2 реплики приложения (держат ~600 RPS суммарно), Postgres на 4 vCPU, Redis-кэш «есть, но покрытие 40%». План по слоям: (1) замер текущего состояния — ступенчатый Locust-прогон staging'а (профиль трафика из прод-логов — топ-10 эндпоинтов с весами — структурированные логи — «синтетика по одному эндпоинту» — врёт); излом: 580 RPS — p95 400 мс; узкое место — БД (CPU 90% при 450 RPS — планы — два тяжёлых запроса каталога); (2) кэш-покрытие 40% → 75% (каталожные запросы — TTL 60 с + инвалидация при изменении — CPU БД при том же трафике −35% — замер повторный — ёмкость 580 → 850 RPS без железа); (3) индексы/запросы (оптимизация — два seq scan → index — ещё −15% CPU БД — ёмкость → 980 RPS); (4) вертикаль БД 4 → 8 vCPU (замеренный запас исчерпан оптимизациями — дальше — железо — ёмкость → ~1700 RPS по БД — реплика на чтение каталога — готовность, не включаем — план B); (5) веб-слой: ёмкость реплики 290 RPS (замер), цель 1500 → 1500/290 = 5.2 → 6 + N+1 = 7 реплик (HPA min=4/max=12 — scale по CPU 60% — пик быстрее запаса — HPA-практика); пул соединений: 7×20=140 — pgbouncer default_pool 200 — ок; (6) репетиция — нагрузочный прогон 1500 RPS на проде (ночное окно, feature-flag «синтетические заказы» помечает тест-данные — идемпотентность — очистка после; chaos-элемент — убили одну реплику под нагрузкой — HPA поднял замену за 90 с — p95 деграднул на 40 мс — приняли); (7) день распродажи — дежурство с runbook (пороги: p95>300 мс — включаем план B (read-реплика), ошибки>1% — деградация (баннеры вместо персонализации), очередь заказов — lag-метрика — Celery-мониторинг); итог дня: пик 1620 RPS (выше прогноза на 8% — запас сработал), p95 max 210 мс, ноль инцидентов; постморте-цифры: ёмкость выросла ×2.9 (580→1700), счёт вырос ×1.6 (БД+реплики на месяц — после — вертикаль обратно, реплики в обычный пул — эластичность — стратегии деплоя — capacity planning — не «купить навсегда», а «арендовать под событие»); вывод: 80% ёмкости дали оптимизации и кэш (замеры→узкое место→фикс), 20% — железо — типичное распределение, когда начинают с замеров, а не с закупки.

FAQ

Сколько запаса (headroom) закладывать — 30%, 50%, 100%? Отправные точки: N+1 по репликам — жёсткий минимум (отказ одного инстанса не нарушает SLO — иначе «деплой = деградация»); headroom к пику: 30–50% для стабильных нагрузок с известным профилем (веб/API), 100%+ для событийных пиков (распродажи/релизы/вирусный контент — «исторический пик» — плохой ориентир для нового продукта — SRE-практика); факторы, съедающие запас: каскадные ретраи (клиенты+очереди удваивают эффективную нагрузку при деградации — circuit breaker обязателен), фоновые задачи (ночные джобы в том же пуле — планируем раздельно), рост данных (запрос, державший 50 мс на 1M строк — 200 мс на 10M — ёмкость БД падает без роста трафика); практика: алерт на 70% утилизации ёмкости (не 95% — реагирование занимает дни — алертинг — «сюрприз» при 100%); «максимальная эффективность = нулевой запас» — урок каскадных отказов — запас — плата за устойчивость.

Чем измерять ёмкость: staging или прод? Staging: безопасно/дёшево, но врёт (железо другое, данные — 1% объёма (планы запросов на малых данных — другие), конфигурация дрейфует — «staging держит 2x прода» — типичная ложь); прод-нагрузка (shadow/synthetic): точна, но требует дисциплины (помеченные тест-данные — вебхук-идемпотентность — очистка; ночные окна; feature-flag'и — флаги); рабочий гибрид: staging — для сравнений «стало против было» (A/B оптимизаций — относительные цифры честны), прод — для абсолютных замеров (ночной прогон + canary-нагрузка — стратегии); обязательное условие обоих — реалистичный профиль (топ эндпоинтов с весами из прод-логов, данные объёма прода или партиции — «один эндпоинт 1000 RPS» — не ёмкость сервиса); вывод: цифры планирования — из прода, эксперименты — на staging'е.

Как планировать ёмкость БД, если приложение скейлится легко? БД — классический неэластичный слой (stateful — репликация/фейловер — минуты-часы, не секунды — failover-практика); метод: ёмкость БД в «тяжести запросов» (не RPS приложения — RPS × запросов-на-запрос × стоимость каждого — профилирование/pg_stat_statements покажут распределение); точки отказа по порядку: CPU (тяжёлые запросы), соединения (пул — pgbouncer), IO/диск (объём+WAL — репликация/PITR), блокировки (дедлоки под нагрузкой — отдельный класс); рычаги до железа: кэш (Redis — самый дешёвый RPS-делитель), оптимизация запросов/индексов (планы), read-реплики (чтение каталога/отчётов — реплики), партиционирование (паттерн), архивация холодных данных; «купим БД побольше» — последний рычаг, не первый (замеры сценария: 65% ёмкости дала оптимизация).

Нужен ли capacity planning в эпоху автоскейлеров и облаков? Да, но смещается фокус: автоскейлер решает «сколько реплик прямо сейчас», планирование решает «каковы границы» (max реплик упирается в БД/IP-адреса/квоты облака/бюджет — HPA упрётся в limit — дальше очередь и деградация — предел надо знать и мониторить — HPA-механика); бюджеты (cloud-биллинг — эластичность не отменяет финансовый лимит — «×5 пик стоил ×5 счёта» — бизнес должен решить заранее — стоимость-вопрос); скорости (scale-up — минуты — резкий пик быстрее реакции — стартовый запас = планирование); зависимости (внешние API rate-limits — ваша эластичность их не волнует — лимиты); итог: автоскейлинг — исполнитель, planning — стратег (границы/бюджеты/планы B — документ на квартал — сценарий выше — распродажа без плана = импровизация ночью).

Источники: Google SRE Book (cascading failures), Prometheus histograms, Little's law — ссылки в шапке.