tmax и задержки в RTB: куда уходят миллисекунды
tmax — это максимальное время в миллисекундах, которое биржа ждет ответ DSP, считая сетевую задержку в обе стороны. Из этого бюджета DSP получает меньше, чем кажется: часть уходит на передачу по сети, установку соединений, разбор JSON и очереди. Чтобы уложиться, DSP держит постоянные соединения, отвечает с запасом до дедлайна и не ходит в медленные хранилища на горячем пути.
Что говорит спецификация
В OpenRTB 2.5 и 2.6 поле BidRequest.tmax описано как максимальное время в миллисекундах, которое биржа дает на получение ставок, включая интернет-задержку, чтобы избежать таймаута. Значение в запросе важнее любых договоренностей, заключенных заранее.
{
"id": "auc-7f3a",
"tmax": 250,
"at": 1,
"imp": [{ "id": "1", "bidfloor": 40.0, "bidfloorcur": "RUB",
"banner": { "format": [{ "w": 320, "h": 50 }] } }],
"app": { "bundle": "com.example.app" },
"device": { "os": "android", "ifa": "..." }
}
Главное слово — «включая». tmax отсчитывается на стороне биржи: от момента отправки запроса до момента, когда ответ должен быть у нее. DSP видит запрос позже, а ответ доходит до биржи позже, чем DSP его отправила. Подробнее про само поле — в статье глоссария «tmax».
Из чего складывается задержка
Разложим путь одной ставки. Цифры в таблице — пример для расчета, а не измерение конкретной биржи: в вашей интеграции их нужно снять самостоятельно.
| Этап | Где происходит | Пример, мс | От чего зависит |
|---|---|---|---|
| Очередь отправки у биржи | Биржа | 0–5 | Нагрузка, пул соединений биржи |
| Передача запроса | Сеть | 5–20 | Расстояние, маршрут, размер запроса |
| Установка соединения | Сеть | 0 или 1–2 RTT | Есть ли keep-alive |
| Прием и разбор запроса | DSP | 1–3 | Размер JSON, распаковка gzip, парсер |
| Поиск профиля пользователя | DSP | 1–10 | Кеш в памяти или сетевое хранилище |
| Отбор и оценка кандидатов | DSP | 1–10 | Число кампаний, индексы, модели |
| Сборка и сериализация ответа | DSP | 0,5–2 | Размер adm, шаблоны |
| Передача ответа | Сеть | 5–20 | Как и для запроса |
| Прием ответа биржей | Биржа | 0–5 | Нагрузка на биржу |
Если tmax = 250 мс, а сеть в обе стороны занимает 40 мс, у DSP на все остальное остается заметно меньше 210 мс: нужен запас на хвосты распределения.
Сеть: физику не обманешь
Свет в оптоволокне проходит примерно 200 км за миллисекунду. Если сервер DSP в 2 000 км от биржи, теоретический минимум кругового прохода (RTT) — около 20 мс, на практике больше: маршрут не прямой, по пути есть маршрутизаторы. Поэтому серверы ставят ближе к биржам, а не наоборот — время на сеть нельзя «оптимизировать кодом».
Соединение: самая дорогая лишняя работа
Новое HTTPS-соединение требует TCP-рукопожатия (1 RTT) и TLS-рукопожатия (1 RTT в TLS 1.3, 2 RTT в TLS 1.2). При RTT 20 мс это 40–60 мс до первого байта запроса — у короткого tmax они съедают значительную долю бюджета. Спецификация OpenRTB называет постоянные соединения (HTTP keep-alive) одним из самых простых и эффективных способов улучшить производительность: снижаются и накладные расходы, и загрузка процессора на обеих сторонах.
На что смотреть:
- таймаут простоя соединения у DSP должен быть больше интервала между запросами биржи, иначе соединения будут рваться и пересоздаваться;
- балансировщик перед DSP тоже держит соединения — его таймауты должны совпадать с настройками сервера;
- при раскатке новых версий соединения разрываются: штатное завершение (сначала перестать принимать новые, потом дождаться текущих) убирает всплеск таймаутов.
Сжатие
OpenRTB описывает gzip для обоих направлений. Ответ сжимается стандартно: биржа шлет Accept-Encoding: gzip, DSP отвечает с Content-Encoding: gzip. Сжатие запроса включают только по договоренности. Сжатие экономит трафик и время передачи больших запросов, но стоит процессорного времени — для маленьких ответов (например, отказ от ставки) выигрыш может быть нулевым.
Разбор запроса
Bid request в OpenRTB — JSON на несколько килобайт с вложенными объектами imp, app/site, device, user, а также расширениями ext. Типичная ошибка — разбирать одни и те же вложенные поля по нескольку раз на разных этапах. Разобрать один раз, сохранить в структуру и передавать дальше — дешевая оптимизация.
Отказ от ставки — тоже ответ
Спецификация предлагает два способа сказать «не ставлю»: пустой ответ с HTTP 204 или объект BidResponse с кодом причины в поле nbr. Быстрый отказ полезен всем: биржа не ждет до дедлайна, а DSP не тратит ресурсы на запросы, которые все равно не купит.
HTTP/1.1 204 No Content
Ранний отказ стоит делать на дешевых проверках: неподдерживаемый формат, запрещенное приложение, флор выше максимальной ставки всех активных кампаний.
Что делает DSP, чтобы уложиться
- Собственный дедлайн. DSP считает время от получения запроса и отвечает до
tmaxминус запас на обратную сеть. Если какой-то этап не успел, ставка собирается из того, что есть, или отправляется отказ — но вовремя. - Все горячее — в памяти. Кампании, креативы, таргетинги и правила ставок лежат в памяти процесса и обновляются в фоне. В сетевое хранилище на горячем пути идет только то, что нельзя держать локально, например профиль пользователя.
- Короткие таймауты на зависимости. Каждое обращение к внешнему хранилищу ограничено по времени. Медленный ответ хранилища не должен превращаться в медленный ответ бирже.
- Параллельные запросы. Независимые поиски (профиль, частотные ограничения, сегменты) выполняются одновременно, а не по очереди.
- Индексы вместо перебора. Кандидаты отбираются по заранее построенным индексам (формат, гео, ОС), а не перебором всех кампаний.
- Защита от перегрузки. При нехватке ресурсов лучше быстро отказаться от части запросов, чем отвечать на все с опозданием: опоздавший ответ стоит ресурсов и ничего не приносит.
- Без лишней работы в ответе. Тяжелое логирование, запись аналитики и отправка событий — асинхронно, после того как ответ ушел.
Отдельный случай — запросы с несколькими показами (imp) или с несколькими форматами в одном показе. Работа по отбору кандидатов растет с их числом, и такие запросы чаще упираются в дедлайн. Ограничить число оцениваемых вариантов на запрос надежнее, чем надеяться, что их будет мало.
Как измерять задержки
Среднее время ответа почти бесполезно: аукцион проигрывают на хвостах. Смотрят перцентили — p50, p95, p99 — и их нужно смотреть раздельно:
| Метрика | Где снимается | Что показывает |
|---|---|---|
| Время обработки в DSP | Внутри сервиса | Код, хранилища, сборщик мусора |
| Время на балансировщике | Вход в инфраструктуру DSP | Очереди и соединения до кода |
| Доля таймаутов у биржи | Отчет биржи, коды проигрыша 2 | То, что видит покупатель на самом деле |
| Число новых соединений в секунду | Балансировщик, сервер | Работает ли keep-alive |
Если внутренний p99 комфортный, а биржа видит таймауты, проблема почти всегда снаружи кода: сеть, соединения, балансировщик, очереди перед приложением. Причины проигрыша и то, как их читать, разобраны в статье «Почему я проиграл аукцион».
Пример: как распределить 250 мс
Пусть биржа шлет tmax = 250 мс, RTT до нее — 25 мс.
| Статья бюджета | мс |
|---|---|
| Доставка запроса (половина RTT) | ~13 |
| Доставка ответа (половина RTT) | ~13 |
| Запас на очереди и разброс сети | 40 |
| Внутренний дедлайн DSP | 150 |
| Резерв | ~34 |
Внутри 150 мс DSP закладывает лимиты на каждую зависимость (например, по 10–20 мс на сетевые хранилища) и прерывает обработку по дедлайну. Если без keep-alive каждое соединение стоит еще 50 мс на рукопожатия, резерв уходит в ноль — и это видно в отчете биржи как рост проигрышей с кодом 2.
Почему таймауты растут внезапно
Если код не менялся, а доля опозданий выросла, причина обычно в одном из пяти мест.
| Симптом | Вероятная причина | Как проверить |
|---|---|---|
| Таймауты растут волнами в часы пик | Не хватает ресурсов: процессор, потоки, соединения к хранилищам | Загрузка процессора и очередь запросов на пиках |
| Всплески каждые несколько минут | Паузы сборщика мусора или фоновые перезагрузки данных | Корреляция пауз с временем всплесков |
| Рост после раскатки | Обрыв постоянных соединений, «холодные» кеши новых экземпляров | Число новых соединений в секунду в момент раскатки |
| Рост только у одной биржи | Сеть до этой биржи или изменение ее tmax |
Сравнить tmax в запросах и RTT до биржи |
| Медленно растущий хвост p99 | Увеличились данные: больше кампаний, тяжелее профили, больше JSON | Размер запроса и число кандидатов на запрос |
Полезная привычка — логировать tmax из каждого запроса вместе с фактическим временем обработки. Тогда видно не только «сколько мы тратим», но и «сколько нам дали», а изменение условий на стороне биржи не путается с деградацией своего кода.
Как задержка связана с деньгами
Опоздавшие ответы не дают показов, но съедают ресурсы — процессор, сеть, обращения к хранилищам. В воронке RTB они выглядят как разрыв между «ставок отправлено» и «ставок учтено биржей». Если доля таймаутов растет, падают и выигрыши, и win rate, хотя ставки и креативы не менялись. Поэтому мониторинг задержек — часть закупки, а не только эксплуатации.
Частые вопросы
Что важнее: tmax из запроса или договоренность с биржей?
Значение из запроса: оно важнее любых предварительных договоренностей. Поле BidRequest.tmax — максимальное время в миллисекундах, которое биржа ждет ставки, включая задержку сети.
Пришлет ли биржа уведомление, если ответ DSP опоздал?
Не обязательно. Опоздавший ответ не участвует в аукционе; биржа может прислать уведомление о проигрыше с кодом 2 (Impression Opportunity Expired) или не прислать ничего.
Почему keep-alive так важен для RTB?
Новое TCP+TLS-соединение стоит одного-двух круговых проходов по сети до первого байта запроса. С постоянными соединениями эта цена платится один раз, а не на каждом аукционе.