# Кэширование и мониторинг

Паттерны продакшна для поиска авиабилетов — кэширование, управление квотой и актуальность результатов.



Обработка ошибок и состояния с пустым результатом: [Обработка ошибок](/docs/v31/tickets-integration-errors). HTTP-ошибки платформы и повторные попытки: [Обработка ошибок](/docs/errors).

### Актуальность данных [#data-freshness]

API кэширует результаты поиска авиабилетов **не более чем на 1 час**. Ключ кэша — полный кортеж запроса: `(origin, destination, date, passengers)`. Любое изменение числа пассажиров или даты создаёт отдельную запись в кэше и новый вызов API.

| Эндпоинт              | Рекомендуемый TTL | Ключ кэша                                     | Примечания                                                                                      |
| --------------------- | ----------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `GET /tickets/search` | Не более 1 часа   | `tickets:{origin}:{dest}:{date}:{passengers}` | Совпадает со временем жизни кэша API — при более длинных TTL есть риск показать устаревшие цены |

**Раскрытие информации:** всегда представляйте цены как ориентировочные снимки рынка. В интерфейсе для пассажиров ясно укажите, что цены могут измениться между поиском и бронированием (например: «Цены могут измениться при бронировании. Уточните тариф на сайте авиакомпании.»).

### Debounce [#debounce]

Интерфейсы поиска авиабилетов обычно отправляют запрос при каждом изменении выбора аэропорта или даты. Применяйте debounce к полям ввода с задержкой **300–500 ms** перед отправкой запроса, чтобы не засыпать API неполными запросами, пока пользователь печатает.

```python
import os
import time
import requests

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"

_cache: dict[str, tuple[dict, float]] = {}
TTL = 3600  # 1 hour — matches API cache lifetime


def search_tickets_cached(
    origin: str,
    destination: str,
    date: str,
    passengers: int = 1,
) -> dict:
    """
    Cache-wrapped ticket search. TTL 1 hour, keyed on (origin, dest, date, passengers).
    Raises ValueError on 422. Returns full response dict including count and flights.
    """
    cache_key = f"tickets:{origin}:{destination}:{date}:{passengers}"
    cached = _cache.get(cache_key)
    if cached and time.time() < cached[1]:
        return cached[0]

    r = requests.get(
        f"{BASE}/tickets/search",
        headers=HEADERS,
        params={
            "origin": origin,
            "destination": destination,
            "date": date,
            "passengers": passengers,
        },
        timeout=(10, 15),
    )
    if r.status_code == 422:
        detail = r.json().get("detail", "validation error")
        raise ValueError(f"422 Unprocessable: {detail}")
    r.raise_for_status()

    data = r.json()
    _cache[cache_key] = (data, time.time() + TTL)
    return data


if __name__ == "__main__":
    result = search_tickets_cached("JFK", "LAX", date="2026-09-15", passengers=1)
    flights = result.get("flights", [])
    print(f"Found {result.get('count', 0)} option(s).")
    if flights:
        cheapest = flights[0]
        duration = cheapest.get("total_duration_min", 0)
        print(
            f"Cheapest: ${cheapest.get('price_usd', 0):.2f}  ·  "
            f"{duration // 60}h {duration % 60:02d}m"
        )
        print("Prices may change at booking.")

    result2 = search_tickets_cached("JFK", "LAX", date="2026-09-15", passengers=1)
    print("Cache hit:", result2 is result)
```

В продакшне замените кэш на основе словаря на Redis или Memcached, чтобы кэш сохранялся после перезапусков и был общим для всех экземпляров.

### Ограничения частоты запросов [#rate-limits]

| Заголовок                        | Значение                              |
| -------------------------------- | ------------------------------------- |
| `X-RateLimit-Requests-Limit`     | Месячная квота запросов вашего тарифа |
| `X-RateLimit-Requests-Remaining` | Оставшиеся запросы в этом месяце      |
| `X-RateLimit-Requests-Reset`     | Секунды до сброса квоты               |

При TTL в 1 час для каждого кортежа `(origin, destination, date, passengers)` каждый уникальный поиск расходует не более одного вызова API в час. Для сценариев с высоким трафиком заранее заполняйте кэш самыми популярными маршрутами в часы низкой нагрузки.

### Мониторинг [#monitoring]

Логируйте при каждом поиске авиабилетов:

* `origin`, `destination`, `date`, `passengers`
* `status_code`
* `elapsed_ms`
* `count` (из ответа — полезно для отслеживания доли запросов без мест)
* `cache_hit` (boolean)
* `quota_remaining` (из `X-RateLimit-Requests-Remaining`)

**Настройте оповещения на:**

* резкий рост доли `count: 0` — может указывать на ошибочный диапазон дат или проблему с параметрами запроса
* `status = 429` более двух раз за 5 минут — увеличьте TTL или ограничьте число параллельных поисков
* повторяющиеся `elapsed_ms > 5000` — рассмотрите более короткий таймаут с резервным сообщением «попробуйте ещё раз»
* `quota_remaining < 200` — приближение к месячному лимиту; увеличьте TTL или сократите число уникальных поисков

***

См. также: [Авиабилеты](/docs/v31/tickets) · [Поиск аэропортов](/docs/v31/airport-search) · [Обработка ошибок](/docs/v31/tickets-integration-errors) · [Обработка ошибок](/docs/errors)
