MAATRIX / Блог / Турагентство: свой сайт подбора туров и кэш выдачи, чтобы не платить за каждый запрос

Турагентство: свой сайт подбора туров и кэш выдачи, чтобы не платить за каждый запрос

MAATRIX

Посетитель вбил на сайте «Турция, 7 ночей, 2 взрослых, вылет из Москвы» — и в этот момент сайт разослал запросы в несколько систем туроператоров, чтобы узнать актуальные цены и наличие мест. Через минуту он поменял даты на пару дней и запустил новый круг запросов. Каждый такой круг — это не бесплатная операция: большинство API туроператоров и агрегаторов тарифицируют именно запросы, а не только состоявшиеся продажи. При росте трафика сайта счёт за обращения к чужим API растёт быстрее, чем растёт число реальных бронирований, потому что ищут все, а покупает меньшинство. Ниже — как устроена эта боль технически и как собственный сервер с кэшем выдачи убирает повторные платные запросы там, где посетители ищут одно и то же.

Почему трафик сайта превращается в счёт от поставщика

Экономика туристического поиска устроена иначе, чем экономика интернет-магазина. В магазине посетитель обычно смотрит карточку товара — цена уже посчитана и лежит в базе, никакого внешнего запроса делать не нужно. В подборе туров всё наоборот: актуальная цена и наличие мест существуют только «здесь и сейчас» на стороне туроператора, потому что квоты меняются десятки раз в день — кто-то забронировал последние места, у авиакомпании изменился тариф, отель поднял цену на конкретные даты. Поэтому сайт подбора туров не хранит готовый прайс-лист, а вынужден спрашивать у поставщика заново при каждом релевантном поиске.

Проблема в том, что «релевантный поиск» на практике означает почти каждый визит. Пользователь меняет даты на день туда-сюда, пробует другой отель, меняет число ночей — и каждая такая правка формы для наивной реализации выглядит как новый уникальный запрос, который нужно переслать поставщику. При этом конверсия в поиске туров низкая: по общей логике индустрии большинство визитов заканчиваются просмотром вариантов без бронирования — люди сравнивают цены на нескольких сайтах, откладывают решение, ищут для себя и для друзей «просто прицениться». Каждый такой визит всё равно генерирует запросы к API поставщика, за которые агентство или платформа платит, даже если бронирования не будет вообще никогда.

Дальше это масштабируется плохо. Если сайт даёт заметный органический или рекламный трафик, число запросов к API туроператоров растёт линейно с посещаемостью — а не с числом продаж. Маркетинговый успех (больше людей заходит и ищет) сам по себе увеличивает операционные расходы на поиск, причём эти расходы платятся внешнему поставщику API за каждое обращение. «Сайт стал популярнее» — не всегда хорошая новость для бухгалтерии, если архитектура не продумана.

Как устроен подбор туров технически

Разберём механику без сказок про «искусственный интеллект туров» — на практике это довольно приземлённая интеграция. У туроператоров и консолидаторов (агрегаторов, которые сводят предложения нескольких операторов в одну выдачу) есть API-шлюзы — где-то это классический XML-протокол, унаследованный ещё от систем бронирования девяностых-двухтысячных, где-то современный JSON/REST. Логика одна: сайт отправляет параметры поиска (направление, даты, состав туристов, категория отеля, класс питания) и получает в ответ список подходящих туров с ценой и статусом наличия мест на момент запроса.

Ключевая деталь — большинство таких API рассчитаны на модель «запрос — плата», где стоимость привязана либо к числу вызовов в единицу времени, либо к пакету запросов, который агентство покупает или получает по договору. Это логично со стороны поставщика: генерация актуальной цены — не бесплатная операция и для него самого, она может идти через тот же XML-шлюз к системе бронирования отеля или чартерной программе перевозчика. Но для сайта, который просто хочет показать посетителю варианты, каждый клик «искать» или «изменить даты» без дополнительной логики — это прямые деньги поставщику, даже если два разных посетителя за пять минут искали буквально одно и то же: Турция, те же даты, тот же отель.

Именно эта деталь — «два разных посетителя искали одно и то же за короткое время» — и есть точка, где кэш выдачи меняет экономику сайта.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Что можно кэшировать, а что нельзя

Здесь важно не наступить на грабли, специфичные именно для туризма: не всё, что приходит от поставщика, безопасно кэшировать одинаково.

Можно кэшировать на короткое время — результаты поиска, которые отображаются в списке вариантов: цена «от», доступные категории номеров, ориентировочное наличие мест. Это то, что посетитель видит, листая карточки на сайте, и здесь короткий кэш (минуты, иногда пара десятков минут в зависимости от волатильности конкретного направления) не создаёт серьёзного риска — если цена чуть устареет, это не приводит к прямым финансовым потерям, потому что это ещё не факт бронирования.

