Runbook — Горизонтальное масштабирование
Техническое руководство из исходного проекта Notty. Примеры, параметры и эксплуатационные ограничения.
Когда: растёт latency, CPU/memory приближаются к limit'у, очередь job'ов накапливается. Цель — расширить ёмкость без переписывания.
Связанный документ: docs/scaling.md — performance toolkit
(cache, read replicas, query guardrails, search adapters).
Diagnose (≤ 15 мин)
Чек до того как что-то трогать:
- CPU/memory pod'ов:
kubectl top pod -n nottyилиdocker stats. - p95 latency:
notty_http_request_duration_seconds. - DB:
notty_db_slow_queries_total,notty_db_replica_fallbacks_total. - Cache: hit ratio =
hits / (hits + misses). Цель > 80%. - Job queue:
/api/admin/jobs/stats— растёт ли pending? - Внешние зависимости (Redis, S3, search) живы и без задержек.
Решение зависит от того, где боттлнек. Не масштабируйте app, если упирается в БД.
App tier — горизонтальное масштабирование
Kubernetes (HPA уже настроен)
deployment/kubernetes/hpa.yaml ставит min=2, max=10, target CPU 70%, memory 80%.
Реакция автоматическая. Если вручную:
kubectl -n notty scale deploy/notty --replicas=6
kubectl -n notty rollout status deploy/notty
При переходе с 1 → N нужно убедиться, что у вас:
- общий БД-конект-пул (managed PG с
max_connections >= replicas * pool_size); - shared-кэш (Redis adapter) — иначе каждый pod строит свой кэш с нуля и
cache_misses_totalвсплывает на rollout'ах. См.scaling.md, раздел Redis-adapter; - объектное хранилище (
STORAGE_TYPE=s3|...), не локальный volume.
docker-compose
docker-compose.yml рассчитан на одну реплику. Для горизонтального запуска
поднимайте Notty за внешним балансировщиком (Caddy/Cloudflare Load Balancing) и
держите stateful слои (PG, Redis, MinIO/S3) общими. Поднимать одного notty
сервиса с deploy.replicas в compose возможно только в Swarm.
Database tier — read replicas
DATABASE_REPLICA_URLS=postgres://...@replica-1:5432/notty,postgres://...@replica-2:5432/notty
Перезапустите Notty, проверьте:
notty_db_replica_fallbacks_total— стабильно низкий;- p95 latency на read-эндпоинтах падает.
Запись остаётся на primary. Locking SELECT FOR UPDATE, advisory locks и
nextval/setval сами уезжают на primary (см. scaling.md).
Cache tier — Redis
In-process кэш (default) хорош для одного pod'а. На N pod'ах:
- Поднимите managed Redis (или kubernetes-statefulset с PVC).
- Подключите Redis-адаптер кэша через плагин (см.
docs/scaling.md). - Прокиньте
REDIS_URLв env Notty. - Перезапустите. Hit ratio должен подняться обратно к 80%+.
Search tier — внешний движок
Если Content API замедляется на полнотекстовом поиске (q=...) на >100k
документов:
- подключите Meilisearch / Typesense / Elasticsearch адаптер
(см.
docs/scaling.md, раздел Search adapters); - запустите
getSearchService().reindex(<contentType>)для перевода корпуса.
Job tier
При росте pending job'ов:
- in-process queue хорош до десятков RPS на job. Дальше — Redis-backed
адаптер (
packages/server/src/lib/jobs/queue.ts). - проверьте, не залип ли конкретный обработчик:
GET /api/admin/jobs?status=running— поминутный slice поstarted_at. Если есть задачи > 30 мин — это баг обработчика, scale не поможет.
Что нельзя делать «для перформанса»
- Отключать query guardrails. Они спасают от случайных deep-pagination и populate-bombs.
- Выключать rate limiting в проде.
- Складывать media в local volume на нескольких репликах одновременно.
- Удалять
statement_timeout— это страховка против runaway-запросов.
После масштабирования
- Обновили minReplicas / maxReplicas в HPA, если это устойчивая нагрузка.
- Подтвердили connection pool в managed PG (
max_connections≥replicas * pool_size + admin/cron headroom). - Зафиксировали изменение в operations log.
- Если изменение временное (event/распродажа) — поставили задачу обратно масштабироваться вниз.
Источник: docs/operations/runbooks/scale-out.md. Снимок документации исходного проекта. Технический справочник сохраняет язык оригинала.