Skip to main content
Back to course
Архитектура высоконагруженных веб-приложений
4 / 1136%

Вертикальное масштабирование: выжать максимум из одной машины

80 минут

Для кого: инженеры и техлиды, которым уже мало «просто добавить сервер». Если вы не можете объяснить, почему CPU 30% при P99 = 2 секунды, или не знаете, что делать с 500k QPS на одной БД — этот урок выбьет почву из-под ног.

Провокация №1: вы профилировали продакшен?

  • Симптом: «У нас тормозит, но CPU свободен». Перевод: никто не снимал профили, никто не знает, где время тратится.
  • Проверьте себя: если последняя запись в profile.txt старше месяца, вы не контролируете систему.
  • План минимум: перед каждым релизом запускайте профилирование хотя бы на staging под реальной нагрузкой. Раз в неделю — на production replica (read-only) или с помощью py-spy/perf.

1. Когда вертикальное масштабирование оправдано

ПризнакЧто делаем
CPU < 60%, P99 растётПрофилируем код, ищем блокировки, I/O-узкие места
CPU > 80% и линейный рост RPSScale-up + оптимизация — ещё есть смысл
Память > 85%, GC > 200 мсУбираем утечки, уменьшаем heap, включаем G1/ZGC, переходим на streaming
Диски/сеть упёрлисьДобавляем NVMe, 25–100G NIC, оптимизируем batching
Один сервер дороже кластераПора scale-out. Вертикаль исчерпана

Ключевой критерий: если удвоение CPU/RAM даёт <50% прироста throughput или стоит дороже, чем добавление второго сервера + LB, значит, пора горизонтально.

Провокация №2: вы знаете top-3 функции по CPU?

  • Node.js/Go/Python — неважно. Если вы не можете назвать топ-3 «пожирателя CPU» за последние сутки, значит, у вас нет наблюдаемости.
  • Чек: node --prof, go tool pprof, py-spy top. Если команда их никогда не запускала — начинайте с этого.

2. Профилирование: инструменты и практика

2.1 Node.js

  1. CPU профили:
    node --prof app.js
    node --prof-process isolate-*.log > profile.txt
    head -50 profile.txt
  2. Flamegraph: clinic flame -- node app.js.
  3. Event loop lag: clinic bubbleprof.
  4. Chrome DevTools: node --inspect, открываем chrome://inspect, записываем CPU/Heap Snapshot.

Action: заведите регламент «1 flamegraph в неделю по топ-эндпоинту». Без доказательств (где CPU сгорает) оптимизация = пальцем в небо.

2.2 Python / Go / JVM

  • Python: cProfile, line_profiler, py-spy record -o profile.svg --pid. Используйте asyncio для I/O-bound задач.
  • Go: go test -cpuprofile=cpu.out, go tool pprof cpu.out. Добавьте /debug/pprof на сервисы.
  • JVM: async-profiler, jcmd, Java Flight Recorder. Следите за GC-паузами (gc_pause_seconds), heap usage.

2.3 Наблюдаемость

  • Prometheus/Grafana: process_cpu_seconds_total, process_resident_memory_bytes, gc_pause_duration_seconds, thread_count.
  • Tracing: Jaeger/Zipkin, чтобы видеть cross-service задержки.
  • Логи: включите structured logging с request ID и latency.

Провокация №3: «Нам не нужна оптимизация, у нас монолит»

Монолит = не повод игнорировать performance. Часто проще оптимизировать 2 функции, чем запускать 10 микросервисов.

3. Оптимизация CPU/памяти/I/O

3.1 Антипаттерны

ПроблемаДиагностикаРешение
N+1 запросыpprof/tracing показывают каскадыJOIN, batch, DataLoader
Большие JSON в памятиHeap > 80%, GC паузыStreaming (pipeline, chunked), gzip на лету
Синхронные блокировкиFlamegraph показывает sync.MutexAsync queue, sharded locks, actor model
Локальный кэш без лимитаRSS растёт линейно, после деплоя падаетLRU/TTL, Redis
Логирование всего подрядCPU/Mem spikes на io.WriterAsync logging, sampling

3.2 Node.js рецепты

  • Event loop: выносите долгие операции в worker threads/queues.
  • Cluster mode: pm2 start app.js -i max, следите за pm2 monit.
  • Memory: используйте clinic heapprofiler, ограничивайте max_old_space_size, отключайте «бесконечные» кэши.
  • Streaming: вместо res.json() огромного массива → Readable.from(cursor) + res.write.

3.3 Python рецепты

  • asyncio, aiohttp, httpx → параллельные I/O.
  • uvloop ускоряет event loop.
  • GIL: вынесите CPU-bound в C extensions/Cython или процессы (multiprocessing, Celery worker).
  • pydantic v2/dataclasses вместо dict для структурированного кода.

3.4 JVM/Go рецепты

  • JVM: G1/ZGC, -XX:+UseStringDeduplication, -Xms=-Xmx (фиксированный heap), избегайте synchronized больших секций.
  • Go: профилируйте goroutine blocking, используйте context, не выделяйте огромные слайсы в циклах.

Провокация №4: ваша БД — узкое место?

  • Симптомы: CPU БД > 80%, pg_locks показывает contention, реплики отстают.
  • Чек-лист:
    1. Добавьте индексы (95% успеха).
    2. Включите connection pooling (pgbouncer, HAProxy, pgxpool).
    3. Уберите long transactions, делайте batch insert/update.
    4. Используйте EXPLAIN ANALYZE для топ-5 запросов.
    5. Включите read replica + app-side routing, если 80% трафика — чтение.

Decision: если одна БД дороже, чем шардированный кластер (Citus/Vitess), думайте про горизонталь. Но сначала выжмите максимум: индексы, partitioning, caching.

4. Memory & GC дисциплина

  • Node: следите за heapUsed, heapTotal, external. GC паузы > 100 мс = тревога.
  • JVM: GC pause > 200 мс → сокращайте heap, меняйте GC, уменьшайте allocation rate.
  • Python: используйте objgraph, tracemalloc для утечек, избегайте циклических ссылок.
  • Go: GOGC=100 по умолчанию. Повышайте для экономии памяти, снижайте для латентности.

Практика: подключите автоматический alert, если rss > 80% или gc_pause > 200ms. Утечки ищите через heap dump + сравнение до/после нагрузки.

Провокация №5: I/O — немой убийца

  • Симптом: CPU свободен, latency зашкаливает. Значит, упёрлись в диски/сеть.
  • Решение:
    • NVMe вместо HDD, RAID10 вместо RAID5.
    • 25G/40G сетевые карты, если у вас Kafka/Redis и большие payload.
    • Batching: отправляйте пачки сообщений, а не по одному (Kafka producer batches, HTTP pipelining, gRPC streaming).
    • Compression: protobuf + gzip = меньше сети.

5. Экономика вертикального масштабирования: когда scale-up дороже scale-out

Критический вопрос: Купить один сервер за $1000/мес или 5 серверов по $150/мес? Неправильный выбор = переплата в 2-3 раза.

5.1 Ценовая нелинейность: закон убывающей отдачи

Проблема: CPU/RAM/disk не масштабируются линейно по цене.

Пример (AWS EC2, us-east-1, on-demand):

InstancevCPURAMЦена/часЦена/месяц$/vCPU$/GB RAM
t3.medium24GB$0.0416$30$15$7.5
t3.large28GB$0.0832$60$30$7.5
t3.xlarge416GB$0.1664$120$30$7.5
t3.2xlarge832GB$0.3328$240$30$7.5
c6i.4xlarge1632GB$0.68$490$30.6$15.3
c6i.8xlarge3264GB$1.36$980$30.6$15.3
c6i.12xlarge4896GB$2.04$1,470$30.6$15.3

Наблюдение:

  • T3 family: линейная цена до 8 vCPU
  • После 8 vCPU: цена за vCPU удваивается (compute-optimized)
  • Большие инстансы (>16 vCPU): premium цена

Вывод: После определенного порога вертикальное масштабирование становится экономически невыгодным.


5.2 Break-even analysis: когда переходить на scale-out

Сценарий: API требует 16 vCPU и 32GB RAM.

Вариант A: Один большой сервер

  • Instance: c6i.4xlarge (16 vCPU, 32GB)
  • Цена: $490/месяц
  • Availability: 99% (single instance)
  • Deployment: downtime required

Вариант B: Кластер из 4 серверов

  • Instance: 4× t3.xlarge (4 vCPU, 16GB каждый)
  • Цена серверы: 4 × $120 = $480/месяц
  • ALB: $25/месяц
  • Total: $505/месяц
  • Availability: 99.95% (multi-instance)
  • Deployment: zero-downtime rolling

Сравнение:

МетрикаScale-up (1 сервер)Scale-out (4 сервера)
Цена$490/мес$505/мес (+3%)
Availability99%99.95%
DeploymentDowntimeZero-downtime
FailoverManual (5-10 мин)Auto (0 секунд)
HeadroomНужен апгрейдДобавить +1 сервер

Вывод: При паритете цен scale-out выигрывает по надежности и гибкости.


5.3 Когда вертикальное масштабирование выгоднее

Кейс 1: Маленькая нагрузка (<500 RPS)

  • 1× t3.medium ($30/мес) vs 2× t3.small + ALB ($50/мес)
  • Экономия: 40% в пользу вертикали

Кейс 2: Stateful приложения (БД, кэш)

  • PostgreSQL требует мощный CPU для сложных запросов
  • Sharding БД = сложность × 10, costs × 3
  • Вертикаль выгоднее до момента, пока 1 инстанс справляется

Кейс 3: Лицензирование per-core

  • Oracle, SQL Server, некоторые коммерческие БД
  • Цена = cores × license cost
  • 1× 32-core ($X) дешевле, чем 4× 8-core ($4X)

Кейс 4: Low latency требования

  • Intra-server communication быстрее inter-server
  • Network latency = 0 vs 0.5-2ms
  • Для highload trading, gaming — критично

5.4 Когда горизонтальное масштабирование выгоднее

Кейс 1: Большая нагрузка (>1000 RPS)

  • Break-even point обычно 500-1000 RPS
  • После этого scale-out дешевле и надежнее

Кейс 2: Stateless приложения

  • API, web servers, workers
  • Нет стоимости координации

Кейс 3: Бюрстовая нагрузка

  • Auto-scaling легко добавляет/убирает серверы
  • Вертикаль требует manual intervention

Кейс 4: Multi-region

  • Легко распределить серверы по регионам
  • Один большой сервер = single region

5.5 Реальный кейс: оптимизация монолита

Компания: SaaS стартап, монолит на Ruby on Rails

Было:

  • 1× c5.9xlarge (36 vCPU, 72GB): $1,530/месяц
  • CPU utilization: 40% avg, 90% peak
  • Memory: 50GB используется
  • Problem: дорого, но scale-out требует рефакторинг

Анализ затрат на рефакторинг:

ВариантSetup costMonthly costBreak-even
Оставить как есть$0$1,530
Rightsizing (меньший инстанс)$0$765 (c5.4xlarge)Сразу
Scale-out (рефакторинг)$20,000 (2 месяца работы)$400 (4× t3.2xlarge + ALB)18 месяцев

Что сделали:

Фаза 1 (немедленно): Right-sizing

  • c5.9xlarge → c5.4xlarge (18 vCPU, 36GB)
  • Стоимость: $1,530 → $765/месяц
  • Экономия: $765/месяц, окупаемость немедленная

Фаза 2 (6 месяцев): Постепенный рефакторинг

  • Вынесли фоновые задачи в отдельные воркеры (2× t3.large)
  • Добавили Redis для сессий
  • Итого: c5.2xlarge (монолит) + 2× t3.large (workers) + Redis
  • Стоимость: $380 + $120 + $50 = $550/месяц
  • Экономия: $1,530 → $550 = -$980/месяц (-64%)

ROI: Работа заняла 3 месяца ($15k зарплат). Окупилось за 15 месяцев.


5.6 Bare metal vs Cloud: когда самохост выгоднее

Сценарий: Нужен мощный сервер (32 vCPU, 128GB RAM).

Вариант A: AWS c6i.8xlarge

  • Цена: $980/месяц (on-demand)
  • Reserved 3yr: $550/месяц (-44%)

Вариант B: Hetzner Dedicated (bare metal)

  • Server: AX102 (32 cores AMD EPYC, 128GB ECC)
  • Цена: €119/месяц (~$130/месяц)
  • Setup: €0

Но учтите скрытые costs:

СтатьяCloud (AWS)Bare metal (Hetzner)
Сервер$550/мес (RI 3yr)$130/мес
Data transfer$45/мес (500GB)$0 (20TB included)
BackupsIncluded (snapshots)$20/мес (external)
MonitoringCloudWatch $10Self-hosted $0 / Paid $20
DevOps time5% = $400/мес15% = $1,200/мес
Total$1,005/мес$1,370/мес

Вывод: Bare metal дешевле по железу, но дороже по DevOps времени. Выгоден только при масштабе (10+ серверов, dedica ted DevOps team).


5.7 Reserved Instances для вертикального масштабирования

Стратегия: Если нагрузка стабильна, экономьте 40-72% через Reserved Instances или Savings Plans.

Пример (c5.4xlarge):

Pricing modelPrice/hourPrice/monthSavings vs On-Demand
On-Demand$0.68$4900%
Reserved 1yr (no upfront)$0.438$316-36%
Reserved 1yr (all upfront)$0.395$285-42%
Reserved 3yr (partial upfront)$0.289$208-58%
Savings Plans 3yr$0.320$230-53%

Рекомендация:

  • Стабильная нагрузка (БД, cache) → Reserved 3yr: экономия 58%
  • Растущая нагрузка → Savings Plans: гибкость + 53% экономии
  • Burst нагрузка → On-Demand + Auto-scaling

5.8 Формула выбора: scale-up vs scale-out

def should_scale_out(current_cost, num_servers_needed, app_type):
    """
    Решает, стоит ли переходить на горизонтальное масштабирование
    """
    # Стоимость scale-up (один большой сервер)
    scale_up_cost = get_instance_cost(target_specs)
 
    # Стоимость scale-out (кластер малых серверов)
    server_cost = get_instance_cost(target_specs / num_servers_needed)
    lb_cost = 25  # ALB
    scale_out_cost = (server_cost * num_servers_needed) + lb_cost
 
    # Refactoring cost (если stateful)
    refactoring_cost = 0
    if app_type == "stateful":
        refactoring_cost = 20000  # 2 месяца работы
 
    # Break-even period
    monthly_savings = scale_up_cost - scale_out_cost
    break_even_months = refactoring_cost / monthly_savings if monthly_savings > 0 else float('inf')
 
    # Решение
    if break_even_months < 12:  # Окупится за год
        return True, f"Scale-out окупится за {break_even_months:.0f} месяцев"
    else:
        return False, "Scale-up выгоднее"
 
# Пример
should_scale_out(
    current_cost=1530,  # $1530/мес (c5.9xlarge)
    num_servers_needed=4,
    app_type="stateful"
)
# Output: (True, "Scale-out окупится за 18 месяцев")

5.9 Cost optimization checklist для вертикального масштабирования

  • Профилирование перед апгрейдом: Убедитесь, что bottleneck — это CPU/RAM, а не код
  • Right-sizing: Проверьте utilization (CloudWatch, Datadog) — может быть, текущий инстанс избыточен
  • Reserved Instances: Если нагрузка стабильна минимум год, экономьте 40%+
  • Spot для dev/staging: Экономия 70% для некритичных окружений
  • Сравните с scale-out: Посчитайте break-even point
  • Учтите DevOps costs: Время команды = деньги
  • Амортизация refactoring: Окупается ли переход на scale-out за 12-24 месяца?

6. Практическая дисциплина

  1. Профилирование:
    • Раз в неделю делайте flamegraph/top для топ-эндпоинта.
    • Сравнивайте профили до/после релизов.
  2. Load test scale-up:
    • На staging увеличивайте лимиты CPU/памяти и смотрите, где упираетесь.
    • Цель: найти точку, когда вертикальный рост перестаёт помогать.
  3. БД-интенсив:
    • Запустите EXPLAIN ANALYZE для 5 самых медленных запросов.
    • Внедрите connection pool, измерьте latency.
  4. Memory audit:
    • Снимите heap dump при нагрузке и после неё.
    • Найдите классы/объекты, которые не освобождаются.
  5. I/O benchmark:
    • Используйте fio, iperf, wrk для проверки дисков/сети.
    • Заведите SLO: «max disk latency < 5ms».
  6. FinOps анализ (НОВОЕ):
    • Рассчитайте break-even point: scale-up vs scale-out для вашей нагрузки
    • Сравните стоимость: on-demand, reserved instances (1yr, 3yr), savings plans
    • Если используете монолит: оцените ROI рефакторинга в scale-out архитектуру
    • Посчитайте TCO (Total Cost of Ownership) на 3 года: cloud vs self-hosted

Безопасность: профилирование и load test выполняйте на staging или изолированных копиях. Для production-профайлеров (py-spy, pprof) следите за overhead и снимайте не больше 30–60 секунд.

Что дальше

Вы squeezed всё из одной машины. Теперь либо переходите к горизонтальному масштабированию (уже умеете после урока 02), либо проектируйте гибридную схему: scale-up + scale-out. В следующих уроках — кэширование, очереди, CQRS. Каждый шаг будет таким же жестоким к самообману, как этот.

Вертикальное масштабирование: выжать максимум из одной машины — Архитектура высоконагруженных веб-приложений — Potapov.me