Files

8 lines
17 KiB
JSON
Raw Permalink 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": 34,
"slug": "editorial-2027-01-field-debugging-decade",
"title": "502 без догадок: как восстановить цепочку запроса по логам",
"excerpt": "Практический разбор 502 по access и application log: как установить границу отказа, проверить корреляцию по request ID и не объявить приложение причиной без подтверждения.",
"contentHtml": "<p>Клиент получает 502, а в журнале приложения не находится запись с тем же запросом. Самый дорогой ответ в этой ситуации — открыть последний релиз и начать исправлять код. 502 мог сформировать proxy после таймаута, ошибки соединения или недействительного ответа upstream. Могла потеряться и сама запись: sampling, collector или неверное поле корреляции оставляют ту же картину.</p>\n<p>Цена неверной атрибуции измеряется не только часами. Команда откатывает исправный релиз, повышает таймаут без проверки границы или добавляет повторные запросы к уже перегруженной зависимости. Следующий дежурный получает уверенную формулировку «упало приложение» и повторяет тот же маршрут расследования.</p>\n<p>Надёжный разбор начинается с вопроса «кто сформировал этот статус?». Сначала фиксируем событие на границе, затем связываем его с попыткой в приложении по идентификатору. Только найденное и согласованное событие разрешает перейти к зависимости. Если запись не найдена, это результат наблюдения, а не доказательство того, что запрос не дошёл.</p>\n<h2>502 описывает границу, а не виновника</h2>\n<p>RFC 9110 определяет 502 как статус, который сервер в роли gateway или proxy возвращает после недействительного ответа от входного сервера, к которому он обращался для выполнения запроса. В этой формулировке нет имени конкретного виноватого сервиса. Статус сообщает о проблеме на участке между посредником и upstream либо о том, что посредник не смог принять его ответ.</p>\n<p>Отделяйте 502 от 504. 502 говорит о недействительном ответе, а 504 — об отсутствии своевременного ответа от upstream или другой вышестоящей системы. На практике конкретный proxy может использовать собственные детали диагностики и маппинг ошибок, поэтому RFC объясняет семантику статуса, но не заменяет документацию вашего узла.</p>\n<p>В access-событии зафиксируйте субъект результата: <code>edge.status=502</code>, <code>edge.name</code> и <code>upstream.name</code>, если последний известен. Добавьте <code>route</code>, <code>method</code>, точное время, <code>duration_ms</code>, <code>request_id</code>, <code>trace_id</code> и <code>attempt</code>. Набор полей не универсален, но без него внешний лог показывает симптом и почти не помогает выбрать следующий запрос.</p>\n<h2>Сначала докажите саму связь</h2>\n<p>Для одной попытки нужны как минимум access event на границе и application event в сервисе. Ищите по точному <code>request_id</code> или по корректному trace context. Маршрут и временное окно — вторичные признаки: два одинаковых запроса могут прийти одновременно, а часы сервисов могут иметь небольшой сдвиг.</p>\n<p>W3C Trace Context задаёт формат заголовка <code>traceparent</code> для передачи идентификаторов между HTTP-границами. Это полезный транспорт связи, но не обещание полной записи: sampled-флаг не гарантирует, что трасса будет сохранена, а промежуточный узел может создать новый контекст при невалидном входе. Поэтому проверяйте и сам заголовок, и фактическое событие в каждом важном слое.</p>\n<p>Корреляция считается подтверждённой, если совпали не только ID, но и операция: маршрут, время, номер попытки и ожидаемый слой. Одного одинакового ID мало. При retry ищите дочерние span или отдельные значения <code>attempt</code>; иначе можно принять ответ первой попытки за результат второй.</p>\n<figure><img src=\"/assets/editorial/2027/debugging-decade-2027-hypothesis-evidence-loop.svg\" alt=\"Диаграмма восстановления цепочки 502: edge-статус, гипотеза, request ID, проверка и разрыв коллектора\" loading=\"lazy\" /><figcaption>Идентификатор направляет поиск от внешнего симптома к событию приложения, но причинность требует проверки времени, статуса и границы операции.</figcaption></figure>\n<h2>Четыре наблюдаемые исхода</h2>\n<div class=\"table-scroll\"><table><caption>Матрица первой проверки для одной попытки 502</caption><thead><tr><th scope=\"col\">Что найдено</th><th scope=\"col\">Что это подтверждает</th><th scope=\"col\">Что ещё не доказано</th><th scope=\"col\">Следующий запрос</th></tr></thead><tbody><tr><td>Edge 502; application event отсутствует</td><td>На границе зафиксирован 502</td><td>Неизвестно, дошёл ли запрос до приложения</td><td>Проверить timeout, маршрут, collector и формат ID</td></tr><tr><td>Edge 502; application 500 с тем же ID и attempt</td><td>Приложение обработало эту попытку с ошибкой</td><td>Неизвестна первопричина внутри приложения или зависимости</td><td>Сверить dependency event, длительность и лимиты</td></tr><tr><td>Edge 502; application 200 с тем же ID</td><td>Приложение завершило свою операцию успешно</td><td>Не объяснено расхождение с клиентским статусом</td><td>Проверить retry, cache и mapping ответа на proxy</td></tr><tr><td>В edge нет request ID</td><td>Схема наблюдения неполна</td><td>Нельзя надёжно связать слои по времени</td><td>Исправить генерацию и передачу ID до следующего разбора</td></tr></tbody></table></div>\n<p>У таблицы есть важная асимметрия. Строка «application event отсутствует» допускает несколько причин: запрос мог остановиться до приложения, запись могла не попасть в хранилище, а идентификатор мог измениться по дороге. Поэтому формулировка должна оставаться отрицательной: «событие не найдено в проверенном источнике и окне», а не «приложение не получило запрос».</p>\n<h2>Воспроизводимая классификация без ложной причинности</h2>\n<p>Ниже — самостоятельный пример на JavaScript. События вымышлены и нужны только для проверки корреляции. Функция не пытается угадать первопричину: она различает наличие согласованного application event и оставляет отдельный статус для отсутствующей записи.</p>\n<pre><code>const edgeEvents = [\n { requestId: 'r-1', attempt: 1, status: 502, durationMs: 3000 },\n { requestId: 'r-2', attempt: 1, status: 502, durationMs: 420 },\n];\n\nconst applicationEvents = [\n { requestId: 'r-2', attempt: 1, status: 500, durationMs: 180, error: 'dependency unavailable' },\n];\n\nfunction classify(edgeEvent, appEvents) {\n const match = appEvents.find((event) =&gt;\n event.requestId === edgeEvent.requestId\n &amp;&amp; event.attempt === edgeEvent.attempt\n );\n\n if (!match) return 'application-event-not-found';\n if (match.status &gt;= 500) return 'application-error-observed';\n return 'application-success-edge-failure-needs-check';\n}\n\nconsole.log(edgeEvents.map((event) =&gt; ({\n requestId: event.requestId,\n result: classify(event, applicationEvents),\n})));\n// r-1: application-event-not-found\n// r-2: application-error-observed</code></pre>\n<p>Для <code>r-1</code> результат ограничен: в переданном массиве нет совпадения по двум полям. Он не различает timeout, потерю записи и ошибку маршрутизации. Для <code>r-2</code> наблюдается application 500 с теми же ID и попыткой. Это подтверждает обработку запроса приложением, но не доказывает, что строка <code>dependency unavailable</code> — первопричина; её надо сопоставить с журналом зависимости.</p>\n<p>В рабочей системе добавьте проверку схемы до классификации: ID не должен быть пустым, <code>attempt</code> — неотрицательным целым, а время — разбираться однозначно. Храните число найденных событий и источник поиска. Не помещайте в общий лог токены, тело формы, email или сырые заголовки; для закрытой корреляции используйте разрешённый идентификатор и действующие правила хранения.</p>\n<h2>Порядок расследования</h2>\n<ol><li>Сохраните одну карточку запроса из edge log: время с часовым поясом, route, method, status, duration, request ID, trace ID и attempt.</li><li>Определите узел, который записал 502, и зафиксируйте выбранный им upstream. Не называйте приложение причиной только по URL.</li><li>Найдите application events по точному ID в узком окне. Запишите источник, диапазон времени и количество совпадений.</li><li>Сверьте route, время, attempt, статус и длительность. Отдельно отметьте retry, очередь и clock skew.</li><li>Если application event согласован, проверьте зависимость: её ID операции, таймаут, ответ, число попыток и лимит соединений.</li><li>Если event не найден, отдельно проверьте путь до приложения, правила маршрутизации, collector, sampling и преобразование ID.</li><li>Сформулируйте вывод в двух строках: наблюдение и следующий тест. Например: «edge 502; application event не найден в окне 12:00:00–12:00:05; проверить timeout и collector».</li><li>После изменения повторите безопасный запрос с тем же набором полей и сравните положительный и отрицательный пути.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Метод требует хотя бы одного надёжного события на границе. Если access log сам неполон, расследование начинается с восстановления его схемы, а не с чтения application stack trace. Sampling, буферизация и задержка доставки могут удалить или переставить события. Близкое время не заменяет ID, а найденный ID не гарантирует полноту цепочки.</p>\n<p>Retry меняет картину. Прокси может повторить запрос, приложение — создать новый span, а пользователь — отправить его ещё раз. Один request ID иногда живёт дольше одной попытки, иногда меняется на границе. Нужны <code>attempt</code>, span ID и правила, по которым именно ваша система связывает повторы.</p>\n<p>Структурированный лог повышает разбираемость, но не делает данные истинными автоматически. RFC 5424 описывает structured data как parseable-формат и допускает, что collector проигнорирует некорректный элемент. Это означает практическую границу: схему полей надо тестировать на реальном транспорте, а не только на примере конфигурации.</p>\n<p>Разбор одной карточки не заменяет анализ нагрузки. Если 502 появляется только при насыщении пула, нужны распределение задержек, число retry, состояние очередей и лимиты соединений. Если проблема связана с TLS, DNS, HTTP/2 или конкретным форматом ответа, потребуется проверка соответствующего протокола. Приведённый алгоритм выбирает границу следующего теста, но не обещает одну причину для всех 502.</p>\n<h2>Критерий готовности</h2>\n<p>Расследование можно закрывать, когда для повторённой попытки показаны access event и application event либо явно зафиксирован разрыв наблюдения; совпадают маршрут, время и attempt; зависимость проверена там, где это разрешает цепочка; назван следующий измеримый результат. Исправление подтверждено только после повторного запроса: ожидаемый статус получен, цепочка снова связывается по ID, а отрицательный путь остаётся различимым.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html#section-15.6.3\" target=\"_blank\" rel=\"noopener\">RFC 9110, раздел 15.6.3: определение 502 Bad Gateway</a></li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html#section-15.6.4\" target=\"_blank\" rel=\"noopener\">RFC 9110, раздел 15.6.4: определение 504 Gateway Timeout</a></li><li><a href=\"https://www.rfc-editor.org/rfc/rfc5424.html#section-6.3\" target=\"_blank\" rel=\"noopener\">RFC 5424, раздел 6.3: structured data в syslog</a></li><li><a href=\"https://www.w3.org/TR/trace-context/\" target=\"_blank\" rel=\"noopener\">W3C Trace Context: traceparent и модель передачи контекста</a></li></ul>"
}