Производительность
Основы GC: за что рантайм берёт налог
p99 latency сервиса взлетает до 800 мс каждые несколько секунд. CPU в норме. Медленных запросов нет. Открываем GC-лог — паузы 600-700 мс каждые 4 секунды. Сервис аллоцирует 1 ГБ/сек, и рантайм останавливает весь мир для уборки.
Что делает сборщик мусора
Сборщик мусора обменивает CPU на memory safety: обходит кучу, маркирует всё достижимое от корней (регистры, стеки потоков, глобальные переменные) и возвращает остальное. Вам не нужно вызывать free() — рантайм делает это за вас. Цена: рантайм тратит CPU на учёт, и иногда останавливает приложение для этого.
Три рычага всегда в напряжении:
- Время паузы — как долго приложение остановлено, пока GC работает.
- Throughput — сколько CPU коллектор забирает у вашего кода.
- Размер кучи — сколько overhead-памяти нужно коллектору для эффективной работы.
Минимизировать все три одновременно невозможно.
Метафора кухни
Представьте кухню ресторана. Повара (ваш код) производят грязную посуду (аллокации). Посудомойщик (GC) её чистит. Если повара генерят посуду быстрее, чем мойщик успевает, посуда копится — кухне приходится останавливаться, чтобы разгрузить раковину. Современный concurrent-посудомойщик моет, пока повара работают; старый stop-the-world-посудомойщик заставляет всех ждать. Даже лучший посудомойщик стоит воды и электричества (CPU). Быстрая кухня — не от магического посудомойщика: от поваров, переиспользующих тарелки.
Почему rate аллокаций важнее размера кучи
Размер кучи показывает, сколько памяти программа держит в данный момент. Rate аллокаций показывает, как часто запрашивается новая память. Циклы GC запускаются пропорционально скорости накопления мусора — то есть rate аллокаций, а не размер кучи.
| Сценарий | Размер кучи | Rate аллокаций | Частота пауз GC |
|---|---|---|---|
| Большой стабильный кэш | 4 ГБ | 50 МБ/с | Раз в ~80 с, короткие паузы |
| High-throughput API | 100 МБ | 1 ГБ/с | Каждые ~0,1 с, частые спайки |
Сервис с 4 ГБ кучи, но медленными аллокациями, почти не замечает пауз. Сервис со 100 МБ кучи и rate 1 ГБ/с — в постоянном GC. Рычаг для хвостовой latency — rate аллокаций, а не размер кучи.
Цикл mark-sweep
Любой tracing GC следует одной схеме:
- Сканирование корней — кратко останавливаем, определяем корни (регистры, стеки, глобалы).
- Маркировка — обходим граф ссылок от корней; маркируем каждый достижимый объект.
- Очистка — возвращаем память каждого немаркированного объекта.
- Опционально компактизация — перемещаем живые объекты, устраняя фрагментацию.
- Обновление ссылок — исправляем указатели на перемещённые объекты.
- Возобновление — приложение работает на полной скорости до следующего цикла.
Наивная версия паузит приложение на шагах 1–5. Для кучи 32 ГБ это могут быть секунды — неприемлемо для latency-sensitive сервиса. Современные коллекторы сокращают или устраняют большинство STW-фаз.
Concurrent vs stop-the-world GC
Stop-the-world (STW) коллектор паузит все потоки приложения, пока работает. Простой в реализации; паузы растут с размером кучи.
Concurrent коллектор выполняет большую часть работы параллельно с потоками приложения, поэтому видимые пользователю паузы коротки (субмиллисекундные) вместо длинных (десятки-сотни мс). Ему нужны write barriers — небольшие фрагменты кода, выполняемые при каждой записи ссылки, чтобы держать коллектор в курсе. Стоимость барьера ~2-10% CPU; выгода — короткие паузы.
Почему это работает
Все современные concurrent GC всё ещё имеют некоторые stop-the-world фазы — сканирование корней, обработка weak-ref, remap. Concurrent коллекторы минимизируют STW, но не устраняют его. Всегда смотрите GC-логи на реальное распределение пауз, а не на заголовки от вендоров.
p99 latency сервиса коррелирует с паузами GC. Что проверить ПЕРВЫМ?
Почему concurrent GC предпочтительнее stop-the-world GC для production-сервисов?
Расставьте концептуальные стадии типичного цикла сборки мусора по порядку:
- 1 Определить корни — регистры, стеки потоков, глобальные переменные
- 2 Маркировка — обход от корней, пометка каждого достижимого объекта
- 3 Очистка — возврат памяти немаркированных объектов
- 4 Опционально компактизация — перемещение живых объектов для устранения фрагментации
- 5 Обновление ссылок на перемещённые объекты
- 6 Возобновление приложения на полной скорости до следующего цикла
Заполните пропуск: сборка мусора обменивает CPU на memory _______ — вам не нужно отслеживать каждую аллокацию вручную, но рантайм платит за учёт циклами, которые могли бы достаться вашему коду.
- 01В одном абзаце: почему rate аллокаций важнее общего размера кучи для хвостовой latency, определяемой GC?
- 02Назовите 3-way tradeoff любого GC и приведите пример коллектора, оптимизирующего каждый из крайних значений.
Сборщик мусора маркирует достижимые объекты и возвращает остальное, обменивая циклы CPU на memory safety. Три рычага — время паузы, throughput и размер кучи — нельзя минимизировать одновременно. Rate аллокаций определяет частоту циклов GC сильнее, чем размер кучи: небольшой сервис с rate 1 ГБ/с испытывает куда большее давление GC, чем большой кэш с rate 50 МБ/с. Современные concurrent GC делают большую часть работы параллельно с потоками приложения, удерживая паузы ниже 1 мс, но всё ещё имеют короткие STW-фазы для сканирования корней. Первый рычаг для хвостовой latency от GC — всегда профиль аллокаций, а не выбор коллектора.
встречается в159
- Путь запроса: семь остановок от сокета до ответаjunior
- Accept и парсинг: от очереди ядра до типизированного запросаmiddle
- Маршрутизация и middleware: что выполняется и в каком порядкеmiddle
- Обработчик и ответ: от бизнес-логики до байтов на проводеmiddle
- Стриминг и backpressure: когда клиент читает медленнее, чем вы пишетеsenior
- Таймауты и хвостовая задержка: бюджеты, дедлайны и ловушка fan-outsenior
- Middleware и DI: два паттерна, формирующие любой backendjunior
- Пишем middleware: сигнатуры, next() и три модели фреймворковmiddle
- Инверсия управления: как зависимости добираются до классаmiddle
- Скоупы и время жизни DI: singleton, request, transientmiddle
- DI как шов для тестов: фейки, моки и граница, которая важнаsenior
- DI-контейнеры в продакшене: графы разрешения, циклы и когда не стоитsenior
- Блокирующий vs неблокирующий I/O: два способа ждатьjunior
- Event loop: один поток, упорядоченные фазыmiddle
- Что блокирует цикл: CPU-работа и синхронные вызовыmiddle
- Вынос CPU-работы: worker threads и пул libuvmiddle
- Backpressure и ограниченная конкурентностьsenior
- Пропускная способность под нагрузкой: хвостовая задержка и насыщениеsenior
- Зачем пул: цена создания соединенияjunior
- Размер пула: почему больше не значит быстрееmiddle
- Взятие и таймауты: очередь ожидания — настоящий дроссель задержкиmiddle
- Стратегии retry: backoff, jitter и thundering herdmiddle
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Задачи, микрозадачи и scheduler.yield()middle
- Точность таймеров, троттлинг и фоновая работаmiddle
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- LCP: четыре фазы, одна доминирующая стоимостьmiddle
- INP: input delay, processing, presentationmiddle
- Lab vs field: почему они расходятся и как использовать каждыйmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Что такое индекс и как он ускоряет запросыjunior
- Leading-column rule: почему порядок столбцов в composite-индексе важенmiddle
- Partial, expression и covering-индексыmiddle
- Типы индексов: GIN, GiST, BRIN, Hash, Bloom и HOT-обновленияmiddle
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- Упражнение по проектированию индексов: стратегия полнотекстового поискаsenior
- EXPLAIN и планы выполнения: что решает планировщик и почемуjunior
- Типы сканирования: Seq, Index, Bitmap, Index-Onlymiddle
- Алгоритмы соединения и каскад ошибок оценки строкmiddle
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Расширенная статистика: исправление ошибок оценки для коррелированных колонокsenior
- Кеш планов, настройка константных стоимостей и внутренности планировщикаsenior
- Производственные режимы отказа и стабильность плановsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Биты в проводеjunior
- Математика задержкиmiddle
- Bufferbloat и перегрузкаsenior
- Граница физического уровняsenior
- Номера последовательности и состояние соединенияmiddle
- Управление потоком и перегрузкойmiddle
- BBR, производственная наблюдаемость и за пределами TCPsenior
- CDN: контент по соседствуjunior
- Anycast и GeoDNS: маршрутизация к ближайшему edgemiddle
- Многоуровневый кеш и Cache-Controlmiddle
- Заголовок Vary и cache keysmiddle
- Stale-while-revalidate и cache stampedesenior
- Edge workers и edge-side compositionsenior
- CDN: операции и observabilitysenior
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- WebSocket vs SSE vs long-polling: выбор правильного транспортаmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Алгоритмы балансировки: от round-robin до power-of-two-choicesmiddle
- L4 vs L7 балансировка и сохранение IP клиентаmiddle
- Health checks, connection draining и slow startmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- QUIC-потоки и head-of-line blockingjunior
- Объединённое рукопожатие и 1-RTTmiddle
- Connection ID и миграция сетиmiddle
- Обнаружение потерь и управление перегрузкойmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- Развёртывание и стоимость CPUsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- Двенадцать слоёв: один URL, семь действующих лицjunior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Критический путь рендеринга и Core Web Vitalsmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Метрики и cardinality: cost-модель time-series databasemiddle
- Логи и объём: cost-модель структурного логированияmiddle
- Трейсы и сэмплирование: cost-модель distributed tracingmiddle
- Join-ключи и exemplar''''ы: как три сигнала становятся компонуемымиmiddle
- Observability 2.0: широкие события и сдвиг стоимостиsenior
- Режимы сбоя и инженерная практика: cardinality budget''''ы, PII и сэмплированиеsenior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- Log levels и маршрутизация алертовmiddle
- Стратегии sampling и стоимость логовmiddle
- PII-редакция и log injectionsenior
- Propagation trace-контекста в логахsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Авто-инструментирование и ручные спаны: правило 80/20 в OTelmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Стратегии сэмплирования: head, tail и parent-basedmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- RED и USE: два чек-листа, одна дисциплина триажаjunior
- Инструментация RED в Prometheus: счётчики, гистограммы и дисциплина cardinalitymiddle
- USE на Linux: CPU, память, диск, сеть и PSImiddle
- Golden signals, структура дашборда и auto-RED в service meshmiddle
- Cardinality как драйвер затрат: label, PII, exemplars и семплированиеmiddle
- Native histograms, SLO и паттерны production-сбоевmiddle
- Выбор SLI и SLO-целей: отношения, не ощущенияmiddle
- Multi-window multi-burn-rate-алертинг: почему AND лучше ORmiddle
- Error budget policy, latency SLO и составные journeysmiddle
- Iceberg SLI, математика составного SLO и SLA vs SLOsenior
- Flame graph: читаем картинку, которая показывает, куда ушло времяjunior
- Sampling vs instrumentation profiling: почему 99 Гц побеждает в productionmiddle
- Типы профилей: CPU, память, off-CPU, mutex — какой когда братьmiddle
- Continuous profiling: always-on flame graphs с eBPF и корреляцией trace-idmiddle
- Как flame graph строится из сэмплов и как использовать его в productionmiddle
- Linux perf, внутренности eBPF, PGO и ограничения sampling''''аsenior
- Profiling в production: безопасность, war stories, OTel profiles и дизайн инфраструктурыsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Экономия на observability: удерживаем затраты в пределах 5% inframiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior