Ускорение запросов к Яндекс Маршрутизации начинается с одного действия: разложить время ответа на сетевую часть и время расчёта. Если из 900 мс на DNS, TCP и TLS уходит 250 мс, кэширование ответов проблему не снимет. Сначала придётся приблизить клиент к серверам сервиса и переиспользовать соединения.
Рабочий порядок такой: замерить базовую задержку, убрать лишние вызовы, включить локальный кэш для геокодирования с TTL в дни, для маршрутов с TTL в минуты, поставить перед API кэширующий прокси на Nginx или распределённый кэш на Redis, а затем следить за hit ratio. Ориентиры, которые чаще всего подходят продакшену: геокодирование адреса 7-30 дней, матрица 5x5 - 5-15 минут, маршрут между двумя точками - 1-5 минут.
Ниже конкретика по каждому шагу: команды для замера задержек, правила выбора ключей кэша, готовые конфигурации Nginx и Redis, требования безопасности при работе с геоданными и метрики, по которым видно, что кэш работает.
Лимиты, доступные эндпоинты, версии API и правила хранения результатов Яндекс Маршрутизации зависят от вашего тарифа и переданы во входных материалах не были. Сверяйте эти параметры с официальной документацией сервиса, а значения TTL и задержек из статьи воспринимайте как инженерные ориентиры для проверки на своём стенде.
Почему запросы к Яндекс Маршрутизации могут тормозить
Время ответа складывается из пяти отрезков: DNS-резолвинг, установка TCP-соединения, TLS-хендшейк, ожидание первого байта (TTFB) и передача тела ответа. Первые три зависят от сети и удалённости клиента. Четвёртый отрезок определяется сложностью расчёта и тем, не упёрлись ли вы в лимиты запросов. Пятый растёт вместе с размером JSON.
К расчётной части добавляются факторы, которые легко недооценить. Геокодирование и обратное геокодирование ищут по индексу адресов и обычно отвечают быстрее, чем построение маршрута. Матрица расстояний считается тяжелее одиночного маршрута: чем больше источников и целей, тем дольше ответ, а запрос матрицы 10x10 и запрос одного маршрута по времени различаются кратно. Таймауты на клиенте, выставленные на глаз, обрывают соединение раньше, чем сервис успевает ответить, и вместо полезного результата вы получаете повторные вызовы.
Отдельный источник задержек - лимиты запросов. При превышении квоты приходят коды 429 и 403, клиент без backoff начинает повторять вызовы и суммарное время до успешного ответа растёт. Диагностика перед оптимизацией обязательна: без замеров не понять, что даёт эффект - кэш, регион размещения или переписанный клиент. Если начинать нужно со стороны приложения, посмотрите материал о повышении производительности без замены оборудования: как убрать узкое место и лишние вызовы.
Как измерить задержку на каждом этапе
Быстрее всего разбивку по фазам показывает curl с шаблоном -w. Подставьте эндпоинт и ключ из настроек вашего окружения:
curl -s -o /dev/null -w "dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n" \
-H "Authorization: Api-Key $ROUTING_API_KEY" "$ROUTING_API_ENDPOINT"
Читайте результат так. Большой time_namelookup указывает на медленный резолвер или на отсутствие кэша DNS на хосте. Разница между time_connect и time_namelookup показывает сетевую задержку до сервера. Разница между time_appconnect и time_connect - стоимость TLS-хендшейка, и здесь помогает session resumption. Разница между time_starttransfer и time_appconnect - время обработки на стороне сервиса: именно её и сокращает кэширование. Разница между time_total и time_starttransfer - передача тела, где помогает сжатие ответа.
Для массовых замеров запускайте тот же curl в цикле из bash и агрегируйте вывод через awk, либо используйте скрипт на Python, который считает p50, p95 и p99. Постоянный контроль удобнее вынести в Prometheus через blackbox_exporter: проба типа http_2xx даёт метрики probe_success, probe_duration_seconds и probe_http_duration_seconds с фазами resolve, connect, tls, processing и transfer. Сравнивайте эти метрики до и после внедрения кэша, иначе эффект останется недоказанным.
Типичные ошибки при интеграции, которые увеличивают задержки
Самая дорогая ошибка - новый HTTP-клиент на каждый запрос. Так теряется пул соединений, и каждый вызов заново проходит TCP и TLS:
import requests
for point in points:
r = requests.get(ENDPOINT, params=params, headers=headers) # новый клиент и новое соединение на каждой итерации
Исправляется переиспользованием сессии и настройкой пула. Одна сессия requests.Session() на процесс плюс HTTPAdapter с pool_connections и pool_maxsize даёт keep-alive по умолчанию. Те же правила действуют для Go (http.Transport с MaxIdleConnsPerHost) и для Java (HttpClient с пулом соединений).
Дальше по списку: синхронные вызовы в цикле вместо параллельных; ретраи без экспоненциальной задержки и без учёта заголовка Retry-After, когда сервис просит подождать; игнорирование лимитов и превращение клиента в источник перегрузки; HTTP/1.1 без мультиплексирования там, где клиент умеет HTTP/2; отсутствие явных таймаутов, из-за чего запрос висит до системного лимита сокета. Каждый пункт проверяется за пару минут: включите логирование времени и числа установленных соединений на 1000 запросов, и картина станет очевидной.
Что и как кэшировать в ответах API Яндекс Маршрутизации
Запросы делятся на три группы по скорости изменения данных. Геокодирование адреса привязано к справочнику адресов и меняется редко, поэтому кэш живёт дни и недели. Обратное геокодирование зависит от адресной базы и административных изменений, поэтому TTL здесь короче. Маршруты и матрицы зависят от дорожной ситуации, и их кэш живёт минуты. Прокси на Nginx хорошо работает с одинаковыми запросами, Redis даёт гибкие ключи и частичное кэширование, Memcached подходит только для простых пар ключ-значение, Varnish силён как HTTP-акселератор перед API.
Ключ кэша стройте из нормализованных параметров: координаты, округлённые до 5-6 знаков, язык, единицы измерения, набор флагов (платные дороги, тип транспорта), а также время отправления, округлённое до 5-минутного интервала. Дополнительно подмешивайте отпечаток ключа API или арендатора, чтобы клиенты с разными правами не получали чужие ответы. Ключ считайте как SHA-256 от строки параметров: это даёт фиксированную длину и убирает проблему спецсимволов.
Выбор TTL для разных типов запросов
| Тип запроса | Рекомендуемый TTL | Обоснование |
|---|---|---|
| Геокодирование адреса | 7-30 дней | Адресные данные меняются редко, привязки к пользователю нет |
| Обратное геокодирование координат | 1-24 часа | Возможны изменения адресной базы, требуется осторожность |
| Маршрут между двумя точками | 1-5 минут | Дорожная ситуация меняется быстро |
| Матрица 5x5 | 5-15 минут | Сценарии планирования допускают небольшую задержку данных |
| Матрица 10x10 и больше | 10-30 минут | Дорогой расчёт, частые повторы одного и того же запроса |
| Изолинии и статические карты | 7-30 дней | Геометрия меняется медленно |
TTL удобно адаптировать по времени суток: ночью и в выходные интервалы разумно увеличивать, в утренний и вечерний час пик сокращать. К значениям добавьте случайный разброс 5-10 %, чтобы волна истечения ключей не била по сервису одним залпом. Для геокодирования есть смысл разделить кэш на два уровня: локальный in-memory с TTL в минуты и общий Redis с TTL в дни. Первый снимает основную часть повторов, второй закрывает запросы после рестарта сервиса.
Инвалидация кэша: когда и как сбрасывать устаревшие данные
Вебхуков об изменении дорожной ситуации API маршрутизации не отдаёт, поэтому событийная инвалидация по трафику недоступна: остаётся TTL и версионирование ключей. На практике хватает трёх приёмов.
Первый: инвалидация по времени. Ключ получает TTL при записи, и старые значения вытесняются сами. В Redis это команда SET с параметром ex, в Nginx - директива proxy_cache_valid для соответствующего кода ответа.
Второй: версионирование ключей. Префикс ключа содержит версию схемы и версию API, например rt:v3:route:... При смене логики или версии API вы поднимаете версию, и весь старый кэш перестаёт использоваться без массового удаления.
Третий: точечная очистка. В Redis удаляйте ключи пакетами через SCAN с шаблоном и UNLINK, а не через KEYS на продакшене. В Nginx вычищайте записи через директивы proxy_cache_purge или удалением файлов по ключу, если модуль очистки не подключён. Добавьте режим stale-while-revalidate: proxy_cache_background_update on и proxy_cache_use_stale updating позволяют отдавать устаревший ответ, пока идёт фоновое обновление, и не задерживать пользователя. Разбор типовых сбоев и способов их диагностики есть в материале об ошибках кэширования: устаревшие данные, переполнение и сбои инвалидации.
Организация локального кэша и прокси для API маршрутизации
Есть две рабочие архитектуры. Кэширующий прокси на Nginx встаёт перед API и не требует изменений в приложении: он кэширует одинаковые ответы по ключу из запроса. Распределённый кэш на Redis живёт внутри сервиса и даёт гибкие ключи, частичные данные и разные TTL для разных полей. Первая схема выигрывает простотой и скоростью запуска, вторая - управляемостью и точностью ключей.
| Критерий | Nginx proxy_cache | Redis в клиенте |
|---|---|---|
| Изменения в коде | Не нужны | Нужны |
| Ключ кэша | Из метода, URL, аргументов, заголовков | Любая строка, включая хэш параметров |
| Инвалидация | TTL и очистка по ключу | TTL, удаление, версионирование |
| Частичный кэш | Нет, только целый ответ | Да, любые структуры |
| Защита от лавины | proxy_cache_lock | Блокировка через SET NX |
Настройка Nginx как кэширующего прокси
Базовый конфиг для зоны кэша и виртуального хоста. Обратите внимание на ключ: в него добавлен отпечаток ключа API, иначе арендаторы начнут получать чужие ответы.
proxy_cache_path /var/cache/nginx/routing levels=1:2 keys_zone=routing:64m max_size=10g inactive=12h use_temp_path=off;
proxy_cache_key "$request_method$uri|$args|$http_x_client_fingerprint";
proxy_cache_valid 200 2m;
proxy_cache_valid 429 5s;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status always;
Проверка работы сводится к одному запросу: заголовок X-Cache-Status должен показать MISS при первом вызове и HIT при повторе с теми же аргументами. Директивы proxy_cache_lock и proxy_cache_lock_timeout защищают от cache stampede, когда сотни клиентов одновременно запрашивают один и тот же маршрут. Перед сменой версии API поднимайте версию в proxy_cache_key и меняйте каталог зоны, чтобы старые записи не смешивались с новыми. Расширенные примеры конфигурации, включая инвалидацию и оценку выигрыша, собраны в руководстве по полной настройке кэширования Nginx: proxy_cache для статики и динамики. Если прокси нужно поставить перед бэкендом вашего собственного сервиса, пригодится схема инверсного кэширования: разгрузка бэкенда через Nginx proxy_cache.
Использование Redis для кэширования ответов API
Клиентский кэш на Redis даёт точный контроль над ключом и TTL. Схема чтения выглядит так:
import hashlib, json, random, redis
pool = redis.ConnectionPool.from_url(REDIS_URL, max_connections=64, decode_responses=True)
r = redis.Redis(connection_pool=pool)
norm = normalize(params) # округление координат, единицы, язык, слот времени
cache_key = "rt:v3:route:" + hashlib.sha256(norm.encode()).hexdigest()
cached = r.get(cache_key)
if cached:
return json.loads(cached)
response = session.get(ENDPOINT, params=params, headers=headers, timeout=(2, 10))
ttl = base_ttl + random.randint(0, max(1, base_ttl // 10))
r.set(cache_key, json.dumps(response.json()), ex=ttl)
Пул соединений обязателен: без него redis-py открывает новое подключение на команду. Настройте сервер под кэш: maxmemory с запасом под пиковый объём, maxmemory-policy allkeys-lru или volatile-lru, персистентность можно отключить, поскольку данные восстанавливаются из API. Для высокой доступности подойдёт Redis Cluster или Sentinel, для защиты от лавины - короткая блокировка через SET key value NX EX 10 перед обращением к API. Сравнение Redis и Memcached, стратегии инвалидации и шаблоны кода для высоких нагрузок разобраны в отдельной статье: кэширование в высоконагруженных системах.
Оптимизация без кэширования: сеть, запросы, инфраструктура
Есть сценарии, где кэш неприменим: данные персонализированы, условия использования API ограничивают хранение результатов, TTL должен быть нулевым. Тогда работают три направления: сократить число запросов, уменьшить стоимость каждого запроса и приблизить клиент к сервису.
Батчинг и асинхронность в клиентах API
Один запрос матрицы заменяет десятки одиночных маршрутов. Если нужно построить 20 маршрутов между складами, выгоднее запросить матрицу и собрать маршруты локально, чем делать 20 вызовов. Проверьте ограничение на число точек в одном запросе: превышение лимита даёт ошибку валидации, а не частичный результат.
Параллельные вызовы делайте через asyncio с aiohttp или httpx и ограничивайте конкурентность семафором, иначе легко упереться в лимиты запросов:
sem = asyncio.Semaphore(8)
async def call(session, params):
async with sem:
async with session.get(ENDPOINT, params=params, timeout=aiohttp.ClientTimeout(total=15)) as resp:
return await resp.json()
Ретраи ставьте только на идемпотентные GET-запросы и только на коды 429 и 5xx, с экспоненциальной задержкой и джиттером. Заголовок Retry-After, если он пришёл в ответе, имеет приоритет над вашей формулой. Держите клиентский таймаут выше p99 времени ответа сервиса, иначе вы будете обрывать корректные медленные запросы матриц.
Выбор региона и провайдера для минимальной задержки
Сетевая задержка растёт с расстоянием. Для сервисов Яндекса разумно размещать клиентские сервисы в дата-центрах Москвы или Санкт-Петербурга: типичный RTT из московской площадки измеряется единицами миллисекунд, а из Западной Европы - десятками, что даёт разницу в 50-100 мс на каждый вызов. При тысячах запросов в час это заметная величина.
Смотрите не только на географию, но и на маршрут трафика: у части облачных провайдеров есть прямые пиринговые соединения с крупными российскими сетями, и такой маршрут короче обходного. Проверить это можно трассировкой mtr до нужного узла из конкретной зоны. Для размещения сервиса рядом с пользователями и API подойдёт облачный провайдер с площадками в РФ, например Timeweb Cloud: там доступны серверы, Kubernetes и хранилище, что позволяет поднять кэширующий прокси и Redis в той же сети, где работает приложение.
Из протокольных настроек эффект дают HTTP/2 с мультиплексированием вместо нескольких TCP-соединений, включённый keep-alive на обоих концах, gzip или brotli для сжатия ответов и повторное использование TLS-сессий. CDN для динамических ответов API смысла не имеет, а вот статику собственного интерфейса и тайлы карт вынести на CDN стоит.
Безопасность и законодательство при кэшировании геоданных
Координаты, привязанные к человеку, попадают под режим персональных данных, поэтому кэш маршрутизации требует отдельного разговора о составе данных. Общее правило простое: в кэше хранится обезличенный справочный результат, а не история перемещений конкретного пользователя.
Какие данные можно хранить в кэше без риска
Безопасный состав: нормализованные адреса без привязки к пользователю, координаты, округлённые до 3 знаков после запятой (это примерно сотни метров), результаты расчёта маршрутов без идентификаторов клиента, агрегированные расстояния и время в пути. Ключ кэша не должен содержать телефон, номер заказа или user_id в открытом виде: для таких случаев считайте HMAC от идентификатора с серверной солью.
Не храните в кэше точные координаты пользователя, историю его перемещений, адреса доставки вместе с именами, содержимое заголовков Authorization и ключи API в открытом виде. Логи кэша тоже подчищайте: строка с полным query string может содержать те же персональные данные, что и сам запрос.
Шифрование и контроль доступа к кэшу
Redis выводите в приватную сеть без публичного порта, включайте TLS для клиентских подключений и шифрование диска, заводите отдельного пользователя через ACL с ограниченным набором команд (GET, SET, DEL, SCAN, UNLINK) и без доступа к конфигурации. Пароль держите в секрет-хранилище, а не в переменной окружения в открытом виде в манифесте.
Аудит доступа стройте на логах подключений и включённых командах Redis, а также на логах Nginx с кодом ответа и статусом кэша. По части регулирования ориентируйтесь на два режима: 152-ФЗ для персональных данных граждан РФ и GDPR для пользователей ЕС, включая правила трансграничной передачи. Отдельно проверьте лицензионные условия API маршрутизации: они задают, что и на какой срок разрешено хранить из полученных результатов. Если условия ограничивают длительное хранение, ставьте TTL в часы вместо дней и храните только производные значения, а не копию ответа.
Мониторинг и отладка кэша в production
Кэш без метрик превращается в чёрный ящик: он может не работать вовсе, а вы будете объяснять улучшения другими причинами. Минимальный набор наблюдаемости собирается за один вечер.
Ключевые метрики для кэша маршрутизации
Основные показатели: cache_hit_ratio и cache_miss_ratio (отношение статусов HIT и MISS из логов Nginx либо keyspace_hits и keyspace_misses из Redis INFO), avg_latency_cached и avg_latency_uncached, evicted_keys, used_memory и его доля от maxmemory, число ошибок API по кодам.
Пороги для алертов, которые хорошо работают на практике: hit ratio по кэшу геокодирования ниже 80 %, hit ratio по маршрутам ниже 40 %, p95 времени ответа вырос на 30 % относительно базовой линии, появились вытесненные ключи при стабильной нагрузке, память превысила 80 % от maxmemory. Метрики Nginx отдаёт модуль stub_status (active, reading, writing, waiting), метрики Redis - redis_exporter, дальше Prometheus и панель Grafana с двумя графиками: hit ratio и распределение задержек в разрезе cached и uncached.
Логирование и трассировка запросов к API
Логируйте структурированно, в JSON, и обязательно пишите в запись префикс ключа кэша, статус кэша, код ответа, время до upstream и время обработки. В Nginx это делается своим log_format с переменными $upstream_cache_status и $upstream_response_time:
log_format routing '"ts":"$time_iso8601","uri":"$uri","status":$status,"cache":"$upstream_cache_status","upstream_time":$upstream_response_time,"request_time":$request_time';
Сквозную картину даёт OpenTelemetry: спаны на поиск в кэше, на вызов API и на запись в кэш показывают, где именно теряется время, а идентификатор трассировки помогает связать запрос пользователя с конкретным ключом. Помните о риске утечки персональных данных в трейсы: query string с координатами логируйте в усечённом виде или только в виде хэша.
Актуальные версии ПО и типичные ошибки в 2026 году
Берите стабильные ветки, которые получают обновления безопасности: Nginx 1.27 и новее, Redis 7.4 и новее, Kubernetes 1.32 и новее, Python 3.12 и новее, актуальные версии Prometheus, Grafana и OpenTelemetry Collector. Точные номера релизов сверяйте в release notes перед обновлением продакшена.
Устаревшие подходы, которые всё ещё встречаются: Memcached вместо Redis там, где нужны структуры и атомарные операции; HTTP/1.1 без keep-alive в высоконагруженном клиенте; отказ от HTTP/2 при параллельных вызовах; игнорирование QUIC и HTTP/3 для мобильных клиентов; TTL в один час для маршрутов с учётом пробок; ключ кэша без отпечатка арендатора. Самая частая ошибка не в конфигурации, а в отсутствии замеров: кэш включают, latency не меняется, а причина была в регионе размещения или в пуле соединений.
Чек-лист внедрения кэша для Яндекс Маршрутизации
- Снять базовые метрики: p50, p95, p99 времени ответа по каждому типу запроса, разбивка по фазам DNS, TCP, TLS, TTFB.
- Разделить запросы на группы по скорости изменения данных и решить, что кэшировать, а что нельзя по условиям использования API.
- Выбрать TTL для каждой группы и заложить разброс 5-10 % против лавины истечения ключей.
- Развернуть кэш: Nginx proxy_cache для одинаковых запросов, Redis для гибких ключей, при необходимости оба уровня.
- Настроить мониторинг: hit ratio, кэшированные и некэшированные задержки, память, вытесненные ключи, алерты по порогам.
- Проверить безопасность: приватная сеть, TLS, ACL, отсутствие персональных данных и ключей API в кэше и логах.
- Провести нагрузочное тестирование и сравнить latency до и после, включая поведение при холодном кэше.
Запускайте изменения на части трафика: направьте 10 % запросов через прокси, сравните p95 с контрольной группой за сутки, затем увеличивайте долю. Критерий готовности простой: hit ratio выше целевого порога, p95 не вырос, а число вызовов API сократилось настолько, что запас по лимитам стал ощутимым.