8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"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) =>\n event.requestId === edgeEvent.requestId\n && event.attempt === edgeEvent.attempt\n );\n\n if (!match) return 'application-event-not-found';\n if (match.status >= 500) return 'application-error-observed';\n return 'application-success-edge-failure-needs-check';\n}\n\nconsole.log(edgeEvents.map((event) => ({\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>"
|
||
}
|