# Caching & Monitoring

Muster für die Ticketsuche im Produktivbetrieb — Caching, Verwaltung des Kontingents und Aktualität der Ergebnisse.



Fehlerbehandlung und Zustände bei leeren Ergebnissen: [Fehlerbehandlung](/docs/v31/tickets-integration-errors). HTTP-Fehler der Plattform und Wiederholungsversuche: [Fehlerbehandlung](/docs/errors).

### Aktualität der Daten [#data-freshness]

Ticketergebnisse werden von der API **bis zu 1 Stunde** im Cache gehalten. Der Cache-Schlüssel ist das vollständige Abfragetupel: `(origin, destination, date, passengers)`. Jede Änderung der Passagierzahl oder des Datums erzeugt einen separaten Cache-Eintrag und einen neuen API-Aufruf.

| Endpoint              | Empfohlene TTL | Cache-Schlüssel                               | Hinweise                                                                                               |
| --------------------- | -------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `GET /tickets/search` | max. 1 Stunde  | `tickets:{origin}:{dest}:{date}:{passengers}` | Entspricht der Cache-Lebensdauer der API — längere TTLs bergen das Risiko, veraltete Preise anzuzeigen |

**Offenlegung:** Stellen Sie Preise immer als unverbindliche Momentaufnahmen des Marktes dar. Weisen Sie in der UI für Passagiere klar darauf hin, dass sich Preise zwischen Suche und Buchung ändern können (zum Beispiel: „Preise können sich bei der Buchung ändern. Bestätigen Sie den Tarif auf der Website der Fluggesellschaft.“).

### Debounce [#debounce]

Oberflächen für die Ticketsuche lösen typischerweise bei jeder Änderung der Flughafen- oder Datumsauswahl eine Anfrage aus. Verzögern Sie Eingaben per Debounce um **300–500 ms**, bevor Sie eine Anfrage senden, damit die API nicht mit unvollständigen Abfragen überflutet wird, während der Nutzer noch tippt.

```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)
```

Ersetzen Sie im Produktivbetrieb den Dict-basierten Cache durch Redis oder Memcached, damit der Cache Neustarts übersteht und von allen Instanzen gemeinsam genutzt wird.

### Rate Limits [#rate-limits]

| Header                           | Wert                                          |
| -------------------------------- | --------------------------------------------- |
| `X-RateLimit-Requests-Limit`     | Monatliches Anfragekontingent Ihres Tarifs    |
| `X-RateLimit-Requests-Remaining` | Verbleibende Anfragen in diesem Monat         |
| `X-RateLimit-Requests-Reset`     | Sekunden bis zum Zurücksetzen des Kontingents |

Bei einer TTL von 1 Stunde pro Tupel `(origin, destination, date, passengers)` verbraucht jede eindeutige Suche höchstens einen API-Aufruf pro Stunde. Füllen Sie bei Abläufen mit hohem Verkehrsaufkommen den Cache in verkehrsarmen Zeiten vorab mit den häufigsten Routen.

### Monitoring [#monitoring]

Protokollieren Sie bei jeder Ticketsuche:

* `origin`, `destination`, `date`, `passengers`
* `status_code`
* `elapsed_ms`
* `count` (aus der Antwort — nützlich, um den Anteil von Suchen ohne Verfügbarkeit zu verfolgen)
* `cache_hit` (boolean)
* `quota_remaining` (aus `X-RateLimit-Requests-Remaining`)

**Alarmieren bei:**

* sprunghaftem Anstieg des Anteils von `count: 0` — kann auf einen fehlerhaften Datumsbereich oder ein Problem mit Query-Parametern hindeuten
* `status = 429` mehr als zweimal innerhalb von 5 Minuten — erhöhen Sie die TTL oder drosseln Sie parallele Suchen
* wiederholt `elapsed_ms > 5000` — erwägen Sie einen kürzeren Timeout mit einer Fallback-Meldung „Bitte erneut versuchen“
* `quota_remaining < 200` — das monatliche Limit ist fast erreicht; verlängern Sie die TTLs oder reduzieren Sie die Anzahl eindeutiger Suchen

***

Siehe auch: [Flugtickets](/docs/v31/tickets) · [Flughafensuche](/docs/v31/airport-search) · [Fehlerbehandlung](/docs/v31/tickets-integration-errors) · [Fehlerbehandlung](/docs/errors)
