Наблюдаемость
Стратегии сэмплирования: head, tail и parent-based
При 10к запросов в секунду запись 100% трассировок заполняет бэкенд за часы и генерирует пятизначный ежемесячный счёт. Но если сбросить 99% трассировок, именно ошибки и медленные запросы, нужные для отладки, будут потеряны. Сэмплирование — это компромисс между стоимостью и точностью.
Head-сэмплирование: решение быстро и дёшево
Sampler SDK запускается на самом первом спане трассировки (корневом спане) и решает, записывать ли трассировку. Решение распространяется на нижестоящие сервисы через флаг sampled в заголовке W3C traceparent.
AlwaysOn — записывает каждую трассировку. Полная точность, максимальная стоимость.
AlwaysOff — ничего не записывает. Полезен для канарейки или сервиса под нагрузочным тестом.
TraceIdRatioBased(p) — записывает случайную долю p на основе хэша trace_id. Детерминировано между сервисами: если два сервиса оба используют TraceIdRatioBased(0.1), они независимо принимают одинаковое решение для одной трассировки, потому что хэш берётся от trace_id, который разделяется по всему запросу. При p=0.1 записывается 10% трассировок.
ParentBased — следует решению о сэмплировании родительского спана (через traceparent.sampled). Когда вышестоящий сервис решает сэмплировать, все нижестоящие следуют за ним. Когда вышестоящий не сэмплирует, нижестоящие обычно тоже нет, если локальный сэмплер не переопределяет. Предотвращает несогласованные частичные трассировки.
Production-комбинация: ParentBased(root=TraceIdRatioBased(0.2)). Корневой спан сэмплирует 20% трассировок. Все нижестоящие спаны наследуют решение. Результат: либо вся трассировка записана, либо ничего — никаких частичных трассировок с осиротевшими дочерними спанами.
Tail-сэмплирование: решение поздно, дорого
Tail-сэмплирование выполняется в Collector после того, как все спаны трассировки прибыли и трассировка завершена (или близка к этому). Оно может анализировать полный контекст трассировки: финальный статус-код, общую задержку, наличие спанов с ошибками где-либо в дереве, бизнес-атрибуты на любом спане.
Типичные политики tail-сэмплирования:
status_code=ERROR— сохранять 100% трассировок с любым спаном ERRORlatency > 1000ms— сохранять 100% медленных трассировокprobabilistic 1%— сохранять 1% всего остального как базовый трафик
Комбинация покрывает весь интересный трафик (ошибки, медлительность) и обеспечивает ограниченный по стоимости базовый уровень для нормального трафика.
| Подход | Момент решения | Видит ошибки? | Стоимость | С состоянием? |
|---|---|---|---|---|
| Head (TraceIdRatioBased) | При старте корневого спана | Нет — решает до возникновения ошибки | Низкая (SDK, <1 мкс) | Нет |
| Tail (процессор Collector) | После завершения трассировки | Да — видит полный контекст | Высокая (буферизует спаны в RAM) | Да — должен видеть все спаны |
| ParentBased head | При старте каждого спана | Нет — то же ограничение, что у head | Низкая | Нет |
Ограничение sticky-маршрутизации
Tail-сэмплирование требует, чтобы все спаны трассировки прибывали на один и тот же экземпляр Collector-шлюза. Если спаны одной трассировки попадают на два разных экземпляра, ни один из них не может оценить полный контекст трассировки, и решение о сэмплировании будет неверным.
Экспортёр loadbalancing OTel Collector на агентном уровне решает эту проблему: он хэширует по trace_id и маршрутизирует детерминировано на один и тот же pod шлюза для всех спанов трассировки.
Компромисс: шлюз становится с состоянием. Перезапуск pod или событие масштабирования перетасовывает кольцо хэшей и теряет трассировки в полёте. Production-меры защиты:
- Предварительный прогрев новых podов перед включением в кольцо балансировки нагрузки
- Консервативные политики масштабирования (избегать частых масштабирований во время пиков трафика)
- Размер буфера
num_tracesшлюза = пиковая_скорость ×decision_wait× защитный_коэффициент (~2x)
Числа
- Буфер tail-сэмплирования: ~1-2 ГБ на 50к активных трассировок (100 спанов по ~1 КБ/спан)
- decision_wait: 30-60 секунд (должен превышать p99 длительности трассировки)
- num_traces: размер =
пиковая_скорость × decision_wait × 2— при 2000 трассировок/с и окне 30с: ~120к - Head-сэмплирование в production: 10-20%
- Tail-сэмплирование сохраняет: 100% ошибок + 100% медленных (порог 1с) + 1-5% базового трафика
- Итого удержано: ~3-5% от общего объёма, 100% интересного трафика
Почему это работает
Почему не делать tail-сэмплирование 100% без head-сэмплирования? Потому что tail-сэмплирование буферизует каждый спан в RAM до закрытия окна decision_wait. При 10к RPS × 50 спанов на запрос × 30с — это 15 миллионов спанов в памяти — несколько ГБ. Head-сэмплирование пропорционально снижает буфер в полёте. При 20% head и tail-выборке все ошибки и медленные хвосты сохраняются, потому что tail-сэмплер всегда переопределяет в сторону сохранения. Комбинация даёт контроль стоимости (head) и точность (tail) с ограниченным и предсказуемым потреблением памяти.
Почему ParentBased(TraceIdRatioBased(0.2)) — стандартная комбинация head-сэмплера для распределённых систем?
Tail-сэмплирование на шлюзе требует, чтобы все спаны трассировки попадали на один экземпляр Collector. Какой механизм обеспечивает это в паттерне agent-to-gateway?
Упорядочите события tail-сэмплирования от создания спана до решения сохранить/сбросить:
- 1 Приложение отправляет спаны агентскому Collector
- 2 loadbalancing exporter агента хэширует trace_id, маршрутизирует на детерминированную реплику шлюза
- 3 Шлюз буферизует спаны в процессоре tail_sampling (буфер num_traces, в RAM)
- 4 После decision_wait (30-60с) оценивается полный контекст трассировки
- 5 Проверка политики: статус ERROR? Задержка свыше 1с? Вероятностный базовый трафик?
- 6 Сохранить или сбросить — сохранённые трассировки пересылаются экспортёру; сброшенные удаляются
- 01В чём ключевое различие между head-сэмплированием и tail-сэмплированием, и когда каждое из них даёт сбой?
- 02Почему tail-сэмплирование должно использовать sticky-маршрутизацию по trace_id, и что ломается, если спаны одной трассировки попадают на две реплики шлюза?
- 03Как определить размер буфера tail-сэмплирования (num_traces) и что происходит при его недостаточном размере?
Сэмплирование — это компромисс между стоимостью и точностью. Head-сэмплирование (TraceIdRatioBased, AlwaysOn, ParentBased) решает при старте корневого спана — дёшево (<1 мкс), без состояния, но слепо к ошибкам и задержке. ParentBased гарантирует, что все спаны трассировки следуют решению корня, предотвращая частичные трассировки. Tail-сэмплирование в Collector решает после прибытия полной трассировки — может сохранить 100% ERROR-трассировок и все трассировки с задержкой свыше 1с плюс 1% базового трафика. Требует буфера в памяти с состоянием (размер = пиковая_скорость × 30с × 2, обычно 1-2 ГБ на 50к активных трассировок) и sticky trace_id-маршрутизации через loadbalancing exporter. Production-паттерн: ParentBased(TraceIdRatioBased(0.2)) на SDK снижает объём буфера на 80%, пока tail-сэмплер сохраняет весь интересный трафик. Итог: ~3-5% от общего объёма удержано, 100% интересных трассировок захвачено.
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- OTel: построй vendor-нейтральный пайплайнsenior
- OTel: тест с множественным выборомsenior
- OTel: чтение конфигов и трейсовsenior
- OTel: тест на свободное припоминаниеsenior
встречается в167
- Путь запроса: семь остановок от сокета до ответа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
- Закон Амдала и 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