Perf: 9 Performance-Fixes — SW by-v1072

Backend:
- DB: 3 neue Indizes (forum_posts thread+user, routes user) — Forum/Routen-Queries
- Caching: cache.py (TTL-Cache ohne neue Dependency) für 5 statische Listen
  (training_exercises, pflege_tipps, wiki_stats, wiki_gruppen, help_articles)
- diary.py + breeder_photos.py: Bildverarbeitung (ffmpeg/PIL/EXIF) per
  run_in_executor → blockiert Event-Loop nicht mehr
- scheduler.py: 11 kollidierende Jobs auf 5-Min-Intervalle gestaggert, coalesce=True
- social.py: ORDER BY RANDOM() ohne LIMIT in 2 Stellen gefixt
- alerts.py: Haversine-Loop bekommt SQL-Bounding-Box-Vorfilter

Frontend:
- sw.js: Tile-Cache mit LRU-Eviction (max 500 Einträge)
- admin.js: Event-Listener-Leak — Tab-Klicks per Delegation statt N Listener
- api.js: compressImage() Helper — Client-seitiges Resize auf max 2000px
  (HEIC/Videos/<500KB unverändert), integriert in 8 Upload-Stellen
  (diary, dog-profile×2, walks, poison, lost, health×2)

Bump APP_VER 1071 → 1072 (sw.js, app.js, main.py, index.html)
This commit is contained in:
rene 2026-05-26 06:30:36 +02:00
parent 3abf974d29
commit c03884cb81
23 changed files with 461 additions and 120 deletions

View file

