Поиск во время отладки: отдельный жанр
Всё, что было в предыдущих главах, применимо к отладке — но применимо в особом режиме, потому что у отладочного поиска четыре отличия.
Вопрос причинный. Не «как это работает» и не «что разрешено», а «почему у нас конкретно сломалось». Ответа на такой вопрос в интернете нет по определению: там нет ваших данных, вашей версии и вашей конфигурации.
Главный источник — ваша система. Логи, метрики, трассировки, история изменений. Веб может дать механизм и класс причин, но конкретную причину даёт только наблюдение.
Есть давление. Отладка часто идёт под сроком, а иногда под инцидентом. Давление систематически ухудшает качество поиска: сужается внимание, растёт склонность хвататься за первое похожее объяснение.
Цена неправильного ответа выше обычного. Скопированное из интернета «решение», которое изменило симптом, но не причину, — это отложенный повтор инцидента плюс потерянный след.
Нулевой шаг: до первого запроса
Самая частая ошибка — открыть поисковик первым действием. До него надо потратить пять минут и получить факты, без которых любой поиск будет вслепую.
| Вопрос | Зачем |
|---|---|
| Что именно наблюдается? Точная формулировка, без интерпретации | «Тормозит» и «99-й перцентиль вырос втрое» ведут в разные места |
| Когда началось? С точностью до часа, если возможно | Момент начала — самый сильный фильтр из всех |
| Что изменилось перед этим? | Самый мощный вопрос отладки, ему ниже отдельный раздел |
| Воспроизводится ли? И при каких условиях | Невоспроизводимое ищется иначе, чем воспроизводимое |
| У всех или у части? Каких именно | Разделение на «все» и «некоторые» сразу отсекает половину гипотез |
| Работало ли когда-нибудь? | «Никогда не работало» и «перестало работать» — разные расследования |
Эти шесть ответов стоят пять минут и меняют весь дальнейший поиск. В частности, они превращают запрос из «почему сервис отдаёт 500» в «после деплоя такого-то часть запросов к одному эндпоинту падает с таким-то кодом только для пользователей с определённым признаком» — а это уже почти диагноз.
Формулировка наблюдения без интерпретации — тот же приём, что и в первой главе, но здесь он критичнее: интерпретация, попавшая в формулировку, направляет поиск на много часов вперёд.
Порядок действий
что, когда, воспроизводимость,
охват, история"] B --> C{"Что изменилось
перед началом?"} C -->|"нашлось"| D["Проверить это изменение
напрямую: откат, флаг, сравнение"] C -->|"не нашлось"| E["1. Локализовать слой:
наш код / зависимость /
платформа / сеть / данные"] E --> F["2. Внутренние источники:
логи, метрики, трассировки,
история изменений, тикеты"] F --> G{"Есть инвариантная строка
или имя механизма?"} G -->|"нет"| H["Вернуться к наблюдению:
сузить, включить debug-логи"] H --> F G -->|"да"| I["3. Внешние источники:
трекер зависимости, changelog,
advisories, status page"] I --> J{"Известная проблема?"} J -->|"да"| K["Обходной путь или обновление,
записать номер issue"] J -->|"нет"| L["4. Эксперимент:
различающая проверка"] L --> M["Причина установлена"] D --> M K --> M M --> N["След: запись, тест,
постмортем"]
Обратите внимание на порядок: внешний поиск стоит третьим, а не первым. До него надо иметь инвариантную строку из исходников или имя механизма — иначе запрос ищет по вашим уникальным данным и не найдёт ничего.
«Что изменилось» — самый сильный вопрос
Если система работала, а потом перестала, что-то изменилось. Даже если «мы ничего не деплоили».
измениться)) Наш код деплой миграция схемы feature flag конфигурация Зависимости обновление библиотеки обновление образа транзитивная зависимость изменение lock-файла Платформа обновление узлов смена драйвера или ядра квоты и лимиты автоматическое масштабирование Внешний мир API провайдера DNS и маршрутизация сертификаты тарифы и квоты вендора Данные объём вырос появились новые значения распределение сместилось кэш прогрелся или сбросился Время срок действия сертификата ротация ключа или токена TTL и ретеншн cron и календарные границы переход на летнее время
Ветка «Время» стоит отдельного внимания, потому что даёт самый удивительный класс инцидентов: ничего не менялось, а сломалось само. Список подозреваемых, который стоит держать в голове:
- истёк сертификат (TLS, подпись клиента, корневой в образе);
- истёк или ротировался токен, ключ доступа, пароль;
- сработал TTL кэша, и нагрузка ушла на источник;
- закончился ретеншн, и данные, на которые все рассчитывали, удалились;
- наступила календарная граница: конец месяца, конец квартала, обнуление квоты;
- сработал cron, о котором все забыли;
- сменился часовой пояс или произошёл переход на летнее время;
- достигнут порог: закончилось место на диске, номера в последовательности, идентификаторы, файловые дескрипторы.
Практический приём поиска «что изменилось»:
# что деплоилось и когда — по истории репозитория
git log --since='2 days ago' --oneline --all
git log --format='%ci %h %s' -20
# что изменилось в зависимостях: дифф lock-файла информативнее кода
git diff HEAD~5 -- package-lock.json go.sum poetry.lock Cargo.lock | rg '^[-+].*[0-9]+\.[0-9]+'
# что изменилось в конфигурации инфраструктуры
git log -p --since='1 week ago' -- deploy/ helm/ terraform/ | rg '^[-+]' | head -50
# какие образы реально запущены и когда собраны
docker inspect --format '{{.Created}} {{index .RepoTags 0}}' $(docker ps -q)
kubectl get pods -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[*].image,START:.status.startTime'
Дифф lock-файла — недооценённый источник. Обновление транзитивной зависимости не видно в коде и не упоминается в описании изменений, но именно оно часто меняет поведение. Про управление зависимостями — глава в треке принципов.
Поиск внутри своей системы
До веба идёт поиск по тому, что у вас уже есть. Несколько приёмов, которые дают больше всего.
Найти первое вхождение. Не последнее, а самое раннее проявление симптома. Обычно оно отличается от остальных: там ещё нет каскада вторичных ошибок, и видно исходное событие.
# первая ошибка за окно, а не сотая
rg -n 'ERROR|panic|exception' app.log | head -5
# отфильтровать окно вокруг момента начала
rg -n '2026-07-16T1[0-2]:' app.log | rg -i 'timeout|refused|reset'
# структурные логи: считаем, что вообще происходит
jq -r 'select(.level=="error") | .msg' app.jsonl | sort | uniq -c | sort -rn | head -20
# корреляция по идентификатору трассировки
rg -n '"trace_id":"abc123"' *.jsonl | sort
Сравнить хороший период с плохим. Самый сильный приём анализа логов и метрик: не «что происходит сейчас», а «чем сейчас отличается от вчера в это же время». Разница обычно короткая и содержит ответ.
# что появилось в логах, чего не было раньше
jq -r '.msg' bad.jsonl | sort -u > /tmp/bad.txt
jq -r '.msg' good.jsonl | sort -u > /tmp/good.txt
comm -23 /tmp/bad.txt /tmp/good.txt | head -30
Включить то, что выключено. У большинства библиотек есть debug-режим, у клиентов — подробное логирование, у СУБД — логирование медленных запросов. Пять минут на включение часто заменяют час гипотез. Про наблюдаемость — глава про мониторинг.
Когда идти во внешние источники и куда
Внешний поиск при отладке эффективен в четырёх случаях, и только в них.
| Случай | Куда | Что ищете |
|---|---|---|
| Есть инвариантная строка ошибки | поиск по коду, трекер | место, где она печатается, и условие |
| Есть имя механизма | документация, спецификация | контракт и гарантии |
| Подозрение на баг зависимости | трекер проекта, changelog, releases | закрытые issue с вашим симптомом и версия с исправлением |
| Подозрение на проблему провайдера | status page, официальные бюллетени | подтверждение инцидента и его окно |
Первым делом — status page провайдера. Тридцать секунд, и иногда расследование заканчивается: у них инцидент, вы ни при чём. Это правило кажется банальным, но проверяют его далеко не первым.
Поиск в трекере зависимости строится по шаблону: ваш симптом плюс версия, среди закрытых задач. Формулировка запроса разбиралась в главе 2; здесь важно только одно уточнение — обязательно ограничивайте по версии, потому что похожие симптомы в разных мажорных версиях имеют разные причины.
Проверка уязвимостей. Если симптом связан с безопасностью или с внезапным отказом после обновления, посмотрите бюллетени: OSV, GitHub Advisory. Они отвечают точно на вопрос «затронута ли моя версия».
Хронология одного расследования
не «сеть упала», а конкретный отказ И->>Г: что менялось до 14:20 Г-->>И: деплоев не было, конфиг не менялся Note over И: значит, ветка «Время»:
сертификаты, токены, TTL И->>Э: проверить срок сертификата хоста Э-->>И: истёк в 14:19 Note over И: причина установлена
за 12 минут И->>Т: есть ли автоматическое обновление
и почему не сработало Т-->>И: известная проблема с продлением
при определённой конфигурации Note over И: теперь есть и причина,
и системное исправление
Ключевой момент — шаг 6. Обнаружив, что изменений не было, инженер не пошёл искать в вебе «почему 5xx», а перешёл к списку «что меняется само по себе». Это переключение и есть навык.
Поиск во время инцидента: другие правила
Когда система лежит, приоритеты меняются: сначала митигация, потом причина. Отладочный поиск в этом режиме подчиняется дополнительным правилам.
- Сначала вернуть работоспособность. Откат, переключение, отключение функции. Поиск причины после.
- Ограничьте поиск жёстким таймбоксом — минуты, а не часы. Если за пять минут не нашлось, применяйте общий обходной путь.
- Не меняйте несколько вещей сразу. Под давлением это происходит само, а потом невозможно понять, что помогло.
- Записывайте хронологию по ходу. Не после. Через час вы не вспомните порядок событий, а он и есть материал для разбора.
- Один человек ищет, другой митигирует. Совмещение ролей приводит к тому, что не делается ни то, ни другое.
- Не копируйте команды из интернета в продакшн. Особенно те, что «сбрасывают состояние».
Полный разбор процесса — Реагирование на инциденты и Постмортемы.
Что искать бесполезно
- «Почему у нас падает X». У «нас» нет представительства в индексе.
- Полный текст вашего стека вызовов. Он уникален; ищите одну инвариантную строку.
- Симптом без версии. Найдёте чужую проблему с похожим проявлением и другой причиной.
- Решение до диагноза. Запрос «как исправить ошибку N» приводит к набору чужих обходных путей; часть из них ухудшит ваше состояние.
- Подтверждение уже выбранной гипотезы. Если вы ищете, чтобы убедиться, вы найдёте — и это ничего не значит.
Невоспроизводимая проблема: отдельный подрежим
Самый тяжёлый класс: «иногда падает», «раз в неделю», «только у одного клиента». Здесь обычный поиск не работает, потому что нет устойчивого наблюдения, которое можно описать.
Порядок действий другой:
- Сначала добиться наблюдаемости, а не причины. Пока событие не фиксируется, искать нечего. Добавьте логирование в подозрительный путь, включите сэмплирование трасс, поставьте счётчик.
- Собрать статистику проявлений. Когда именно? У кого? На каком узле? После чего? Часто уже эта таблица содержит ответ: все случаи оказываются на одной реплике, или все — в первые минуты после запуска, или все — у клиентов с определённой версией.
- Искать корреляцию, а не совпадение. Признак, встречающийся во всех случаях и отсутствующий в остальных, — это гипотеза. Признак, встречающийся во всех случаях и вообще везде, — нет.
- Проверить границы и редкие ветки. Невоспроизводимое почти всегда живёт в местах, где сходятся конкурентность, время, ошибки сети или необычные данные.
- Только потом внешний поиск — по имени механизма, который вы заподозрили, плюс слова
race,flaky,intermittent,under load.
Отдельно про флаки-тесты: они тоже относятся к этому режиму, и у них есть своя литература и свои приёмы фиксации (повторные прогоны с записью, детерминированные сиды, изоляция состояния). Про устройство тестов в конвейере — Тесты в CI.
Ловушки отладочного поиска
Похожее сообщение — другая причина. Одна строка ошибки может печататься в десятке разных условий. Прежде чем принять чужое объяснение, проверьте, что у вас выполнено то же условие.
Изменение симптома вместо устранения причины. Увеличенный таймаут убирает ошибку и оставляет проблему. Признак: вы не можете объяснить, почему помогло.
Каскад. Первая ошибка порождает сотню вторичных, и поиск идёт по вторичным. Отсюда правило «искать первое вхождение».
Туннельное зрение под давлением. Первая правдоподобная гипотеза захватывает внимание целиком. Противоядие простое и работает: вслух назвать три гипотезы, прежде чем проверять первую.
Разведка вместо наблюдения. Полчаса чтения там, где включение debug-лога дало бы ответ за две минуты. Про баланс — предыдущая глава.
Что оставить после расследования
Отладка — жанр, где след окупается быстрее всего, потому что похожие поломки повторяются. Минимум, который стоит оставить каждый раз:
- Скрипт или тест, воспроизводящий проблему. Он же — доказательство того, что вы починили именно её.
- Одна строка в коде рядом с исправлением: почему так, со ссылкой на issue или на источник.
- Запись о причине с датировкой: какая версия, какое условие, что именно изменилось.
- Тупики: где искали и не нашли. Следующий человек начнёт с тех же трёх мест.
Подробно про формы следа — следующая глава.
Типичные ошибки
- Открывать поисковик первым действием. Сначала шесть вопросов нулевого шага.
- Не спрашивать «что изменилось». Самый сильный вопрос, и он бесплатный.
- Забывать про ветку «время». Сертификаты, токены, TTL и календарные границы ломают системы без всяких изменений.
- Искать по последней ошибке вместо первой.
- Не смотреть status page провайдера. Тридцать секунд, иногда закрывает вопрос целиком.
- Искать без ограничения по версии.
- Менять несколько вещей одновременно.
- Не записывать хронологию. После инцидента её уже не восстановить.
Мини-итог
- Отладочный поиск — причинный: ответа «вообще» не существует, главный источник — ваша система.
- Нулевой шаг: что наблюдается точно, когда началось, что менялось, воспроизводимо ли, у кого, работало ли раньше.
- «Что изменилось» — самый сильный вопрос; если ответ «ничего», работает ветка «время»: сертификаты, токены, TTL, календарь, пороги.
- Внутренние источники идут до внешних: логи, метрики, трассировки, история изменений, дифф lock-файла.
- Ищите первое вхождение, а не последнее, и сравнивайте плохой период с хорошим.
- Внешний поиск эффективен в четырёх случаях: есть инвариантная строка, есть имя механизма, подозрение на баг зависимости, подозрение на инцидент провайдера.
- Во время инцидента митигация идёт раньше причины, таймбокс жёсткий, изменения по одному, хронология пишется по ходу.
- Похожее сообщение об ошибке не означает ту же причину; проверяйте условие.
Источники
- Andreas Zeller. Why Programs Fail: A Guide to Systematic Debugging (2-е изд., 2009) — систематический подход к поиску причин, включая дельта-отладку.
- David J. Agans. Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems (2002).
- Brendan Gregg. Systems Performance (2-е изд., 2020) — методики USE и другие подходы к локализации узкого места.
- Betsy Beyer et al. (ред.). Site Reliability Engineering (Google, O’Reilly, 2016), глава «Effective Troubleshooting» — sre.google/sre-book/effective-troubleshooting.
- OSV — база уязвимостей с точным указанием затронутых версий: osv.dev.
Что дальше
Расследование закончено. Осталось сделать так, чтобы через год его не пришлось повторять. Что делать с найденным: воспроизводимый след.