# Caching & Monitoring

METAR und TAF im Produktivbetrieb cachen — Regeln zur Aktualität, TTLs, Kontingent-Header und Monitoring.



[METAR](https://en.wikipedia.org/wiki/METAR)/[TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast)-Fallbacks bei 404: [Fehlerbehandlung](/docs/v31/weather-integration-errors). HTTP-Fehler der Plattform und Wiederholungsversuche: [Fehlerbehandlung](/docs/errors).

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

| Datentyp        | Aktualisierungsrhythmus                                         | Hinweise                                                                              |
| --------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| METAR           | Alle 30 min (große Flughäfen), alle 60 min (kleinere Stationen) | SPECI-Meldungen werden bei signifikanten Änderungen der Bedingungen sofort ausgegeben |
| TAF             | 4-mal täglich — 00Z, 06Z, 12Z, 18Z                              | Gültig für 24 oder 30 Stunden; ein TAF bleibt zwischen den Ausgaben aktuell           |
| Höhenwinde (FB) | Fester Produktzeitplan der FAA                                  | Nur USA; `valid_time` in der Antwort kennzeichnet die Gültigkeit der Vorhersage       |
| PIREPs          | Fortlaufende Meldung durch Piloten                              | Kurzlebig — verwenden Sie einen kurzen Rückblickzeitraum (Parameter `hours`)          |
| AIRMET/SIGMET   | Ausgabe und Aktualisierung nach FAA-Zeitplan                    | Clientseitig anhand von `start_time` / `end_time` ablaufen lassen                     |

Das Feld `timestamp` in jeder Antwort gibt an, wann die API die Daten abgerufen hat — es ändert sich bei jeder Anfrage, auch wenn sich die zugrunde liegende Beobachtung nicht geändert hat. Für das &#x2A;*Alter eines [METAR](https://en.wikipedia.org/wiki/METAR)** verwenden Sie `parsed.time&#x60; (Beobachtungszeit aus dem Kopf der Meldung). Beim &#x2A;*[TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast)** sollten Sie `parsed.start_time` nicht als Maß für die Aktualität der Meldung verwenden — es markiert den Beginn des Gültigkeitszeitraums der Vorhersage. Zwischen den vier täglichen Ausgaben bleibt derselbe [TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) aktuell; fragen Sie höchstens alle 15–30 Minuten oder nach den erwarteten Ausgabezeiten (00Z, 06Z, 12Z, 18Z) ab.

```python
import requests
from datetime import datetime, timezone

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


def parse_observation_time(iso: str) -> datetime:
    return datetime.fromisoformat(iso.replace("Z", "+00:00"))


def observation_age_minutes(metar: dict) -> float:
    obs_time = parse_observation_time(metar["parsed"]["time"])
    now = datetime.now(timezone.utc)
    return (now - obs_time).total_seconds() / 60


def fetch_metar(icao: str) -> dict:
    r = requests.get(
        f"{BASE}/weather/metar/{icao}",
        headers=HEADERS,
        params={"parsed": "true"},
        timeout=(10, 15),
    )
    r.raise_for_status()
    return r.json()


# age = observation_age_minutes(data)
# if age > 90:
#     print(f"Warning: observation is {age:.0f} minutes old - station may be offline")
```

Liefert `observation_age_minutes` mehr als 90 Minuten, ist die Station wahrscheinlich offline oder meldet nur sporadisch. Sie können die Daten trotzdem anzeigen, sollten sie aber als möglicherweise veraltet kennzeichnen.

### Caching [#caching]

Antworten zu [METAR](https://en.wikipedia.org/wiki/METAR) und [TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) ändern sich im Verhältnis zur Aktualisierungsrate der UI nur langsam. Speichern Sie [METAR](https://en.wikipedia.org/wiki/METAR) 3–5 Minuten und [TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) 15–30 Minuten im Cache, statt die Daten bei jedem Rendern neu abzurufen.

| Endpoint                                | Empfohlene TTL | Cache-Schlüssel                   |
| --------------------------------------- | -------------- | --------------------------------- |
| `GET /weather/metar/{icao}`             | 3–5 Minuten    | `metar:{icao}`                    |
| `GET /weather/metar/{icao}?parsed=true` | 3–5 Minuten    | `metar:{icao}:parsed`             |
| `GET /weather/taf/{icao}`               | 15–30 Minuten  | `taf:{icao}`                      |
| `GET /weather/taf/{icao}?parsed=true`   | 15–30 Minuten  | `taf:{icao}:parsed`               |
| `GET /weather/winds-aloft?bbox=…`       | 30–60 Minuten  | `winds:{bbox}:{forecast}:{level}` |
| `GET /weather/pireps?bbox=…`            | 5–15 Minuten   | `pirep:{bbox}:{hours}`            |
| `GET /weather/airsigmet?bbox=…`         | 10–15 Minuten  | `airsig:{bbox}:{type}`            |

```python
import time
import requests

HEADERS = {
    "X-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]] = {}


def cached_get(url: str, params: dict, ttl_seconds: int) -> dict | None:
    key = url + str(sorted(params.items()))
    cached = _cache.get(key)
    if cached and time.time() < cached[1]:
        return cached[0]

    r = requests.get(url, headers=HEADERS, params=params, timeout=(10, 15))
    if r.status_code == 404:
        return None
    r.raise_for_status()
    data = r.json()
    _cache[key] = (data, time.time() + ttl_seconds)
    return data


def get_metar(icao: str) -> dict | None:
    return cached_get(f"{BASE}/weather/metar/{icao}", {"parsed": "true"}, ttl_seconds=300)


def get_taf(icao: str) -> dict | None:
    return cached_get(f"{BASE}/weather/taf/{icao}", {"parsed": "true"}, ttl_seconds=1800)
```

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]

Jede Antwort enthält Rate-Limit-Header:

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

Kontingente der Tarife:

| Tarif | Anfragen/Monat |
| ----- | -------------- |
| Free  | 1.000          |
| Basic | 5.000          |
| Pro   | 50.000         |
| Ultra | 200.000        |
| Mega  | 600.000        |

```python
import requests

HEADERS = {
    "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}


def check_quota() -> dict:
    r = requests.get(
        "https://skylink-api.p.rapidapi.com/weather/metar/KJFK",
        headers=HEADERS,
        timeout=(10, 15),
    )
    r.raise_for_status()

    remaining = int(r.headers.get("X-RateLimit-Requests-Remaining", 0))
    limit      = int(r.headers.get("X-RateLimit-Requests-Limit", 0))
    reset_in   = int(r.headers.get("X-RateLimit-Requests-Reset", 0))

    return {
        "remaining": remaining,
        "limit": limit,
        "reset_in": reset_in,
        "low_quota": remaining < 100,
    }


# quota = check_quota()
# print(f"Quota: {quota['remaining']}/{quota['limit']} remaining, resets in {quota['reset_in']}s")
#
# if quota["low_quota"]:
#     print("Warning: approaching quota limit - reduce polling frequency or upgrade plan")
```

Wenn Sie einen 429 erhalten, lautet der Antworttext `{"message": "Too many requests"}` (Struktur des RapidAPI-Gateways). Warten Sie per Backoff und versuchen Sie es erneut — siehe die [Retry-Hilfsfunktion](/docs/errors#retry-rules) auf der Plattformseite zur Fehlerbehandlung.

### Monitoring und Logging [#monitoring-and-logging]

Protokollieren Sie bei jeder Wetteranfrage Folgendes:

```python
import logging
import time
import requests
from datetime import datetime, timezone

logger = logging.getLogger("weather")
HEADERS = {
    "X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"


def fetch_metar_logged(icao: str) -> dict | None:
    start = time.monotonic()
    r = requests.get(f"{BASE}/weather/metar/{icao}", headers=HEADERS, params={"parsed": "true"}, timeout=(10, 15))
    elapsed_ms = (time.monotonic() - start) * 1000

    log_data = {
        "endpoint": "metar",
        "icao": icao,
        "status": r.status_code,
        "elapsed_ms": round(elapsed_ms),
        "quota_remaining": r.headers.get("X-RateLimit-Requests-Remaining"),
    }

    if r.status_code == 200:
        data = r.json()
        if "parsed" in data:
            obs_time = datetime.fromisoformat(data["parsed"]["time"].replace("Z", "+00:00"))
            age_min = (datetime.now(timezone.utc) - obs_time).total_seconds() / 60
            log_data["observation_age_min"] = round(age_min, 1)
            log_data["flight_rules"] = data["parsed"]["flight_rules"]
        logger.info("metar_fetch", extra=log_data)
        return data
    elif r.status_code == 404:
        logger.info("metar_not_found", extra=log_data)
        return None
    else:
        logger.warning("metar_error", extra=log_data)
        r.raise_for_status()
```

**Alarmieren bei:**

* `observation_age_min > 90` — die Station ist wahrscheinlich offline oder die Aktualisierungen sind verzögert
* `status = 429` mehr als zweimal innerhalb von 5 Minuten — Ihre Abfragefrequenz ist für Ihren Tarif zu hoch
* wiederholt `elapsed_ms > 5000` — Latenzproblem auf der Upstream-Seite; erwägen Sie einen Timeout mit Fallback
* `quota_remaining < 200` — Sie haben Ihr monatliches Limit fast erreicht

***

Siehe auch: [METAR](/docs/v31/weather-metar) · [TAF](/docs/v31/weather-taf) · [Höhenwinde](/docs/v31/winds-aloft) · [PIREPs](/docs/v31/pireps) · [AIRMET/SIGMET](/docs/v31/airsigmet) · [Fehlerbehandlung](/docs/v31/weather-integration-errors) · [Anwendungsfälle am Flughafen](/docs/v31/weather-integration-airport) · [Anwendungsfälle auf der Strecke (En-Route)](/docs/v31/weather-integration-enroute)
