Браузер и фронтенд-рантайм
Голодание микрозадач, длинные задачи и LoAF
Пользователь сообщает, что страница «полностью зависла» на несколько секунд. DevTools Performance показывает один непрерывный жёлтый скриптовый прямоугольник. Ни один вызов функции не доминирует в flame chart — работа продолжает перепланировать себя как микрозадачи, и цикл никогда не вырывается.
Паттерн отказа: голодание микрозадач
Микрозадачи дренируются до пустой между задачами. Если микрозадача планирует ещё одну микрозадачу до возврата, цикл никогда не вырывается из microtask checkpoint — нет рендеринга, нет ввода, нет дальнейших задач. Патологический пример:
function loop() {
Promise.resolve().then(loop); // планирует себя как микрозадачу
}
loop();Это вешает страницу бесконечно: очередь микрозадач никогда не пуста, шаг 4 (рендеринг) никогда не выполняется, пользователь видит замороженную вкладку. DevTools Performance показывает это как один непрерывный жёлтый скриптовый прямоугольник. Тот же паттерн встречается в продакшене как случайная рекурсия через async-цепочки: .then, который переподписывается на тот же observable, роутер-мидлвар, который ре-резолвится при каждом изменении, библиотека состояния, которая перезапускает реакции внутри реакции.
Обнаружение: записи длинных задач без явной доминирующей функции, скриптовый прямоугольник растёт без границ, INP > 1 с.
Лечение: вставить хотя бы одну уступку уровня задачи (setTimeout, MessageChannel, scheduler.postTask) в цепочку.
- Жёлтый скриптовый прямоугольник
- непрерывный, никогда не прерывается
- Записи Long Tasks API
- длительность растёт без границ
- INP
- > 1 000 мс
- Счётчик кадров
- 0 fps во время голодания
- Лечение
- вставить уступку уровня задачи в цепочку
Колбэк `MutationObserver` срабатывает синхронно после DOM-мутации. Каким видом работы он ставится в очередь?
Long Tasks API и Long Animation Frames
PerformanceLongTaskTiming. Появился в 2017, вызывает запись PerformanceObserver для любой задачи, превышающей 50 мс на главном потоке. Каждая запись включает время начала, длительность и массив attribution с исходным контекстом просмотра. Ограничение: attribution редко указывает на конкретную функцию; он говорит «что-то внутри iframe A заняло 230 мс» — полезно для SLO-дашбордов, менее полезно для поиска корневой причины. Используйте Long Tasks API для подсчёта длинных задач за сессию, а не для их отладки.
PerformanceLongAnimationFrameTiming (LoAF). Появился в 2023–2024 в Chromium. Срабатывает для любого кадра, время рендеринга которого превышает 50 мс. Запись LoAF включает атрибуцию по скриптам: массив того, какие скрипты выполнялись в кадре, что они делали (event-handler, classic-script, module-script, user-callback), сколько каждый занял. Это именно тот продакшен-диагностический инструмент, которым Long Tasks должны были быть с самого начала. Совместно с INP: когда INP регрессирует, запрашивайте записи LoAF из той же сессии, находите виновный кадр, получайте атрибуцию скриптов, деплойте фикс. Полный конвейер: от воспринимаемой пользователем метрики (INP) до конкретного скрипта (LoAF) до конкретной функции (sourcemap по URL скрипта).
[Long Task] 312 мс
attribution: same-origin
startTime: 2456.3
duration: 312.4
[Long Task] 287 мс
attribution: same-origin
startTime: 5102.7
duration: 287.1
[INP candidate] 410 мс
type: pointerdown -> click -> next paint
startTime: 2401.0
processingStart: 2456.3
processingEnd: 2768.7
presentationTime: 2811.0
attribution: handleSearch (search.js:142) Поле поиска показывает INP 410 мс, и лог длинных задач указывает на handleSearch. Обработчик делает: валидация запроса → вызов setSearchTerm (Redux) → запуск дебаунсированного fetch → ожидание результатов. Где 312 мс?
scheduler.yield() и Scheduler API
Что делает scheduler.yield(). Scheduler API (Chrome 115+, частично в Edge, Firefox/Safari за флагом на конец 2025) даёт платформе первоклассный примитив уступки. await scheduler.yield() приостанавливает текущую задачу, дренирует очередь микрозадач, позволяет ввоту и рендерингу выполниться, и возобновляет приостановленную задачу как следующую задачу — с приоритетом, который предотвращает низкоприоритетную работу от вклинивания вперёд. Раньше уступка через setTimeout(0) работала для рендеринга, но теряла позицию в очереди (любая задача, запланированная тем временем, выполнялась первой). С scheduler.yield() можно разбить задачу 200 мс на четыре куска по 50 мс, которые с точки зрения пользователя остаются одной логической операцией.
scheduler.postTask() с приоритетами. Тот же API предоставляет scheduler.postTask(callback, { priority }) с тремя приоритетами: user-blocking (работа ответа на ввод), user-visible (по умолчанию), background (несрочная). Практический паттерн: направлять обработчики ввода через user-blocking, чтобы они прыгали в очереди вперёд фоновой работы вроде сброса аналитики; направлять фоновую аналитику через background, чтобы она уступала срочному. Планировщик имеет доступ к системным сигналам (батарея, тепловой троттлинг, видимость страницы), которых нет у JS-кода.
Уступка длинной задачи без потери логической непрерывности
1/3Какая спецификация определяет event loop, очереди задач, очередь микрозадач и шаги рендеринга пошагово?
Спроектируйте конвейер ввода для поля поиска, фильтрующего 50 000 клиентских элементов и обязанного держать INP ниже 200 мс p75 на среднем Android.
- Бюджет кадра: ~10 мс после накладных расходов браузера.
- Цель INP: ≤200 мс p75.
- Нет длинных задач > 50 мс на главном потоке во время печати.
- Результаты фильтрации должны обновляться видимо в пределах 200 мс от последнего нажатия клавиши.
- Внеэкранные результаты могут рендериться лениво, но экранные должны быть корректными.
- Поддержка браузеров: Chrome, Safari, Firefox (без проблем с воркер-фолбэком).
- Дебаунсировать нажатия для объединения работы.
- Нарезать фильтрацию на задачи ≤50 мс с scheduler.yield() между ними.
- Использовать useTransition, чтобы рекоцилиация React не блокировала ввод.
- Для очень больших датасетов — перенести фильтрацию в Worker, главный поток остаётся свободным для ввода/рендеринга.
- Подключить LoAF-телеметрию, чтобы регрессии отлавливались в продакшене, а не в локальной разработке.
LoAF (PerformanceLongAnimationFrameTiming) отличается от PerformanceLongTaskTiming в ключевом аспекте. В каком?
- 01Поле поиска показывает INP 410 мс. LoAF сообщает о скрипте длительностью 312 мс, атрибутированном handleSearch. Проследите путь от телеметрии до фикса.
- 02В чём разница между scheduler.yield() и await Promise.resolve() как механизмами уступки?
- 03Опишите паттерн голодания микрозадач и приведите один продакшен-сценарий, где он появляется случайно.
Голодание микрозадач возникает когда микрозадача ставит в очередь другую микрозадачу до возврата — цикл застрял в microtask checkpoint и никогда не достигает шагов рендеринга или ввода. В DevTools это проявляется как один непрерывный жёлтый прямоугольник, а в продакшене — как INP > 1 с. Long Tasks API (PerformanceLongTaskTiming, 2017) считает задачи, превышающие 50 мс, но даёт только атрибуцию контекста просмотра. LoAF (PerformanceLongAnimationFrameTiming, 2023) срабатывает на кадр и даёт атрибуцию по скриптам, что делает его правильным инструментом для поиска корневых причин регрессий INP в продакшене. scheduler.yield() из Scheduler API (Chrome 115+) обеспечивает структурированную уступку уровня задач с сохранением приоритета очереди, а scheduler.postTask() позволяет назначать приоритеты user-blocking, user-visible или background, чтобы планировщик браузера — имеющий доступ к тепловым и батарейным сигналам — принимал лучшие решения, чем любая написанная вручную очередь приоритетов.
встречается в143
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимки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
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior