Браузер и фронтенд-рантайм
Точность таймеров, троттлинг и фоновая работа
Ваш цикл опроса дрейфует. setInterval фоновой вкладки срабатывает раз в минуту вместо каждой секунды. Диагностический таймер, измеряющий 50 мс, показывает 120 мс. Таймеры в браузере — это обещание со звёздочками, и звёздочек становится больше, как только что-то занято.
Почему setTimeout — нижняя граница, а не гарантия
setTimeout(fn, 100) не обещает запустить fn ровно через 100 мс — он обещает поставить задачу в очередь не раньше 100 мс. Фактическое время выполнения зависит от того, когда главный поток следующий раз освободится. Если во время срабатывания таймера выполняется задача длиной 300 мс, ваш колбэк ждёт все 300 мс плюс свою позицию в очереди.
Три слоя троттлинга усугубляют это:
Слой 1 — зажим вложенности 4 мс. После 5 уровней вложенности setTimeout (или setInterval) браузеры зажимают задержку до минимума 4 мс. Это правило спецификации, а не особенность: оно предотвращает busy-loop через цепочки setTimeout(0). Цикл из 100 итераций с setTimeout(0) занимает минимум 400 мс независимо от скорости работы.
Слой 2 — троттлинг фоновой вкладки. Когда вкладка скрыта (document.visibilityState === 'hidden'), браузеры зажимают таймеры до минимума 1 секунда. После того как вкладка несколько минут была скрытой, Chrome дополнительно снижает до одного раза в минуту. Ваш фоновый цикл опроса тихо замедляется до черепашьего темпа.
Слой 3 — выравнивание/объединение таймеров. Для экономии батареи браузеры округляют времена срабатывания таймеров до общей сетки, чтобы процессор просыпался один раз для многих таймеров вместо многих раз. Практическое следствие: никогда не используйте setTimeout/setInterval для чего-либо, требующего субкадровой точности. Для анимаций — requestAnimationFrame; для точного планирования — часы AudioContext или дельты performance.now() внутри rAF-цикла.
- Нормальная минимальная задержка
- 0 мс (зависит от позиции в очереди)
- Зажим вложенности (глубина ≥5)
- минимум 4 мс
- Фоновая вкладка
- ≥1 000 мс
- Фоновая вкладка (много минут)
- ≥60 000 мс
- Объединение для экономии батареи
- выравнивание по сетке, ~1 мс джиттер
Страница вызывает `setTimeout(loop, 0)`, где `loop` перепланирует себя. После 5 уровней вложенности что происходит с эффективной задержкой?
Видимость страницы и жизненный цикл event loop
Event loop не останавливается, когда вкладка уходит в фон, но меняет режим. document.visibilityState переключается в hidden, requestAnimationFrame перестаёт срабатывать совсем (нет рисования = нет rAF), и таймеры троттлятся как описано выше.
Событие visibilitychange — правильное место для остановки опроса, паузы видео, сброса несохранённого состояния. Современная замена ненадёжного события unload — pagehide плюс Page Lifecycle API с состояниями frozen и terminated: браузер может полностью заморозить фоновую вкладку (остановив её цикл), чтобы освободить память, затем оттаять при возврате. Код с открытыми соединениями или таймерами должен слушать freeze/resume, иначе после оттаивания он работает с устаревшим состоянием — закрытым WebSocket, истёкшим токеном, отсоединённым observer.
Фоновая вкладка запускает setInterval с опросом раз в секунду. После 5 минут в фоне, как часто Chrome обычно вызывает колбэк?
requestIdleCallback — планирование несрочной работы
Не вся работа срочна. Аналитические маяки, предзагрузка, прогрев кеша и сброс логов могут подождать, пока цикл ничем лучшим не занят. requestIdleCallback(fn) ставит в очередь колбэк, который браузер запускает только когда итерация заканчивается с запасным временем до следующего дедлайна кадра. Колбэк получает объект deadline, чей timeRemaining() сообщает, сколько мс можно безопасно использовать (ограничено примерно 50 мс).
Дисциплина: делайте небольшой срез, проверяйте timeRemaining(), и если он близок к нулю — перепланируйте остаток ещё одним requestIdleCallback. Необязательный параметр { timeout } принудительно запускает колбэк после дедлайна, чтобы работа не голодала вечно.
requestIdleCallback — кооперативная противоположность requestAnimationFrame: rAF говорит «запусти меня прямо перед следующей отрисовкой», ridle говорит «запусти меня только если никому больше не нужен поток». Вместе они позволяют держать срочную работу на rAF и откладывать всё остальное с критического пути.
deadline.timeRemaining() у requestIdleCallback ограничен примерно этим количеством миллисекунд — тем же, что и порог длинной задачи.
Комбинаторы промисов и планирование
Promise.all, Promise.allSettled, Promise.race и Promise.any меняют что вы ожидаете, но не то, как цикл планирует. Все четыре запускают каждый переданный промис немедленно — параллелизм достигается за счёт начала асинхронных операций до их ожидания.
Promise.all— отклоняется при первой ошибке (fail-fast). Подходит, когда любая ошибка делает весь результат бесполезным.Promise.allSettled— никогда не отклоняется; резолвится с массивом статусов. Подходит когда нужны частичные результаты (пять независимых виджетов дашборда, где ошибка одного не должна обнулять остальные).Promise.race— завершается при первом урегулировании, успехе или ошибке. Классическое использование: таймаут:Promise.race([fetchData(), rejectAfter(5000)]).Promise.any— резолвится при первом успехе, игнорируя отклонения пока все не провалятся. Подходит для избыточности, например гонки трёх CDN-зеркал.
Выбор неправильного комбинатора — ошибка корректности, а не производительности: Promise.all для дашборда означает, что ошибка одного медленного виджета обнуляет страницу.
Распространённый баг: написать const a = await fetchA(); const b = await fetchB(); когда оба fetch независимы. Два запроса выполняются последовательно, потому что второй await не начинается до резолва первого. const [a, b] = await Promise.all([fetchA(), fetchB()]) запускает оба немедленно. Разница в стоимости — один полный RTT. Признак в DevTools — два сетевых запроса с непересекающимся временем там, где оба могли бы выполняться параллельно.
У вас пять независимых API-вызовов для дашборда. Один из них может провалиться. Какой комбинатор подходит?
Срабатывает событие `visibilitychange` фоновой вкладки. Код держит открытый WebSocket и цикл опроса setInterval. Что делать?
- 01Назовите три слоя троттлинга, влияющих на точность setTimeout.
- 02Когда использовать requestIdleCallback вместо setTimeout(fn, 0)?
- 03Почему `const a = await fetchA(); const b = await fetchB();` — баг когда оба fetch независимы?
setTimeout(fn, delay) ставит задачу в очередь не раньше delay миллисекунд, но фактическое время срабатывания зависит от доступности главного потока и трёх слоёв троттлинга: зажим вложенности 4 мс (предотвращает busy-loop через setTimeout(0)), троттлинг фоновой вкладки (1 с → 1 мин) и объединение таймеров для экономии батареи. Для точного времени в анимациях — requestAnimationFrame или часы AudioContext: оба управляются конвейером рендеринга, а не очередью таймеров. Для несрочной работы requestIdleCallback выполняется только в настоящие промежутки простоя и даёт бюджет timeRemaining() для безопасного нарезания работы. Комбинаторы промисов не меняют планирование цикла, но меняют корректность: Promise.all для fail-fast, Promise.allSettled для частичных результатов, Promise.race для таймаутов, Promise.any для избыточности при первом успехе.
- Голодание микрозадач, длинные задачи и LoAFsenior
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- React, Vue и наблюдаемость INP в продакшенеsenior
- Event loop: вытащи взаимодействие из INP-адаsenior
- Event loop: тест с выбором ответаsenior
- Event loop: предскажи выводsenior
- Event loop: тест на припоминаниеsenior
встречается в193
- Путь запроса: семь остановок от сокета до ответа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
- Что такое индекс и как он ускоряет запросы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
- Сначала профиль: измерь куда реально уходит времяjunior
- Закон Амдала и self-time: потолок любого ускорения, которое ты можешь выпуститьmiddle
- Измерительный цикл: микробенч, макробенч, prod-профиль, эффект наблюдателяmiddle
- Чтение флейм-графов: формы, профайлеры по языкам и 60-секундный сканmiddle
- Статистические baseline''''ы: почему один запуск — не измерениеmiddle
- История профайлеров и ловушки микробенчей: от Кнута до GWPsenior
- Hardware counters, профили холодного старта и безопасность профилейsenior
- Непрерывное профилирование в масштабе: затраты, CI-гейты, корреляция с трейсами и антипаттерныsenior
- Что делает путь горячим: симптом против причиныjunior
- Пять форм hotspot''''а: CPU, аллокации, кэш, лок, syscallmiddle
- Чтение parent и child chains: где применять правкуmiddle
- JIT deopt, цикл fix-and-verify и PR-time профилированиеmiddle
- Аппаратные счётчики и Intel TMA: диагностика подкатегорийsenior
- False sharing и горячие пути нативных мостовsenior
- Горячие пути в production: безопасность, хвостовая латентность и происхождение инструментовsenior
- Иерархия памяти: почему расстояние важнее числа операцийjunior
- Row-major vs column-major: порядок доступа и разрыв в 9xjunior
- Branch prediction: 10–30 циклов штрафа за неожиданный ifmiddle
- Hardware prefetcher, TLB и memory-level parallelismsenior
- Основы GC: за что рантайм берёт налогjunior
- Алгоритмы GC: поколенческая гипотеза, concurrent marking и write barriermiddle
- GC tradeoffs: пауза, throughput, память и давление аллокацийmiddle
- Настройка GC: пейсинг, форма кучи и наблюдаемость аллокацийmiddle
- Внутреннее устройство GC: tri-color инвариант, write barriers и глубокое погружение в рантаймыsenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- N+1: одна логическая операция, много round-trip''''овjunior
- Семейства фиксов: JOIN, IN, preload и DataLoadermiddle
- Обнаружение N+1: query logs, APM traces и CI gatesmiddle
- DataLoader: батчинг по дереву резолверовmiddle
- Кросс-протокольный N+1: HTTP fan-out и Redis MGETmiddle
- N+1 в масштабе: исчерпание пула, изменения планов и денормализацияsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- Что на самом деле стоит bundle: download, parse, compile, executejunior
- Core Web Vitals: LCP, INP и CLSmiddle
- Code splitting: route-level, component-level, vendor splittingmiddle
- Tree shaking и compression: удаляем то, что не используемmiddle
- Third-party scripts: тихий убийца бюджетаmiddle
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior