2.6 KiB
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
- Precompute-Skript (
gtfs_pfade_cache.py): iteriertSELECT DISTINCT von,nachausurlaub.db, ruft für jedes Paarrouter.path(von,nach)(liefert Segmentliste der Zwischenhalte), schreibtpfade_cache.json:{"<von>|<nach>": {"seg": [["Halt1","Halt2",...]], "dauer_h": 1.5}} - Flask-Route liest NUR
json.load(open('/opt/<app>/pfade_cache.json'))mit Keyf"{start}|{ziel}"→ Antwort < 1 s, kein Timeout. Nie den Router zur Laufzeit importieren/rechnen. - 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
# 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).