Нельзя бездумно кэшировать — финальное подтверждение цены и квоты непосредственно перед оплатой. Когда посетитель нажимает «забронировать», сайт обязан либо сходить к поставщику заново за живой ценой, либо явно предупредить, что цена подлежит подтверждению. Показать в кэше цену, которая устарела двадцать минут назад, а потом продать тур по ней, хотя оператор уже поднял стоимость или продал последние места — прямой путь к отменённым бронированиям, недовольным клиентам и разбирательствам с поставщиком. Это не техническая мелочь, а вопрос доверия и денег: агентство, которое подтверждает тур клиенту, а через час сообщает «извините, мест больше нет», теряет клиента гарантированно, а иногда и репутацию на отзывах.

Практическое правило простое: список вариантов — кэшируем, финальный шаг перед оплатой — всегда живой запрос. Кэш экономит деньги на этапе «человек листает и сравнивает», а не на этапе «человек платит».

Стек: свой сервер, кэш и обратный прокси

Схема не требует экзотики — это классический слой кэширования перед внешним API, который вы полностью контролируете на собственном сервере.

Рабочий вариант из двух возможных подходов, которые можно сочетать:

  • Redis как кэш-хранилище результатов поиска. Приложение (бэкенд сайта подбора туров) перед обращением к API поставщика формирует ключ из параметров поиска и проверяет, есть ли уже сохранённый ответ. Если да — отдаёт его посетителю без обращения к внешнему API. Установка Redis на сервер разобрана в статье как установить и настроить Redis на VPS — там же нюансы конфигурации maxmemory-policy, которые важны именно для кэша с истекающими по времени записями.
  • Кэширующий обратный прокси на nginx — вариант, если у вас есть отдельный внутренний REST/HTTP-эндпоинт, который агрегирует ответы поставщиков и отдаёт их фронтенду сайта. Nginx можно поставить перед этим эндпоинтом и включить кэш ответов на уровне HTTP, ориентируясь на нормализованные параметры запроса. Пошагово это описано в статье как установить и настроить кэширование nginx на VPS.

Пример ключа для Redis, который нормализует поисковый запрос в устойчивую строку (важно отсортировать и привести к единому виду параметры, иначе один и тот же поиск с разным порядком фильтров создаст разные ключи и кэш не сработает):

tour:search:v1:direction=turkey:city=antalya:checkin=2026-09-05:nights=7:adults=2:children=0:hotel_class=4

Пример сохранения результата с истечением через 10 минут:

redis-cli SETEX "tour:search:v1:direction=turkey:city=antalya:checkin=2026-09-05:nights=7:adults=2" 600 "$(cat response.json)"

Пример конфигурации nginx для кэша ответов агрегирующего API-эндпоинта:

proxy_cache_path /var/cache/nginx/tours levels=1:2 keys_zone=tours_cache:64m max_size=1g inactive=30m;

server {
    location /api/search {
        proxy_cache tours_cache;
        proxy_cache_key "$request_method$request_uri$args";
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating;
        add_header X-Cache-Status $upstream_cache_status;

        proxy_pass http://backend_search;
    }
}

Заголовок X-Cache-Status полезен не для посетителей, а для вас — по нему сразу видно в логах, отдавался ли ответ из кэша (HIT) или ушёл живым запросом к поставщику (MISS), что удобно при разборе, почему счёт за API за месяц оказался выше ожидаемого.

Путь одного запроса: от формы до ответа

Проследим весь цикл на примере одного поиска, чтобы стало видно, где именно экономятся деньги.

1. Посетитель отправляет форму поиска. Направление, даты, состав туристов уходят на бэкенд POST-запросом.

2. Бэкенд нормализует параметры и формирует ключ кэша. Здесь важно не полагаться на сырой JSON от формы — привести регистр, отсортировать поля, округлить несущественные детали (например, если система всё равно ищет по датам заезда без учёта времени суток).

3. Проверка кэша. Если по ключу уже есть свежая запись — отдаём её посетителю сразу, без единого обращения к внешнему API поставщика. Это и есть тот случай, когда десять человек искали «Анталия, 7 ночей, вылет 5 сентября» в течение получаса — оплачивается только первый запрос, остальные девять обслуживаются из кэша бесплатно с точки зрения счёта поставщику.

4. Если кэш пуст (или запись устарела) — идём к API туроператора. Здесь может быть один запрос или несколько параллельных — к разным операторам/консолидаторам, если сайт агрегирует предложения не из одного источника.

import redis, json, hashlib

def get_search_results(params: dict):
    cache_key = build_cache_key(params)  # нормализация + сортировка полей
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached), "HIT"

    results = call_operator_apis(params)  # платный внешний запрос
    r.setex(cache_key, 600, json.dumps(results))  # TTL 10 минут — пример, не догма
    return results, "MISS"

5. Ответ сохраняется в кэш с ограниченным временем жизни и отдаётся посетителю. Следующий похожий поиск — от того же человека или от другого — обслуживается из кэша, пока запись не устарела.

Важно: шаг 4 — единственное место, где тратятся деньги на внешний API. Всё остальное происходит на вашем собственном сервере и ничего не стоит поставщику, кроме ресурсов сервера, которые вы уже оплачиваете фиксированно, независимо от числа посетителей.

