Request ID (идентификатор запроса)
Request ID (идентификатор запроса)
Request ID (идентификатор запроса) — короткая уникальная строка, которая присваивается одному запросу в момент входа в систему и передаётся вместе с ним через все сервисы, очереди и фоновые задачи. По ней потом собирают из логов и ответов одну связную историю: кто отправил, кто принял, где сломалось.
История
Идея появилась не в вебе, а в интеграции корпоративных систем, когда выяснилось, что асинхронные сообщения нельзя сопоставить с ответами «на глаз».
- 2003, США. Грегор Хоуп (Gregor Hohpe) и Бобби Вулф (Bobby Woolf) выпускают книгу «Enterprise Integration Patterns» — каталог паттернов интеграции. Среди них паттерн Correlation Identifier (идентификатор корреляции): если ты отправил десять сообщений в очередь и получил десять ответов в произвольном порядке, нужен общий признак, чтобы понять, какой ответ к какому запросу относится. Это буквально прародитель request id.
- 2010, Google. Выходит статья «Dapper, a Large-Scale Distributed Systems Tracing Infrastructure» — описание внутренней системы трассировки Google. Оттуда приходит связка trace id + span id: один общий идентификатор на всю цепочку и отдельный — на каждый шаг внутри неё. Статья стала идейным фундаментом почти всего, что потом появилось в open source.
- 2012, Twitter. Компания открывает исходники Zipkin, системы трассировки, вдохновлённой Dapper. Вместе с ней появляются заголовки семейства B3 (
X-B3-TraceId,X-B3-SpanId,X-B3-ParentSpanId,X-B3-Sampled) — де-факто стандарт передачи идентификатора между сервисами на несколько лет. - 2016–2019. Появляются OpenTracing (инициатива Бена Сигелмана, тогда LightStep) и OpenCensus (Google, 2018). В 2019-м они сливаются в OpenTelemetry — проект под крылом CNCF, который сегодня и является мейнстримом по сбору трассировок и метрик.
- 2021. W3C публикует Trace Context как Recommendation — заголовки
traceparentиtracestate. Впервые сквозной идентификатор получил официальный, а не «так исторически сложилось» формат. - 2024. RFC 9562 описывает UUIDv7 — UUID, у которого старшие биты содержат метку времени. Для request id это удобно: идентификаторы монотонно растут и хорошо сортируются в базе логов.
Отдельно про сам заголовок X-Request-ID: это де-факто конвенция, а не стандарт. В IETF был черновик draft-ietf-httpapi-request-id, но до RFC он, насколько я знаю, не дошёл. Поэтому в разных компаниях живут X-Request-ID, X-Correlation-ID, Request-Id, X-Amzn-RequestId, cf-ray — и это одна из главных практических болей.
Что это такое
Request id — это не способ авторизации, не кэш-ключ и не защита от повторов. Это метка «это всё про один и тот же запрос», которую удобно положить в каждую строчку лога.
Разберём пары, которые чаще всего путают.
Request ID vs Trace ID. Request id — идентификатор запроса, то есть события «клиент пришёл». Trace id — идентификатор трассы, то есть всего распределённого сценария, который этот запрос породил. В простом случае это одно и то же значение, и так обычно и делают: берут request id и объявляют его trace id. Разница проявляется, когда один входящий запрос порождает несколько независимых ветвей (например, fan-out на десять сервисов): трасса одна, а внутри неё десятки span id — по одному на каждый шаг.
Request ID vs Idempotency Key. Idempotency key (ключ идемпотентности) — это идентификатор намерения: «списать деньги по этому заказу ровно один раз». Он живёт дольше запроса и переживает повторные отправки: если клиент повторил запрос с тем же ключом, сервер вернёт сохранённый результат, а не выполнит операцию второй раз. Request id, наоборот, уникален для каждой попытки: две попытки — два разных request id. Это разные поля, и подменять одно другим — классическая ошибка.
Request ID vs Session ID. Session id живёт часами и объединяет много запросов одного пользователя. Request id живёт секунды и объединяет много сервисов внутри одного запроса. Они ортогональны: полезно логировать оба.
Request ID vs бизнес-ключ. order_id, invoice_id, user_id — это доменные сущности, они существуют независимо от запроса. Request id нужен там, где бизнес-ключа ещё нет (запрос упал до создания заказа) или где бизнес-ключей несколько.
Главное свойство request id — он должен быть дешёвым и неинтересным. 16–32 символа, без смысла, без персональных данных. Если в идентификаторе зашит e-mail клиента или номер договора — это уже утечка, а не идентификатор.
Аналогии из жизни
1. Трек-номер посылки. Отправил посылку — получил номер вида RU123456789. Дальше и магазин, и склад, и курьер, и ты сам смотрите статус по одному и тому же номеру, хотя системы у всех разные.
Где ломается: при передаче между перевозчиками номер часто меняется — у Почты свой, у курьерской службы свой, и склейка происходит «через накладную», которую кто-то должен ввести руками. В IT это ровно та же проблема: если сервис на границе не пробросил заголовок дальше, вся цепочка рассыпается на несвязанные куски. И ещё: трек-номер говорит, где посылка, но не говорит, что внутри — так и request id сам по себе не объясняет бизнес-смысл запроса.
2. Номер заказа в большой кухне. Ты платишь на кассе, получаешь номер 47. Кассир пробил, повар приготовил, бармен налил — и всё это один заказ, потому что номер прошёл через все три точки.
Где ломается: если кассир не передал номер на кухню (а в реальности это случается — «пробей без номера»), то чек и тарелка оказываются не связаны, и разбираться приходится по времени и по памяти. В IT это забытый headers: {} при проксировании — самая частая причина, почему «трассировка не работает». Второе ограничение: один заказ легко распадается на несколько тикетов (кофе отдельно, еда отдельно), и тогда номер один, а записей много — как один trace id с несколькими span id.
3. Багажная бирка при пересадке. В аэропорту на сумку клеят бирку с номером; при пересадке её считывают, и багаж уезжает в нужный самолёт, а если потерялся — его ищут по номеру, а не по описанию «чёрный чемодан».
Где ломается: при переходе на другую авиакомпанию бирку не всегда считывают, и сумка «теряет историю». Кроме того, бирка привязана к вещи, а не к человеку: если ты летишь с двумя чемоданами, номеров два, и «мой рейс» и «мой багаж» — не одно и то же. Так и request id привязан к запросу, а не к пользователю.
Как это работает
Пошагово, на примере запроса, который проходит через три сервиса.
клиент --> edge/gateway --> service A --> service B --> БД
| генерирует | пробрасывает | пробрасывает
| request id | в заголовке | в заголовке
v v v
log: rid=7f3a.. log: rid=7f3a.. log: rid=7f3a..
Шаг 1. Решение, кто генерирует. Правило простое: генерирует первый, кто увидел запрос извне — обычно это edge-прокси, API-гейтвей или сам сервис, если он без прокси. Если клиент прислал свой идентификатор — его принимают, но с осторожностью (см. callout ниже). Внутренние сервисы никогда не генерируют новый id, они только пробрасывают полученный.
Шаг 2. Формат. Вариантов три.
- UUIDv4 — 128 бит случайности, вид
f47ac10b-58cc-4372-a567-0e02b2c3d479. Просто, есть везде, но не сортируется по времени и плохо дружит с индексами в логовых базах (вставки «в случайные места»). - UUIDv7 — тот же UUID, но первые 48 бит — миллисекунды. Сортируется по времени, индексы работают лучше. Сегодня это разумный дефолт.
- Snowflake-подобные — 64-битное число: метка времени + номер машины + счётчик. Придумано в Twitter около 2010 года. Компактно (можно влезть в
bigint), но требует аккуратной настройки, чтобы две машины не выдали одинаковый id.
Плюс почти всегда — hex-строка фиксированной длины (16 или 32 символа): её удобно резать глазами и она не содержит спецсимволов.
Шаг 3. Передача. Везде, где есть граница между процессами, нужен носитель:
- HTTP — заголовок (
X-Request-ID,traceparent). - gRPC — metadata (те же ключ-значение, только в бинарном протоколе).
- Очереди (Kafka, RabbitMQ, SQS) — поле в заголовках сообщения, а не в теле. Важно: если в теле, то продюсер и консьюмер должны договориться о схеме, а заголовки обычно уже есть.
- Фоновые задачи (cron, воркеры) — id кладут в параметр задачи или в поле таблицы
jobs, чтобы «поздний» обработчик знал, к чему относится.
Шаг 4. Логирование. В каждом сервисе request id попадает в структурированный лог — JSON-строку, а не «текст с пробелами». В большинстве фреймворков для этого есть готовый механизм: MDC (Mapped Diagnostic Context) в мире Java/slf4j, contextvars в Python, AsyncLocalStorage в Node.js, context.Context в Go. Смысл один: положить id в контекст один раз — и он автоматически появится в каждой строчке лога, включая те, что пишут библиотеки.
Шаг 5. Возврат клиенту. Идентификатор отдают обратно — и в заголовке ответа, и (обязательно!) в теле ошибки. Это то, из-за чего вообще стоит вся возня: клиент видит «внутренняя ошибка», копирует id и присылает в поддержку, а инженер за один запрос в логовой системе находит конкретную цепочку. Именно так выглядит ответ любого крупного API: поле request_id рядом с error.
Шаг 6. Трассировка (опционально, но обычно да). К request id добавляют span id — идентификатор конкретного шага — и parent span id — ссылку на шаг-родителя. Получается дерево. Формат W3C traceparent выглядит так:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
| | | |
| | | + флаги (сэмплировать или нет)
| | + span id (текущий шаг)
| + trace id (общий на всю цепочку)
+ версия формата
Шаг 7. Сэмплирование. На высоких нагрузках хранить все трассы дорого, поэтому часть отбрасывают. Есть head-based (решение принимается в начале, «сохраняем 1 из 100 запросов») и tail-based (решение принимается в конце: «сохраняем всё, что закончилось ошибкой или было медленным»). Второй вариант полезнее для отладки, но требует буферизации.
Чужой request id — это входные данные, а не факт
Если клиент может прислать свой X-Request-ID, он может прислать что угодно: строку на 10 килобайт, перевод строки (попытка log injection — подделки логов), SQL-кусок, чужой идентификатор, чтобы «подставить» другого. Всегда валидируй: только [A-Za-z0-9-], только разумная длина, иначе генерируй свой и логируй чужой отдельным полем.
Где встречается в обычной жизни
- Приложение банка показало «Ошибка. Код обращения: 8842-АБ». Это request id, который банк сгенерировал на входе, и по которому поддержка найдёт весь путь операции.
- Страница ошибки Cloudflare с надписью «Ray ID: 7c9a…».
cf-ray— это и есть request id на уровне CDN; по нему Cloudflare мгновенно находит запрос в своих логах. - Поддержка маркетплейса просит «номер заявки». Часто это не номер заказа, а идентификатор обращения — та же логика: одна метка на одну историю.
- Ты отправил форму, сайт ответил «Что-то пошло не так», и больше ничего. Плохой пример: request id не показали, и теперь ни клиент, ни поддержка не могут связать жалобу с логами. Отсутствие id — это тоже сигнал о зрелости сервиса.
- Трек-номер доставки. Смотришь статус по номеру, а не по адресу и фамилии — ровно та же идея сопоставления.
Где встречается в IT и бизнесе
- Отладка распределённых систем. Класс задач: «запрос упал у одного клиента из тысячи, надо найти именно его». Без request id это поиск по времени и IP — то есть гадание.
- Разбор инцидентов и SLA. Когда клиент говорит «в 14:32 у меня ничего не работало», request id из его скриншота сразу даёт точку входа в логи. Это сокращает время диагностики с часов до минут — и это измеримая экономия.
- Повторные операции и платежи. Здесь request id работает в паре с idempotency key: ключ защищает от двойного списания, а request id позволяет увидеть, что «двойное списание» — это на самом деле два разных запроса с разными ключами.
- Аудит и разбор полномочий. «Кто изменил тариф?» — по request id поднимается вся цепочка: какой пользователь, какой токен, какой сервис, какое время.
- Поддержка клиентов. Наличие request id в ответе API превращает тикет «у меня не работает» в конкретную ссылку на лог. Дешёвая, но очень заметная улучшалка клиентского опыта.
- Биллинг и аналитика. Сквозной id помогает не потерять событие при переносе между сервисами и не посчитать его дважды.
Кто пользуется
- AWS возвращает идентификатор запроса в заголовке вида
x-amzn-RequestIdпрактически на всех API — это часть их публичного контракта. - Cloudflare — заголовок
cf-rayна каждый запрос, плюс отдельная страница со списком Ray ID для поддержки. - GitHub API —
x-github-request-idв каждом ответе; их документация прямо советует указывать его в обращениях в поддержку. - Stripe —
Request-Idв ответах и отдельныйIdempotency-Keyдля защиты от повторов; хороший пример того, что это два разных поля. - nginx — встроенная переменная
$request_id(появилась в версии 1.11.0), которая генерирует 16 случайных байт в hex; ставится в лог и в заголовок ответа одной строкой конфига. - OpenTelemetry, Zipkin, Jaeger — стандартный стек для трассировки; Jaeger вырос в Uber, Zipkin — в Twitter, оба сегодня используются тысячами компаний. Точные цифры по объёмам трасс называть не буду — они меняются, и я не хочу их выдумывать.
Альтернативы и конкуренты
Ничего не пробрасывать, искать по времени и IP.
Плюсы: ноль работы, ничего не надо менять в коде и конфигах.
Минусы: работает только при одном сервисе и низкой нагрузке; при параллельных запросах от одного клиента вы уже не различите их; при разборе инцидента превращается в археологию.
Полноценный APM (Datadog, New Relic, Sentry, Elastic APM).
Плюсы: автоматическая инструментация, готовые дашборды, трейсы без ручной работы, алерты.
Минусы: деньги (обычно за объём данных), зависимость от вендора, «магия» — при проблеме непонятно, что именно не пробросилось; и всё равно нужно, чтобы заголовок доходил до конца.
Сразу W3C traceparent вместо своего X-Request-ID.
Плюсы: совместимость с OpenTelemetry и любым трассировщиком «из коробки»; один формат на весь зоопарк сервисов.
Минусы: длиннее и менее очевидно для человека; trace id жёстко 16 байт и не подчиняется вашим правилам; для простого «найти строчку в логе» — избыточно.
Идентификация по бизнес-ключу (order_id, user_id).
Плюсы: человекопонятно, поддержка уже умеет с этим работать, не нужна инфраструктура.
Минусы: не работает до появления сущности (запрос упал раньше); один запрос может затрагивать несколько сущностей; и это персональные данные, которые не хочется тащить в каждый лог.
Когда НЕ стоит использовать
- Один монолит без фоновых задач. Там request id почти бесполезен: в логах и так одна цепочка на запрос, а стектрейс информативнее. Начинать стоит, когда появляется второй сервис или первая очередь.
- Как ключ идемпотентности. Потому что request id уникален для каждой попытки, а защита от повторов требует как раз одинакового ключа при повторе. Если сделать наоборот — двойные списания гарантированы.
- Если в него пытаются зашить смысл или персональные данные.
rid=client-12345-order-99выглядит удобно, но превращает каждый лог в хранилище персональных данных и ломает ротацию. Идентификатор должен быть бессмысленным. - Как замену нормальному логированию. Request id связывает записи, но не создаёт их: если сервис пишет «error occurred» без контекста, id не спасёт. Сначала — осмысленные логи, потом — сквозной id.
Ценность появляется только на границах
Request id бесполезен ровно в одном месте: там, где его забыли пробросить. И это всегда граница — HTTP-клиент, вызов очереди, воркер, сторонний SDK. Поэтому в зрелых командах пишут не «не забудь передать заголовок», а интеграционный тест, который проверяет, что id дошёл до последнего сервиса в цепочке.
Связанные понятия
- Trace ID — идентификатор всей распределённой трассы, объединяющий все шаги одного запроса.
- Span ID — идентификатор одного шага внутри трассы; вместе с parent span id образует дерево.
- W3C Trace Context — стандарт заголовков
traceparent/tracestateдля передачи контекста трассировки. - OpenTelemetry — открытый стандарт и набор SDK для сбора трасс, метрик и логов; наследник OpenTracing и OpenCensus.
- Idempotency Key — ключ, гарантирующий, что повторная отправка того же намерения не выполнит операцию дважды.
- MDC (Mapped Diagnostic Context) — механизм, который автоматически добавляет поля (например, request id) в каждую строку лога.
- B3 — более старый набор заголовков трассировки из Zipkin, до сих пор широко встречается.
- Сэмплирование — отбор части трасс для хранения, чтобы не платить за весь объём.
Литература и источники
- Gregor Hohpe, Bobby Woolf. «Enterprise Integration Patterns», 2003, en — паттерн Correlation Identifier, первоисточник идеи.
- «Dapper, a Large-Scale Distributed Systems Tracing Infrastructure», Google, 2010, en — искать по названию; классическая статья про trace id и span id.
- W3C Trace Context — https://www.w3.org/TR/trace-context/ — официальная спецификация заголовков
traceparentиtracestate. - OpenTelemetry — https://opentelemetry.io/ — документация по инструментации и контексту.
- Zipkin — https://zipkin.io/ , Jaeger — https://jaegertracing.io/ — практические реализации трассировки.
- RFC 9562 (UUID, включая UUIDv7) — https://www.rfc-editor.org/rfc/rfc9562 — если выбираешь формат идентификатора.
- Документация nginx, директива
$request_id— https://nginx.org/ — минимальный рецепт для тех, у кого nginx уже стоит на входе.
Где встретилось у меня
Вчера утром мой собственный скрипт-генератор статей запустился три раза почти одновременно, и два запуска упали на одном и том же ответе внешнего API: HTTP 400 с телом ошибки, где рядом с текстом лежали поля вида request_id, upstream_status и code. Практическая ценность этих полей стала очевидна сразу: три параллельных запуска оставили в логах почти одинаковые строчки, отличить их друг от друга можно было только по идентификатору запроса — и именно он позволил бы провайдеру найти конкретный упавший вызов, а не «примерно тот интервал времени». Отдельно отмечу: сам вчерашний контекст был очень скудным (почти всё — повторы одного и того же сообщения об ошибке), поэтому тема выбрана по самой заметной технической сущности, которая в нём встречалась.
Краткое резюме
- Request id — бессмысленная уникальная строка, которая присваивается запросу на входе и передаётся через все сервисы, очереди и фоновые задачи.
- Придумано в интеграции корпоративных систем (паттерн Correlation Identifier, 2003), в веб пришло через трассировку (Dapper, 2010; Zipkin, 2012) и стандартизировалось в W3C Trace Context (2021).
- Работает только при двух условиях: идентификатор генерируется ровно один раз на границе и пробрасывается на каждой границе дальше — включая очереди и воркеры.
- Не путать с idempotency key (защита от повторов), session id (жизнь пользователя) и trace id (вся распределённая трасса).
- Обязательный минимум: валидировать входящий id, писать его в структурированные логи, возвращать клиенту в теле ошибки.