Files
progcode/editorial/agent-rewrites/036.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 36,
"slug": "editorial-2027-01-practice-debugging-decade",
"title": "502, пустой экран, отказ: как проверить причину по сигналам",
"excerpt": "Практический маршрут от наблюдаемого web-симптома к проверяемой гипотезе: что сохранить, где искать разрыв и когда исправление действительно готово.",
"contentHtml": "<p>Пользователь открывает страницу, а получает 502. Или видит пустой экран после ответа 200. Или форма отвечает 403, хотя доступ должен быть разрешён. В каждом случае команда быстро называет причину: «упал сервис», «сломался фронтенд», «протух токен». Если первая версия неверна, инженер меняет не тот слой, стирает исходный сигнал и тратит часы на новый симптом. Для пользователя это недоступная операция. Для команды — лишний релиз, повторный инцидент и решение, которое трудно откатить.</p><p>Тезис этой статьи простой: диагностика должна идти от наблюдаемого эффекта к различающему сигналу, а не от любимого инструмента к предполагаемому виновнику. Сначала сохраните конверт запроса. Затем назовите две правдоподобные причины. Для каждой запишите проверку, которая может её опровергнуть. Только после этого меняйте конфигурацию или код.</p><h2>Что именно наблюдает клиент</h2><p>HTTP-статус описывает ответ на одной границе. Статус 502 означает, что шлюз или прокси получил недействительный ответ от вышестоящего сервера. Он не сообщает, разорвалось ли соединение, истёк ли таймаут, не совпал ли маршрут или приложение вернуло неожиданные данные. Статус 504 говорит о таймауте шлюза, но тоже не указывает, на каком участке закончился бюджет времени. Поэтому запись «получили 502» — факт, а не диагноз.</p><p>Пустой экран требует такой же дисциплины. Браузер мог получить пустой HTML. JavaScript мог не выполнить рендер. API мог вернуть пустой массив по корректному условию. Компонент мог скрыть ошибку и вывести контейнер без содержимого. Все четыре случая выглядят похоже на скриншоте. Различают их тело ответа, ошибки консоли, сетевые события и фактически построенный DOM.</p><p>Сохраните исходный сигнал до повтора и до исправления. Минимальный конверт содержит метод, путь, статус, время, длительность, размер ответа, content-type, идентификатор запроса и границу наблюдения. Для браузерного симптома добавьте URL документа, статус API, ошибку консоли и результат проверки DOM. Эти поля не доказывают причину. Они ограничивают поиск и показывают, какого сигнала пока не хватает.</p><figure><img src=\"/assets/editorial/2027/debugging-decade-2027-evolution-timeline.svg\" alt=\"Маршрут диагностики от web-симптома к проверяемому сигналу\" loading=\"lazy\" /><figcaption>Каждый переход от симптома к действию должен добавлять наблюдаемый признак. Иллюстрация показывает маршрут, а не готовый диагноз.</figcaption></figure><h2>Механизм: гипотеза должна различать причины</h2><p>Формулируйте гипотезу в условной форме: «если причина X, то при проверке Y увидим Z». Такая запись заранее допускает отрицательный результат. Например: если gateway не получил ответ приложения, в access log будет 502, а в application log не будет события с тем же идентификатором. Если приложение вернуло ошибку, обе записи появятся в одном временном окне, а статусы и длительности будут различаться.</p><p>Идентификатор запроса связывает записи, но не доказывает причинность. Он может потеряться на границе, повториться из-за ошибки интеграции или не попасть в sampled trace. Совпадение времени тоже не является связью: параллельные запросы имеют похожие отметки, а часы узлов могут расходиться. Если ключа нет, это результат проверки — «цепочка не связана», — а не разрешение взять ближайшую запись.</p><p>Для распределённого маршрута полезно различать три вида данных. Log показывает сообщение и локальное состояние процесса. Span показывает границу операции, родителя и длительность. Metric показывает агрегат по множеству запросов. Trace ID помогает найти общий контекст. Ни один из этих сигналов в одиночку не отвечает на все вопросы. Длинный span не обязательно является причиной задержки. Ошибка в метрике не доказывает ошибку конкретного запроса.</p><h2>Учебный пример: классифицировать вход, не объявляя root cause</h2><p>Ниже — локальный учебный пример. Он не обращается к сети, не читает production-логи и не утверждает, что найден источник ошибки. Функция только выбирает следующий сигнал по форме ответа. Автоматизация может упорядочить проверку, но не может выдать доказательство из отсутствующих данных.</p><pre><code>function classifyWebSymptom(input) {&#10; const status = Number(input?.status);&#10; const body = String(input?.body ?? '');&#10; const headers = Object.fromEntries(Object.entries(input?.headers ?? {}).map(([key, value]) =&gt; [key.toLowerCase(), String(value)]));&#10; if (status === 502 || status === 504) return headers.traceparent || headers['x-request-id'] ? 'связать gateway и upstream по идентификатору' : 'включить идентификатор на границе';&#10; if (status === 401 || status === 403) return 'сверить аутентификацию, авторизацию и политику доступа';&#10; if (status === 200 &amp;&amp; /empty|blank|undefined/i.test(body)) return 'сравнить тело ответа API с фактическим DOM';&#10; return 'сохранить метод, путь, статус, размер и время';&#10;}&#10;console.log(classifyWebSymptom({ status: 502, headers: { traceparent: '00-abc-123-01' }, body: 'Bad Gateway' }));&#10;// связать gateway и upstream по идентификатору</code></pre><p>Вход с 502 и traceparent ведёт к сопоставлению записей на двух границах. Вход с 502 без идентификатора ведёт к исправлению наблюдаемости, а не к перезапуску приложения. Вход с 200 и пустым содержимым ведёт к сравнению ответа, ошибок рендера и DOM. Вход 403 ведёт к проверке схемы аутентификации и решения авторизации. Эти маршруты не заменяют расследование. Они не дают функции права выбрать базу данных, прокси или браузер виновником.</p><h2>Симптом → причина → проверка → действие</h2><div class=\"table-scroll\"><table><caption>Минимальные развилки для первого прохода</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Возможная причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>502 на границе</td><td>gateway не получил корректный ответ upstream</td><td>Сопоставить request-id или traceparent; проверить запись приложения в том же окне</td><td>Разделить отказ до приложения и ошибку приложения; затем исправлять найденный слой</td></tr><tr><td>504 после фиксированного интервала</td><td>Бюджет времени закончился на gateway, клиенте или зависимости</td><td>Сравнить таймауты границ и длительности span-ов</td><td>Найти участок, который исчерпал бюджет; не добавлять повтор вслепую</td></tr><tr><td>200 и пустой экран</td><td>пустые данные, ошибка рендера или пустой HTML</td><td>Сопоставить response body, console error и DOM</td><td>Исправить контракт данных или рендер; отдельно проверить fallback</td></tr><tr><td>403 для ожидаемого пользователя</td><td>решение политики не совпало с контекстом доступа</td><td>Проверить токен, claims, scope, ресурс и версию политики</td><td>Исправить конкретное условие; не ослаблять всю политику</td></tr><tr><td>Запись есть только в одном слое</td><td>потеря корреляции, sampling или отказ до следующей границы</td><td>Проверить формат полей, перенос заголовка и временное окно</td><td>Отметить разрыв как результат; добавить сигнал перед повтором</td></tr></tbody></table></div><h2>Порядок действий</h2><ol><li>Запишите симптом без объяснения: метод, путь, статус, время, длительность, размер ответа, content-type и границу, на которой увидели ответ.</li><li>Сформулируйте минимум две причины. Для каждой запишите наблюдение, которое должно появиться, и наблюдение, которое её опровергнет.</li><li>Проверьте внешний access log и журнал следующего слоя в одном временном окне. Сопоставляйте записи по идентификатору, а не только по пути и секунде.</li><li>Разделите синхронный и асинхронный путь. Для очереди или фоновой задачи найдите отдельную связь между producer, сообщением и consumer.</li><li>Проверьте отрицательный путь: запрос без токена, просроченный токен, отсутствующий parent span, пустой ответ и превышение таймаута.</li><li>Внесите одно изменение в найденном слое. Повторите тот же сценарий с теми же входами и сравните исходный конверт с новым.</li><li>Сохраните проверку рядом с исправлением: тест, запрос для воспроизведения, структурированный лог или короткую операционную инструкцию.</li></ol><h2>Ограничения</h2><p>Этот маршрут не восстанавливает данные, которых система не записала. Если gateway не переносит идентификатор, связь нельзя честно реконструировать по одному времени. Если trace sampling отбросил span, отсутствие span не означает отсутствие работы. Если прокси переписал статус или тело, нужно искать его access log и правила маршрутизации. Если несколько запросов выполняются параллельно, порядок строк в журнале не равен порядку причин.</p><p>Учебный классификатор не подходит как готовое правило блокировки или маршрутизации. Его ветки намеренно грубые. В реальной системе нужно учесть редиректы, retries, кеш, CDN, разные схемы авторизации и версию контракта API. Не добавляйте повторные попытки только потому, что ответ медленный: retry может увеличить нагрузку и скрыть первичный отказ. Не меняйте таймаут, пока не измерили бюджет на каждой границе.</p><p>Статус 502 или 504 также не доказывает, что downstream был недоступен. Причина может быть в несовместимом формате ответа, неверном DNS, закрытом соединении или ограничении шлюза. Статус 403 не доказывает, что пользователь «не имеет доступа» в бизнес-смысле: решение могло использовать устаревшие claims или другую версию политики. Проверяйте именно тот контекст, который использовал компонент.</p><h2>Проверяемый критерий готовности</h2><p>Диагностика готова, когда другой инженер получает исходный конверт и может без устных пояснений назвать запрос, границу и следующий сигнал. Для исправления нужен ещё один результат: тот же сценарий проходит по ожидаемому пути, а отрицательный вариант по-прежнему получает правильный отказ. В журнале остаются идентификатор, статус, длительность и причина завершения. Если повтор только «стал зелёным», но различающий сигнал не сохранился, причина не доказана.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9110: HTTP Semantics</a> — официальная семантика HTTP и статусы 401, 403, 502 и 504. Документ описывает протокол, но не устанавливает причину конкретного отказа.</li><li><a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Trace Context</a> — формат и перенос контекста трассировки между сервисами. Спецификация помогает связать запросы, но не гарантирует полноту трассы и причинность.</li></ul>"
}