Грабли: где кэш выдачи может подвести

Кэш — не бесплатная магия, и если поставить его небрежно, можно получить проблемы хуже, чем без него.

Эффект стада на популярных направлениях. Если TTL истёк одновременно для ключа с высоким спросом (например, самое популярное направление сезона), сразу много посетителей могут промахнуться мимо кэша и одновременно отправить запросы к API поставщика — вместо экономии вы получите всплеск платных обращений именно в момент истечения кэша. Решается блокировкой на обновление (первый запрос идёт к поставщику и обновляет кэш, остальные ждут или получают чуть устаревший ответ) — паттерн называют stale-while-revalidate. Он разобран в статье кэш прогрелся и уронил базу — эффект стада: пример там не про туризм, но механика проблемы и решения совпадает один в один.

Слишком долгий TTL — устаревшие цены и обиженные клиенты. Заманчиво поставить TTL побольше, чтобы сильнее сэкономить на запросах — но чем дольше живёт запись, тем выше риск, что посетитель увидит цену, которой уже нет, и разочаруется на следующем шаге. Разумный баланс — короткий TTL (минуты, не часы) для списка вариантов и обязательный живой запрос перед фактическим бронированием, как уже сказано выше. Нюансы выбора конкретного значения TTL — почему интуитивно «больше — безопаснее» тут не работает — разобраны в статье почему TTL кэша 60 секунд хуже, чем пять.

Инвалидация кэша при изменении условий у поставщика. Если оператор резко меняет цену или снимает направление с продажи, а ваш кэш держит старую запись до истечения TTL — посетители какое-то время видят неактуальную информацию. Полноценный push-механизм (когда поставщик сам уведомляет об изменении) есть не у всех API — многие туристические интеграции устроены так, что узнать об изменении можно только повторным опросом. Здесь TTL — единственный практический рычаг, и держать его слишком длинным ради экономии — обманчивая выгода. Общие сложности инвалидации кэша, не только применительно к турам, разобраны в статье инвалидация кэша: почему это так сложно.

Разные TTL для разных типов данных. Не все поля одинаково волатильны: наличие мест на чартерный рейс в высокий сезон может меняться за минуты, а базовая информация об отеле (звёздность, адрес, тип питания) практически не меняется неделями. Разумно кэшировать статичные атрибуты отеля отдельно и надолго, а цену и квоту — отдельно и на короткий срок, а не валить всё в один ключ с одним TTL.

Риск показать чужую цену не тому клиенту. Если у вас есть разные ценовые условия для разных партнёров или подпартнёров (например, у части агентств своя комиссионная сетка), убедитесь, что ключ кэша учитывает эту разницу — иначе кэш, рассчитанный на одного партнёра, может по ошибке отдать его цену другому. Это частная, но реальная ошибка проектирования ключей кэша: если в системе есть персонализация цены, персонализация обязана быть частью ключа, а не подразумеваться «по умолчанию».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Насколько короткий TTL нужен, чтобы не показывать устаревшие цены?

Универсального числа нет — зависит от волатильности направления и от того, как часто меняются цены у поставщиков в высокий сезон. Практический подход: начать с нескольких минут для списка вариантов, смотреть по логам долю жалоб на «цена изменилась при бронировании» и подстраивать TTL по факту, а не гадать заранее.

А если поставщик не хочет, чтобы его данные кэшировали?

Такое условие иногда прописано в договоре с оператором или агрегатором — стоит уточнить это заранее. Обычно ограничения касаются именно финальной цены перед оплатой (её и так не стоит кэшировать), а не промежуточного списка вариантов, но формулировки у разных поставщиков отличаются.

Кэш окупает свой сервер, если у нас небольшой сайт с умеренным трафиком?

Даже небольшой сайт подбора туров, где посетители активно перебирают даты и отели, может генерировать больше запросов к API, чем кажется — форма с автоматическим обновлением при каждом изменении фильтра плодит запросы быстро. Сервер под Redis или кэширующий nginx не требует мощного железа — младший VPS-тариф в большинстве случаев отбивается за счёт сокращения платных запросов уже в первый месяц, но точную цифру для своего трафика стоит прикинуть самостоятельно, отталкиваясь от текущего счёта за API.

Что делать с несколькими источниками (несколько операторов и агрегаторов) с разными правилами тарификации?

Кэшировать каждый источник отдельным пространством ключей и со своим TTL, ориентируясь на условия конкретного поставщика — не сводите всё к одному общему TTL «на всякий случай», это либо недоэкономит на одних источниках, либо создаст риск устаревших данных на других.

Нужно ли кэшировать сам факт бронирования?

Нет. Бронирование — не идемпотентная операция для кэша, каждое обращение должно идти к поставщику напрямую и живьём. Кэшируется только предварительный поиск и просмотр вариантов, а не действия, которые меняют состояние (бронь, оплата, отмена).

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →