53 lines
2.6 KiB
Markdown
53 lines
2.6 KiB
Markdown
# GTFS-Verbindungs-PDF: Offline-Precompute statt Live-Routing (2026-08-19)
|
||
|
||
Bewährt für `urlaub.perfekt-ohne-en.de` (Flask + SQLite + GTFS-Zugdaten). Wenn eine
|
||
Einzel-Verbindungs-PDF eine **komplette Fahrplan-Verbindung inkl. Haltestellenliste**
|
||
zeigen soll, darf die Flask-Route den Router/RAPTOR/Dijkstra **nicht bei jedem Request**
|
||
ausführen.
|
||
|
||
## Symptom
|
||
|
||
`wkhtmltopdf` + RAPTOR auf großen Knoten (Köln, Berlin) lief 40–200 s je Aufruf →
|
||
**nginx 504 Gateway Timeout** (Gateway-Zeitlimit überschritten). Nutzer meldete
|
||
„Wenn ich die PDF anklicke … keine Haltestellenliste".
|
||
|
||
## Fix: einmaliger Offline-Backfill → JSON-Cache → schneller Cache-Read
|
||
|
||
1. **Precompute-Skript** (`gtfs_pfade_cache.py`): iteriert `SELECT DISTINCT von,nach`
|
||
aus `urlaub.db`, ruft für jedes Paar `router.path(von,nach)` (liefert Segmentliste
|
||
der Zwischenhalte), schreibt `pfade_cache.json`:
|
||
```json
|
||
{"<von>|<nach>": {"seg": [["Halt1","Halt2",...]], "dauer_h": 1.5}}
|
||
```
|
||
2. **Flask-Route** liest NUR `json.load(open('/opt/<app>/pfade_cache.json'))` mit
|
||
Key `f"{start}|{ziel}"` → Antwort < 1 s, kein Timeout. **Nie** den Router zur
|
||
Laufzeit importieren/rechnen.
|
||
3. Skript als **Hintergrund-Job** (background=true, notify_on_complete) laufen lassen,
|
||
nicht foreground.
|
||
|
||
## Teilmenge wählen, sonst quadratische Backfill-Zeit
|
||
|
||
Eindeutige `von|nach` über ALLE Ebenen waren **10 510 Paare** → ~2,4 s/Paar = **~7 h**.
|
||
Auf die **Ebenen 1+2** (`WHERE ebene IN (1,2)`, ~1 979 Paare) beschränkt = **~35 min**.
|
||
Ebene-3 (Kleinstädte) = reine Verzweigung ohne eigene PDFs → weglassen.
|
||
|
||
- Trefferquote ist KEINE 100 % (Radevormwald/Kleinstädte ohne Bahnhof im Feed).
|
||
Template ehrlich auf „Keine GTFS-Haltestellenliste verfügbar" zurückfallen, nie erfinden.
|
||
|
||
## Pfad-Rekonstruktion muss dasselbe Abfahrtsprofil wie die Dauer nutzen
|
||
|
||
Ein Precompute mit **fester Abfahrtszeit (09:00)** rekonstruierte nur ~34 % der Pfade,
|
||
weil viele Verbindungen erst zu späteren Tageszeiten existieren. Die Dauer-Profile-Suche
|
||
(06–22 Uhr) liefert mehr Treffer — die Pfad-Rekonstruktion muss **dasselbe Profil**
|
||
durchlaufen statt fest 09:00, sonst bleibt die Trefferquote konsistent niedrig.
|
||
|
||
## Verifikation
|
||
|
||
```bash
|
||
# Service nach Template-/Code-Änderung neu starten, dann:
|
||
curl -sk -o /dev/null -w "PDF: %{http_code} (%{time_total}s)\n" \
|
||
"https://<sub>/pdf/<von>/<nach>"
|
||
# Erwartung: 200 in <1 s (nicht 504), Haltestellenliste enthalten sobald Cache da.
|
||
```
|
||
Flask läuft mit `debug=False` (systemd) → **nach Änderung IMMER `systemctl restart`**
|
||
(wg. Template-/Code-Reload-Pitfall, siehe Haupt-SKILL). |