3 Performance und Optimierungen
Ralf Warmuth edited this page 2026-01-09 22:32:38 +01:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Performance & Optimierungen (Code-getrieben)

Diese Seite erklärt die wichtigsten Performance-Optimierungen im Projekt: was sie tun, warum sie helfen und wo sie im Code umgesetzt sind.

Begriffe (WAL, PRAGMA, PRG, etc.): siehe Glossar.

Leitgedanke

Das System ist “einfach”, aber unter Last entstehen Engpässe vor allem durch:

  • SQLite-Overhead (viele Verbindungen, Locking),
  • unnötige Queries / File-I/O,
  • synchronen Mailversand,
  • Caches (fehlend oder zu aggressiv).

1) SQLite: Connection-Pooling + PRAGMAs

Wo: zgb-backend/Repository/Repository.php (Repository::CreatePDO()).

Connection-Pooling (Reuse)

  • Pro DB-Pfad wird eine PDO-Verbindung in einem statischen Pool wiederverwendet.
  • Vor Reuse wird SELECT 1 geprüft (falls Verbindung kaputt ist, wird sie neu erstellt).

Warum das hilft: SQLite ist dateibasiert. Viele kurzlebige Connections erzeugen unnötigen Overhead und können Locking-Probleme verstärken.

PRAGMA-Einstellungen

Im Code werden (konservativ) diese PRAGMAs gesetzt:

  • PRAGMA journal_mode = WAL
    • Warum: WAL erlaubt parallele Reads/Writes besser (wichtig bei gleichzeitigen Zugriffen).
  • PRAGMA synchronous = NORMAL
    • Warum: weniger fsync-Overhead → schneller (mit akzeptablem Risiko).
  • PRAGMA cache_size = 10000
    • Warum: mehr SQLite-Cache → weniger Disk-I/O.
  • PRAGMA temp_store = MEMORY
    • Warum: temporäre Daten im RAM → schneller.

2) Schema-Init nur einmal pro PDO

Wo: RegistrationRepository, ConfigRepository, MailQueueRepository.

  • Jede Repo-Klasse führt CREATE TABLE IF NOT EXISTS ... nur einmal pro PDO-Instanz aus (statisches Flag spl_object_hash($pdo)).

Warum: CREATE TABLE IF NOT EXISTS ist zwar idempotent, aber unnötiger Overhead, wenn es bei jeder Instanziierung läuft.

3) SQL-Datei-Caching

Wo: zgb-backend/Repository/Repository.php (getSQL()).

  • SQL-Dateien aus Repository/Schema/* werden einmal gelesen und dann aus einem statischen Cache geliefert.

Warum: spart File-I/O, besonders bei vielen Requests/Queries.

4) Config-Caching (TTL)

Wo: zgb-backend/Repository/ConfigRepository.php.

  • loadConfig() verwendet einen statischen Cache pro PDO-Instanz (TTL: 60s).
  • Bei setConfig()/saveConfig() wird der Cache invalidiert.

Warum: loadConfig() würde sonst viele einzelne getConfig()-Reads erzeugen. Mit Cache werden DB-Reads stark reduziert.

5) Registrierungsnummern: weniger Daten laden + atomare Vergabe

Wo: zgb-backend/Repository/RegistrationRepository.php und static/registration/index.php.

Effizientere “freie Nummer” Suche

  • getFirstFreeNumber(start, end) lädt nur die belegten number im Bereich und sucht dann in PHP mit O(1)-Lookups.

Warum: reduziert Datenmenge und CPU gegenüber “alle Registrierungen laden”.

Atomare Vergabe beim POST (Race-Fix)

  • Nummern werden erst beim POST vergeben und in SQLite mit BEGIN IMMEDIATE serialisiert.

Warum: verhindert Race-Conditions, wenn viele Nutzer gleichzeitig anmelden.

6) Mailversand: Queue + Rate-Limit (Token-Bucket)

Wo: zgb-backend/Service/MailQueueWorker.php, static/registration/index.php.

  • Statt synchroner mail()-Calls im User-Request werden Jobs enqueued.
  • Cron-Worker sendet kontrolliert nach, mit Token-Bucket Rate-Limit.

Warum: stabilere Requests, Einhaltung von Provider-Limits, Retry/Backoff, weniger Lastspitzen.

Siehe Details: Mail-Queue

7) API-Caching: Event-Datum

Wo: static/api/event-date.php + static/assets/script.js.

  • Browser-Cache: 30 Sekunden
  • Server-Cache: 60 Sekunden (file-basiert in public/data/.event-date-cache.json)
  • JS nutzt einen 30s Cache-Buster (damit das Frontend zeitnah aktualisiert)

Warum: statische HTML-Seiten können das Event-Datum nicht aus der DB lesen; die API ist eine kleine Brücke, die trotzdem performant bleibt.

8) Build: Copy-Optimierungen / Robustheit

Wo: scripts/build.mjs.

  • Statische Dateien werden rekursiv kopiert.
  • Einige unnötige Duplikate werden übersprungen (Original/, doppelte logo-svg.svg).
  • Kopieren von Dotfiles ist robust gegen Deploy-Setups, die Dotfiles verlieren (warnen statt Build hart zu brechen).

Warum: Build-Zeit/Artefakte klein halten und Deploy-Fehler vermeiden.

9) Kleine aber wichtige Sicherheits-/Stabilitätsoptimierungen

  • Mail Header Injection Schutz (CR/LF Checks) + RFC2047-Betreff-Encoding:
    • Wo: zgb-backend/Service/MailService.php
  • Session-Härtung (Cookie-Flags, SameSite):
    • Wo: zgb-backend/Service/SessionService.php

Warum: verhindert “billige” Angriffe und reduziert Support-Fälle indirekt auch Performance (weniger Failures/Noise).