Сэмплирование и разбор инцидента
Сбор каждого trace удобен в маленькой лаборатории, но при большом потоке требует ресурсов. Рассмотрим sampling и применим уже созданные сигналы к модельному инциденту «Пульса». API быстро принимает задания, а возраст очереди растёт. Наша задача — отличить отказ worker, задержку базы и потерю наблюдения. Сам инцидент не выполнялся и не наблюдался: это последовательный разбор ожидаемых признаков на подготовленной конфигурации.
Sampling определяет, какие traces сохранять. Он не заменяет фильтрацию секретов и не делает выбранный payload безопасным. Даже один сохранённый trace с Authorization является утечкой. Ограничение данных, кардинальности и прав действует независимо от доли выборки. Также метрики должны измерять полный прикладной поток, а не выводиться только из сохранённых traces, иначе оценка надёжности начнёт зависеть от политики отбора.
Решение в начале trace
В SDK используется ParentBasedSampler с TraceIdRatioBasedSampler для новых корней. Переменная TRACE_RATIO по умолчанию равна единице, а для собственной лаборатории может задавать меньшую долю. Значение проверяется в диапазоне от нуля до единицы. Уже извлечённый родительский контекст влияет на решение потомков, поэтому worker обычно сохраняет согласованность producer и consumer одной операции.
Head sampling принимает решение до знания будущего результата. Если сохранить десять процентов новых корней, часть редких отказов может не попасть в traces. Это не обязательно поломка доставки. Для оценки общего числа отказов остаются metrics и безопасные logs. Нельзя взять десять найденных traces с ошибкой и объявить их полным количеством ошибок сервиса, не учитывая метод выборки и интервал.
Публичный посетитель способен прислать sampled flag в traceparent. Parent-based модель может ему довериться, если доверенная граница не установлена. Поэтому внешний gateway должен иметь согласованное правило: принять контекст от доверенного клиента, ограничить его либо начать свой trace. Случайная передача заголовков снаружи может изменить стоимость сбора или намеренно связать чужие операции в неудобную историю.
Решение после накопления
Tail sampling в Collector рассматривает уже полученные spans и может сохранять traces с ошибкой или большой задержкой. Это требует буфера, времени принятия решения и доставки всех spans одного trace к одному месту анализа. Нельзя сначала отбросить девяносто процентов корней head sampler, а затем ожидать, что tail sampler восстановит все ошибки. Данные, не отправленные SDK, отсутствуют для следующего слоя.
В архиве есть отдельный collector-tail.yaml как альтернативная полная конфигурация; основной Compose продолжает монтировать collector.yaml. Для такого варианта SDK сначала оставляют TRACE_RATIO=1. Tail config добавляет status-code policy и ограниченную вероятностную выборку обычных traces. Это подготовленный вариант для самостоятельного эксперимента, а не включённый нами режим запущенного Collector.
Окно решения не обязано дождаться worker. Наш consumer может начать после длинной очереди, когда producer-span уже обработан sampler. Поздние spans требуют анализа decision cache и реального времени задачи; короткое окно может дать неполный trace. Поэтому tail sampling нельзя вводить механически по совету «оставлять только ошибки»: асинхронный процесс «Пульса» имеет собственную длительность и риск поздних частей.
Модельный инцидент очереди
Начнём с общих метрик за выбранный интервал. Rate API остаётся обычным, 5xx не увеличивается, HTTP p95 невысок, но pulse_queue_oldest_seconds растёт. Это гипотеза, что проблема находится после фиксации HTTP. Одновременно проверяем heartbeat worker и наличие свежих точек. Если данные worker исчезли, сначала проверяем процесс и путь Collector, а не принимаем последнее значение очереди за текущее.
Полный файл incident.md состояния 11:
# Модельный инцидент: очередь растёт
Это учебная гипотеза; фактическая авария не выполнялась и не наблюдалась.
1. Определить интервал и scope: API pulse-api, worker pulse-worker, версия 1.0.0.
2. Сопоставить rate запросов, долю 5xx, p95 и возраст очереди. Проверить наличие самих данных.
3. Найти SERVER span POST /v1/reports в Tempo; по trace ID открыть Loki:
{service_name="pulse-api"} | trace_id="<32 hex>"
4. Открыть дочерний postgres.INSERT; проверить длительность, error_code и отсутствие SQL/params.
5. Найти job.process в том же trace; отделить время до старта от времени самой обработки.
6. Если worker-метрики отсутствуют, проверить процесс, Collector logs pipeline и export failures.
7. Если postgres.UPDATE медленный, проверить pool, PostgreSQL блокировки и план в закрытой среде.
8. Ограничить новые задания или вернуть прежнюю совместимую версию; не стирать очередь.
9. Подтвердить восстановление по нескольким сигналам и новым задачам, записать timeline без secrets.
10. Создать действие по причине: индекс, capacity, timeout, lease или исправление потери контекста.
Текущий alerts.yaml вычисляет правила в Prometheus, но не отправляет уведомления:
Alertmanager и contact point нужно настроить отдельно до настоящего выпуска.
Следующий шаг — один trace принятого задания. Если есть SQL INSERT и нет consumer, причина ещё не определена. Задание могло остаться queued; consumer мог потерять контекст; sampling мог отбросить часть пути; exporter мог не доставить span. Проверяем реальную строку PostgreSQL в закрытой среде и сопоставляем безопасные logs worker. Диагноз опирается на несколько независимых фактов, а не на пустой результат одного запроса Tempo.
Если consumer есть и report.compute короткий, а завершающий UPDATE долгий, подозрение переносится на базу. Отдельно смотрят pool timeout, блокировки и схему индексов в разрешённой среде. SQL-параметры не нужно копировать в общую телеметрию. Ограниченный код db_timeout помогает выбрать направление, но не доказывает конкретную причину: timeout может быть следствием блокировки, перегрузки или сетевого отказа.
Если worker многократно получает одну задачу, сравниваем attempt, lease и время работы. Слишком короткий lease создаёт повтор до окончания прежней попытки. Trace покажет несколько consumer-spans, а outcome lease_lost отличит вычисление от принятия результата. Увеличить число worker без понимания такой гонки может усугубить повторную нагрузку. Сначала устанавливаем причину и границу ресурса, затем выбираем изменение capacity.
Ограничение ущерба и подтверждение
При растущей задолженности можно временно ограничить приём новых отчётов либо вернуть прежнюю совместимую версию worker. Нельзя очищать очередь ради красивого графика: это удалит пользовательскую ответственность вместо восстановления сервиса. Действие должно сохранять задания и объяснять пользователю задержку. Возможность rollback зависит от схемы и формата данных, поэтому согласованный выпуск следующего урока важен для оперативной реакции.
После изменения смотрим новые задания, снижение возраста, свежие heartbeat и успешные условные UPDATE. Старое окно ошибок может ещё отображать прежний отказ, а graph retention не изменяет сами задания. Несколько независимых сигналов подтверждают восстановление лучше, чем один зелёный span. При этом реальный incident report должен отделять наблюдённые факты от предположений и описывать, какие доказательства ещё отсутствуют.
Timeline сохраняет время, версию приложения, выбранное действие и результат без токенов, query и пользовательского body. Конкретное последующее действие привязано к причине: исправить индекс, лимит pool, heartbeat lease или доставку контекста. Общая фраза «улучшить мониторинг» не устраняет найденный механизм. Если причине нет доказательства, записывают дальнейшую проверку, а не объявляют гипотезу установленным root cause.
В результате вы понимаете цену выборки и умеете пройти от общего симптома к проверяемой гипотезе API → PostgreSQL → job. Здесь нет вымышленного фактического incident или обещания сохранения всех поздних spans. Последняя глава оформит выпуск так, чтобы версия, настройки sampling, retention и права наблюдения были известны команде заранее, до первого настоящего отказа.
Предыдущий урок: Ошибки и правила оповещения · Следующий урок: Выпуск наблюдаемого сервиса
Оглавление серии · Исходники всех состояний.
Документация: OTel sampling, Tail sampler 0.123.0.