Триггер паники: ваше приложение упало при росте в 10 раз
Добро пожаловать в ад
Вы — Senior Backend Engineer в стартапе электронной коммерции. Сегодня среда, 14:00. Вы пьете кофе и планируете спокойный день.
Вдруг:
14:03 — Slack: @channel URGENT: сайт тормозит
14:04 — PagerDuty: CRITICAL: API P99 latency > 5000ms
14:05 — CEO звонит: "КЛИЕНТЫ НЕ МОГУТ ОФОРМИТЬ ЗАКАЗ!"
14:06 — Grafana: RPS вырос с 200 до 2000 за 10 минутЧто произошло? Ваш продукт попал в топ ProductHunt. Органический трафик x10. Маркетинг счастлив. Инфраструктура — нет.
Бюджет: $10,000/месяц на инфраструктуру. У вас есть кредитка для экстренных случаев.
Время до полного краха: 30 минут, если ничего не делать.
Для кого этот урок: Для всех, кто думает, что highload — это теория. Это симуляция реального кризиса. Вы будете принимать решения под давлением, считать деньги и последствия. Ошибки будут стоить виртуальных денег и реального понимания.
Текущее состояние системы (14:06)
Архитектура "как есть"
┌─────────────┐
│ Nginx │ ← 1 instance (t3.small, 2 vCPU, 2GB RAM)
│ (LB + SSL) │ CPU: 85%, connections: 950/1024
└──────┬──────┘
│
├─────────────────┬─────────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ API-1 │ │ API-2 │ │ API-3 │
│t3.medium │t3.medium │t3.medium
│4vCPU,8GB│ │4vCPU,8GB│ │4vCPU,8GB│
│CPU: 95% │ │CPU: 92% │ │CPU: 89% │
└────┬───┘ └────┬───┘ └────┬───┘
│ │ │
└─────────────────┴─────────────────┘
▼
┌─────────────────┐
│ PostgreSQL │
│ (db.t3.large) │
│ 4vCPU, 16GB │
│ CPU: 98% │
│ Connections: │
│ 95/100 │
└─────────────────┘Метрики критичности (live)
| Метрика | Норма | Сейчас | Статус |
|---|---|---|---|
| RPS | 200 | 2,000 | 🔴 CRITICAL |
| API P50 latency | 50ms | 800ms | 🔴 CRITICAL |
| API P99 latency | 200ms | 5,200ms | 🔴 CRITICAL |
| Error rate | 0.1% | 12% | 🔴 CRITICAL |
| DB CPU | 30% | 98% | 🔴 CRITICAL |
| DB Connections | 20/100 | 95/100 | 🟡 WARNING |
| Nginx CPU | 15% | 85% | 🟡 WARNING |
| Estimated revenue loss | $0 | $450/min | 💰 BLEEDING |
Топ-5 медленных запросов (за последние 5 минут)
-- #1: 3,500ms avg (было 45ms)
SELECT * FROM orders
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT 20;
-- Выполнено: 15,000 раз за 5 минут
-- Индекс: ОТСУТСТВУЕТ на (user_id, created_at)
-- #2: 2,800ms avg (было 30ms)
SELECT p.*, r.avg_rating
FROM products p
LEFT JOIN (
SELECT product_id, AVG(rating) as avg_rating
FROM reviews GROUP BY product_id
) r ON p.id = r.product_id
WHERE p.category = $1;
-- Выполнено: 22,000 раз за 5 минут
-- Проблема: N+1 в агрегации, нет кэша
-- #3: 1,900ms avg (было 15ms)
SELECT * FROM users WHERE email = $1;
-- Выполнено: 35,000 раз за 5 минут
-- Индекс: ЕСТЬ, но табличка раздулась (10M строк, не VACUUM)
-- #4: 1,200ms avg (было 80ms)
INSERT INTO analytics_events (user_id, event_type, payload)
VALUES ($1, $2, $3);
-- Выполнено: 18,000 раз за 5 минут
-- Проблема: синхронная вставка в аналитику
-- #5: 900ms avg (было 25ms)
UPDATE sessions SET last_activity = NOW() WHERE session_id = $1;
-- Выполнено: 45,000 раз за 5 минут
-- Проблема: обновление в БД на каждый запросВаши действия: Что делать ПРЯМО СЕЙЧАС?
У вас есть 30 минут до того, как система полностью ляжет.
Каждое решение имеет:
- Стоимость (деньги)
- Время реализации (минуты)
- Эффект (снижение нагрузки, latency)
- Риски (что может пойти не так)
Доступные действия
ВАЖНО: Вы не можете сделать всё сразу. Выбирайте приоритеты. Каждое действие отнимает время и деньги. Неправильный выбор может усугубить ситуацию.
Действие A: Вертикальное масштабирование БД
Что: Апгрейд PostgreSQL с db.t3.large (4vCPU, 16GB) на db.r6g.2xlarge (8vCPU, 64GB)
Стоимость:
- Было: $140/месяц
- Станет: $460/месяц
- Дельта: +$320/месяц
Время: 15 минут (downtime 5 минут во время переключения)
Эффект:
- ✅ CPU БД: 98% → ~40%
- ✅ Latency топ-5 запросов: -30%
- ✅ Connections headroom: больше
- ⚠️ Но запросы всё равно медленные (индексов нет!)
Риски:
- 🔴 5 минут downtime = $2,250 потерь
- 🟡 Если проблема не в CPU, деньги потрачены зря
Действие B: Добавить индексы в БД
Что: Создать индексы на orders(user_id, created_at) и users(email)
Стоимость:
- $0 (только время инженера)
Время: 10 минут (создание индексов на 10M строк)
Эффект:
- ✅ Запросы #1 и #3: 3,500ms → 50ms, 1,900ms → 15ms
- ✅ DB CPU: 98% → ~60%
- ✅ P99 latency: 5,200ms → ~1,800ms
Риски:
- 🟡 Создание индекса заблокирует таблицу на 10 минут (можно
CONCURRENTLY, но дольше) - 🟡 Если БД уже перегружена, индекс может не создаться
Действие C: Горизонтальное масштабирование API
Что: Добавить 3 дополнительных инстанса API (итого 6 вместо 3)
Стоимость:
- Было: 3 × $70/месяц = $210/месяц
- Станет: 6 × $70/месяц = $420/месяц
- Дельта: +$210/месяц
Время: 8 минут (деплой через auto-scaling group)
Эффект:
- ✅ API CPU: 95% → ~50%
- ✅ RPS per instance: снижается в 2 раза
- ⚠️ Но БД всё равно bottleneck (CPU 98%)
Риски:
- 🔴 Если БД не выдержит, API будет простаивать
- 🟡 Увеличение connections к БД (может упереться в лимит 100)
Действие D: Добавить Redis для кэша
Что: Поднять Redis кластер (3 ноды) и кэшировать топ-запросы
Стоимость:
- Redis: $180/месяц (cache.r6g.large × 3)
- Дельта: +$180/месяц
Время: 20 минут (провижн + код для кэширования топ-запросов)
Эффект:
- ✅ Запросы #2 (продукты): 2,800ms → 5ms (кэш на 5 минут)
- ✅ DB нагрузка: -40%
- ✅ P99 latency: 5,200ms → ~2,000ms
Риски:
- 🟡 20 минут — долго, система может не дождаться
- 🟡 Кэш может быть stale (пользователи видят старые данные)
Действие E: Async обработка аналитики
Что: Вынести INSERT INTO analytics_events в очередь (Redis + worker)
Стоимость:
- $0 (используем существующие серверы)
Время: 15 минут (код + деплой)
Эффект:
- ✅ Запрос #4: 1,200ms → 10ms (async)
- ✅ DB CPU: -15%
- ✅ P99 latency: -20%
Риски:
- 🟡 Аналитика придет с задержкой (eventual consistency)
- 🟡 Нужна очередь (можно использовать Redis)
Действие F: Перенести сессии в Redis
Что: Хранить сессии в Redis вместо БД
Стоимость:
- $0 (используем Redis из действия D, если он уже есть)
- Если Redis нет, нужно сначала создать → см. действие D
Время: 10 минут (код + деплой)
Эффект:
- ✅ Запрос #5: 900ms → 2ms
- ✅ DB CPU: -20%
- ✅ P99 latency: -15%
Риски:
- 🔴 Если Redis нет, нужно сначала создать (время × 2)
Действие G: Rate Limiting на Nginx
Что: Ограничить RPS на уровне Nginx (например, 500 RPS)
Стоимость:
- $0
Время: 5 минут (конфиг + reload)
Эффект:
- ✅ Защита от перегрузки
- ✅ Система стабилизируется
- ⚠️ Но часть пользователей получит 429 (Too Many Requests)
Риски:
- 🔴 Бизнес будет недоволен (отказ клиентам)
- 🟡 Нужно правильно выбрать лимит
Действие H: Включить CDN для статики
Что: Включить CloudFront/Fastly для статики (images, CSS, JS)
Стоимость:
- $50/месяц (при текущем трафике)
- Дельта: +$50/месяц
Время: 10 минут (конфиг)
Эффект:
- ✅ Nginx CPU: 85% → ~40%
- ✅ Connections: освобождаются
- ⚠️ Не влияет на API latency
Риски:
- 🟡 Минимальный эффект на основную проблему (БД)
Действие I: Connection Pooling (pgbouncer)
Что: Установить pgbouncer перед БД
Стоимость:
- $0 (поднимаем на существующих серверах)
Время: 12 минут
Эффект:
- ✅ DB connections: более эффективное использование
- ✅ Защита от connection exhaustion
- ⚠️ Не влияет на медленные запросы
Риски:
- 🟡 Требует настройки pool size
Действие J: Убить аналитику (временно)
Что: Отключить запись аналитики (analytics_events)
Стоимость:
- $0
Время: 2 минуты (feature flag)
Эффект:
- ✅ DB CPU: -15%
- ✅ Latency: -10%
Риски:
- 🔴 Потеря данных аналитики (навсегда)
- 🟡 Маркетинг будет недоволен
Ваш выбор: Составьте план спасения
У вас есть $1,000 экстренного бюджета и 30 минут.
Задание: Выберите действия и обоснуйте
Заполните таблицу:
| Порядок | Действие | Время | Стоимость | Обоснование |
|---|---|---|---|---|
| 1 | ? | ? мин | $? | Почему первым? |
| 2 | ? | ? мин | $? | Почему вторым? |
| 3 | ? | ? мин | $? | Почему третьим? |
| Итого | ? мин | $? | Укладываетесь ли в 30 минут и $1,000? |
Вопросы для самопроверки
-
Какая главная проблема? CPU БД, медленные запросы, отсутствие кэша, или всё сразу?
-
Что даст максимальный эффект быстрее всего?
- Индексы (10 мин, $0, -70% latency)
- Масштабирование БД (15 мин, $320/мес, -60% CPU)
- Кэш (20 мин, $180/мес, -40% нагрузка)
-
Можете ли вы позволить себе downtime?
- 5 минут downtime = $2,250 потерь
- Vs. медленная работа = $450/мин потерь
-
Какие действия дадут долгосрочный эффект?
- Индексы — навсегда
- Масштабирование — временно (пока трафик не вырастет еще)
- Кэш — пока данные актуальны
Моё решение: Как я бы спасал систему
Важно: Это один из вариантов. Правильных ответов может быть несколько. Главное — обоснование.
План действий (30 минут)
| Порядок | Действие | Время | Стоимость | Накопленное время |
|---|---|---|---|---|
| 1 | G: Rate Limiting | 5 мин | $0 | 5 мин |
| 2 | B: Индексы | 10 мин | $0 | 15 мин |
| 3 | J: Убить аналитику | 2 мин | $0 | 17 мин |
| 4 | C: +3 API | 8 мин | $210/мес | 25 мин |
| 5 | I: pgbouncer | 5 мин | $0 | 30 мин |
| Итого | 30 мин | $210/мес | ✅ |
Обоснование каждого шага
1️⃣ Rate Limiting (5 минут, $0)
Зачем первым?
- Немедленная защита от полного краха
- Система перестанет принимать больше, чем может обработать
- Лимит: 800 RPS (текущая capacity × 1.5)
- Да, часть пользователей получит 429, но система выживет
Эффект:
- RPS: 2,000 → 800
- DB CPU: 98% → ~60%
- API CPU: 95% → ~50%
- Error rate: 12% → 2% (контролируемый отказ)
Минусы:
- ~1,200 запросов/сек отклоняются
- Потери: $450/мин → $300/мин (улучшение!)
2️⃣ Индексы (10 минут, $0)
Зачем вторым?
- Максимальный эффект при минимальных затратах
- Решает root cause (медленные запросы)
- Запросы #1 и #3 ускоряются в 70-100 раз
Эффект:
- DB CPU: 60% → 30%
- P99 latency: 5,200ms → 1,200ms
- Error rate: 2% → 0.5%
Риск:
- Создание индекса занимает 10 минут
- Используем
CREATE INDEX CONCURRENTLY(не блокирует, но дольше)
3️⃣ Убить аналитику (2 минуты, $0)
Зачем третьим?
- Быстрая победа
- Освобождаем БД от ненужной нагрузки
- Аналитика не критична для checkout
Эффект:
- DB CPU: 30% → 20%
- Latency: -10%
Минусы:
- Потеря данных аналитики
- Нужно будет вернуть async (действие E) позже
4️⃣ Горизонтальное масштабирование API (8 минут, $210/мес)
Зачем четвертым?
- БД уже не bottleneck (20% CPU)
- Распределяем нагрузку по API
- Готовимся к следующему скачку трафика
Эффект:
- API CPU: 50% → 25%
- Headroom для роста
5️⃣ pgbouncer (5 минут, $0)
Зачем пятым?
- Оптимизация connections
- Защита на будущее
- Легко и быстро
Эффект:
- Connection pooling работает
- Готовность к росту
Результат через 30 минут
| Метрика | Было (14:06) | Стало (14:36) | Улучшение |
|---|---|---|---|
| RPS | 2,000 | 800 (rate limited) | Контролируемо |
| API P99 latency | 5,200ms | 400ms | -92% |
| Error rate | 12% | 0.3% | -97% |
| DB CPU | 98% | 20% | -80% |
| Revenue loss | $450/min | $90/min | -80% |
| Total cost | $210/мес | Окупится за 1 день |
Что НЕ делали и почему
❌ Действие A (Вертикальное масштабирование БД) — не нужно, индексы решили проблему дешевле
❌ Действие D (Redis кэш) — нет времени (20 минут), сделаем позже
❌ Действие E (Async аналитика) — просто убили аналитику, вернем async позже
❌ Действие F (Сессии в Redis) — нужен Redis, которого нет
❌ Действие H (CDN) — не критично прямо сейчас
Что дальше: Post-Incident Action Items
Сегодня вечером (после инцидента)
-
✅ Написать постмортем
- Что случилось
- Почему
- Что сделали
- Lessons learned
-
✅ Вернуть аналитику через очередь (Действие E)
- Redis + worker
- Async processing
-
✅ Добавить мониторинг
- Alert: DB CPU > 70%
- Alert: P99 latency > 1000ms
- Alert: Error rate > 1%
На этой неделе
-
✅ Добавить Redis кэш (Действие D)
- Кэширование топ-запросов
- Cache warming для популярных продуктов
-
✅ Перенести сессии в Redis (Действие F)
- Разгрузка БД
- Быстрее обновления
-
✅ CDN для статики (Действие H)
- Разгрузка Nginx
В следующем месяце
-
✅ Read replicas для БД
- Разделение read/write
- Масштабирование чтений
-
✅ Auto-scaling для API
- Автоматическое масштабирование при росте
-
✅ Chaos engineering
- Регулярные тесты отказоустойчивости
- Kill random pod every day
Уроки из этого инцидента
1. Индексы — это бесплатная производительность
ROI: $0 вложений, -70% latency, -80% DB CPU
Почему не было индексов?
- Никто не проверял медленные запросы
- Нет
pg_stat_statements - Нет регулярного аудита
Что изменить:
- ✅ Включить
pg_stat_statements - ✅ Еженедельный аудит топ-10 запросов
- ✅ Автоматический alert на missing indexes
2. Мониторинг должен быть ДО инцидента
Что было:
- Узнали о проблеме от CEO, не от мониторинга
- Нет alertов на критичные метрики
Что нужно:
- ✅ Alert: P99 latency > 1000ms
- ✅ Alert: Error rate > 1%
- ✅ Alert: DB CPU > 70%
- ✅ Runbook для каждого alert
3. Rate Limiting — это не зло, это защита
Заблуждение: "Мы не можем отказывать клиентам"
Реальность:
- Лучше отказать 40% клиентам с 429, чем всем с 500
- Контролируемая деградация > полный крах
Стратегия:
- ✅ Rate limiting по умолчанию
- ✅ Приоритизация (authenticated users > anonymous)
- ✅ Graceful degradation
4. Вертикальное масштабирование — не всегда ответ
Соблазн: "Давайте просто купим больше CPU"
Реальность:
- $320/месяц за +4 vCPU БД
- VS $0 за индексы
- Индексы дали больше эффекта
Правило:
- Сначала оптимизируй
- Потом масштабируй
5. Async — твой друг
Проблема: Аналитика блокирует основной поток
Решение:
- Всё, что не критично для ответа → в очередь
- Аналитика, email, push notifications — async
6. Стоимость downtime vs стоимость решения
Расчёт:
- Downtime 5 минут = $2,250
- Индексы + rate limiting = $0, 15 минут, без downtime
- Потери за 15 минут = $6,750 (vs $2,250)
Но:
- Индексы решили проблему навсегда
- Вертикальное масштабирование — временно
Вывод: Иногда стоит потерпеть 15 минут, чтобы решить root cause
7. Бюджет — это constraint, который делает тебя лучше
С деньгами: "Давайте купим самую большую БД" Без денег: "Давайте оптимизируем запросы"
Результат:
- С деньгами: проблема вернется через месяц
- Без денег: проблема решена навсегда
Практика: Ваш инцидент
Задание 1: Постмортем
Напишите постмортем для этого инцидента по шаблону:
# Postmortem: API Latency Spike (ProductHunt Traffic)
**Date:** 2024-12-15
**Duration:** 30 минут
**Impact:** 12% error rate, $13,500 revenue loss
**Severity:** SEV-1
## Timeline
- 14:03 — Первое упоминание в Slack
- 14:04 — Alert: P99 > 5000ms
- 14:06 — Началось расследование
- ...
## Root Cause
...
## Resolution
...
## Action Items
1. [ ] ...
2. [ ] ...
## Lessons Learned
...Задание 2: Ваш план
Решите инцидент своим способом. Может быть, вы выберете другие действия?
Что, если:
- У вас $5,000 бюджета, а не $1,000?
- У вас 60 минут, а не 30?
- У вас уже был Redis?
- У вас не было прав создавать индексы (нужно согласование DBA)?
Задание 3: Автоматизация
Напишите alerting rules для Prometheus, чтобы такое не повторилось:
# alerting_rules.yml
groups:
- name: api_critical
rules:
- alert: HighLatency
expr: ???
for: ???
annotations:
summary: "???"
runbook: "???"Задание 4: Стоимостный анализ
Посчитайте ROI каждого действия:
| Действие | Стоимость | Эффект (снижение latency) | ROI |
|---|---|---|---|
| Индексы | $0 | -70% | ♾️ |
| Вертикальное | $320/мес | -30% | ??? |
| Кэш | $180/мес | -40% | ??? |
Что дальше
Вы пережили свой первый инцидент. Поздравляю!
Но это только начало. В следующих уроках мы разберем:
- Урок 01: Метрики и компромиссы — как правильно измерять и что игнорировать
- Урок 02: Горизонтальное масштабирование — когда добавлять серверы, а когда нет
- Урок 03: Вертикальное масштабирование — как выжать максимум из одной машины
- Урок 04: Кэширование — стратегии, стоимость, ROI
- И так далее...
Но прежде чем идти дальше, убедитесь, что вы поняли:
- ✅ Индексы — первое, что проверяешь
- ✅ Мониторинг — обязательный минимум
- ✅ Rate limiting — защита, не зло
- ✅ Async — всё, что можно
- ✅ Стоимость — считается всегда
- ✅ Root cause > симптомы
Готовы к следующему уроку? Там будет теория, метрики, и глубокое погружение в каждый из аспектов, которые вы только что видели в боевых условиях.
Не готовы? Пройдите практику еще раз. Попробуйте другие сценарии. Highload — это не про знание, это про умение принимать решения под давлением.
Поздравляю! Вы прошли триггер паники. Теперь вы знаете, как выглядит реальный highload. Дальше будет проще — мы разберем каждый аспект детально, без паники.