@ -24,12 +24,19 @@ _job_log: dict = {}
def start():
# ------------------------------------------------------------------
# Job-Staffelung in 5-Minuten-Intervallen — verhindert gleichzeitige
# Last-Spitzen (mehrere Jobs zur selben Sekunde 08:00 Uhr).
# coalesce=True: bei verpassten Läufen nur ein Lauf nachholen.
# misfire_grace_time: Mindestwert 300s, höher wo Job lange dauern kann.
# ------------------------------------------------------------------
_scheduler.add_job(
_job_health_reminders,
CronTrigger(hour=8, minute=0), # täglich 08:00 Uhr
id="health_reminders",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.add_job(
_job_poison_archive,
@ -37,6 +44,7 @@ def start():
id="poison_archive",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.add_job(
_job_weather_alert,
@ -44,6 +52,7 @@ def start():
id="weather_alert",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.add_job(
_job_milestone_check,
@ -51,6 +60,7 @@ def start():
id="milestone_check",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.add_job(
_job_import_events,
@ -58,6 +68,7 @@ def start():
id="import_events",
replace_existing=True,
misfire_grace_time=7200,
coalesce=True,
)
# Einmalig beim Start (nach 10s Verzögerung) für sofortige Befüllung
@ -68,29 +79,32 @@ def start():
id="import_events_startup",
replace_existing=True,
)
# Alle 4 Wochen Di 03:00 — Rassen aus TheDogAPI aktualisieren
# 1. des Monats 03:00 — Rassen aus TheDogAPI aktualisieren
_scheduler.add_job(
_job_seed_breeds,
CronTrigger(day=1, hour=3, minute=0), # 1. jedes Monats
id="seed_breeds",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Alle 4 Wochen Di 04:00 — fehlende Rassen aus Wikidata ergänzen
# 1. des Monats 04:00 — fehlende Rassen aus Wikidata ergänzen
_scheduler.add_job(
_job_seed_wikidata_breeds,
CronTrigger(day=1, hour=4, minute=0), # 1. jedes Monats
id="seed_wikidata",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Jeden Montag 09:00 — Wöchentlicher Fortschritts-Lober
# Jeden Montag 09:05 — Wöchentlicher Fortschritts-Lober (staggered)
_scheduler.add_job(
_job_weekly_praise,
CronTrigger(day_of_week='mon', hour=9, minute=0),
CronTrigger(day_of_week='mon', hour=9, minute=5),
id="weekly_praise",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 06:00 Uhr Status-Report per Mail
_scheduler.add_job(
@ -99,6 +113,7 @@ def start():
id="status_report",
replace_existing=True,
misfire_grace_time=1800,
coalesce=True,
)
# Täglich 12:00 — Moderation-Overdue-Check
_scheduler.add_job(
@ -107,22 +122,25 @@ def start():
id="moderation_overdue",
replace_existing=True,
misfire_grace_time=1800,
coalesce=True,
)
# 1. Feb / Mai / Aug / Nov 07:00 — Quartalsbericht
# 1. Feb / Mai / Aug / Nov 07:10 — Quartalsbericht (staggered weg von 07:00)
_scheduler.add_job(
_job_quarterly_report,
CronTrigger(month="2,5,8,11", day=1, hour=7, minute=0),
CronTrigger(month="2,5,8,11", day=1, hour=7, minute=10),
id="quarterly_report",
replace_existing=True,
misfire_grace_time=7200,
coalesce=True,
)
# Jeden Montag 07:00 — KI-Gesundheitsberichte (alle 2 Wochen)
# Jeden Montag 07:05 — KI-Gesundheitsberichte (staggered weg von 07:00)
_scheduler.add_job(
_job_ki_health_report,
CronTrigger(day_of_week='mon', hour=7, minute=0),
CronTrigger(day_of_week='mon', hour=7, minute=5),
id="ki_health_report",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 06:30 — Wiederkehrende Ausgaben anlegen
_scheduler.add_job(
@ -131,6 +149,7 @@ def start():
id="recurring_expenses",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# 1. des Monats 00:05 — Hund des Monats Sieger festlegen
_scheduler.add_job(
@ -139,6 +158,7 @@ def start():
id="hdm_winner",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 19:00 Uhr — Streak-Erinnerung
_scheduler.add_job(
@ -147,22 +167,25 @@ def start():
id="streak_reminder",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 08:00 Uhr — Tierfutter-Rückrufe prüfen (RASFF)
# Täglich 08:05 Uhr — Tierfutter-Rückrufe prüfen (RASFF) (staggered weg von 08:00)
_scheduler.add_job(
_job_recall_check,
CronTrigger(hour=8, minute=0),
CronTrigger(hour=8, minute=5),
id="recall_check",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Jeden Montag 08:00 Uhr — Neue Foto-Challenge anlegen
# Jeden Montag 08:10 Uhr — Neue Foto-Challenge anlegen (staggered weg von 08:00)
_scheduler.add_job(
_job_new_foto_challenge,
CronTrigger(day_of_week='mon', hour=8, minute=0),
CronTrigger(day_of_week='mon', hour=8, minute=10),
id="new_foto_challenge",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 07:00 Uhr — Goldene Gassi-Stunde
_scheduler.add_job(
@ -171,6 +194,7 @@ def start():
id="golden_gassi_hour",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 09:00 Uhr — Jahrestags-Erinnerungen (Tagebuch-Einträge von heute vor X Jahren)
_scheduler.add_job(
@ -179,6 +203,7 @@ def start():
id="anniversary_reminders",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# 1. des Monats 10:00 — Monatlicher Rückblick per Push
_scheduler.add_job(
@ -187,13 +212,16 @@ def start():
id="monthly_recap",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
# Täglich 03:15 — Abo-Ablauf prüfen (staggered weg von 03:00 poison_archive)
_scheduler.add_job(
_job_subscription_check,
CronTrigger(hour=3, minute=0),
CronTrigger(hour=3, minute=15),
id="subscription_check",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.add_job(
_job_invoice_reminder,
@ -201,9 +229,10 @@ def start():
id="invoice_reminder",
replace_existing=True,
misfire_grace_time=3600,
coalesce=True,
)
_scheduler.start()
logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00, Streak-Reminder 19:00, Rückruf-Check 08:00, Goldene-Gassi-Stunde 07:00, Jahrestags-Erinnerungen 09:00, Monatlicher-Rückblick 1. des Monats 10:00, Foto-Challenge Mo 08:00, Abo-Check 03:00. OSM-Cache: on-demand (kein Prewarm).")
logger.info("Scheduler gestartet (gestaffelt) — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import 1.+2./4./7./10. 02:00, Rassen-Seed 1. 03:00, Wikidata-Seed 1. 04:00, Status-Report 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:10, KI-Gesundheitsbericht Mo 07:05, Streak-Reminder 19:00, Rückruf-Check 08:05, Goldene-Gassi-Stunde 07:00, Jahrestags-Erinnerungen 09:00, Monatlicher-Rückblick 1. 10:00, Foto-Challenge Mo 08:10, Weekly-Praise Mo 09:05, Abo-Check 03:15, Invoice-Reminder 08:30. OSM-Cache: on-demand (kein Prewarm).")
def stop():