SZTech Notes

Persönliche Notizen zu Backend-Engineering & Infrastruktur

12. März 2026

Connection Pooling in PostgreSQL: PgBouncer vs. Pgpool-II im Jahr 2026

Nach der Migration von drei Produktionsdiensten auf PostgreSQL 17 hatte ich endlich Zeit, unser Connection-Pooling-Setup ordentlich zu benchmarken. Die Kurzfassung: PgBouncer im Transaction-Modus ist für die meisten Workloads nach wie vor die richtige Wahl, aber es gibt Randfälle, in denen das Query-Caching von Pgpool-II spürbare Vorteile bringt.

Das Testsetup war unkompliziert — eine 4-Kern-VM mit 8 GB RAM, PG17 auf Debian 12, ein separater Benchmarking-Host mit pgbench bei 200 gleichzeitigen Verbindungen und jeder Pooler mit einer Pool-Größe von 25 konfiguriert. Nichts Besonderes.

Was mich überrascht hat, war die Latenzverteilung unter Dauerlast. PgBouncer zeigte p99-Latenzen von ~12 ms für einfache SELECT-Abfragen, während Pgpool-II bei etwa 18 ms für denselben Workload lag. Aber sobald wir komplexe JOINs hinzufügten und der Query-Cache aufgewärmt war, sank Pgpool-IIs p50 auf 4 ms — verglichen mit PgBouncers konstanten 8 ms.

# PgBouncer-Konfiguration, die bei uns am besten funktioniert hat
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_port = 6432
pool_mode = transaction
max_client_conn = 400
default_pool_size = 25
reserve_pool_size = 5
server_idle_timeout = 300

Das eigentliche Fazit: Wenn deine Anwendung hauptsächlich einzigartige Abfragen mit variablen Parametern ausführt, bleib bei PgBouncer. Wenn du einen leseintensiven Workload mit wiederkehrenden Abfragemustern hast, verdient Pgpool-II einen zweiten Blick. Wir haben PgBouncer für unsere schreibintensiven Dienste behalten und testen Pgpool-II für das Reporting-Backend.

postgresql infrastruktur benchmarks

28. Februar 2026

Cron durch systemd-Timer ersetzen: Eine praktische Migration

Ich habe das jahrelang aufgeschoben, aber letzten Monat endlich alle unsere Cron-Jobs auf systemd-Timer migriert. Der Auslöser war ein stiller Cron-Ausfall, der 11 Tage lang unbemerkt blieb — ein Log-Rotationsskript, das nach einem System-Update wegen eines PATH-Problems nicht mehr lief. Mit systemd-Timern wäre dieser Fehler sofort in systemctl --failed sichtbar gewesen.

Die Migration selbst war mechanische Arbeit. Jeder Cron-Eintrag wird zu zwei Dateien: einer .service-Unit, die beschreibt, was ausgeführt werden soll, und einer .timer-Unit, die beschreibt, wann es ausgeführt werden soll. Die Ausführlichkeit nervt anfangs, aber das integrierte Logging, Abhängigkeitsmanagement und die Ressourcenkontrollen sind es wert.

# /etc/systemd/system/backup-db.service
[Unit]
Description=Tägliches Datenbank-Backup
After=postgresql.service

[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup-postgres.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/backup-db.timer
[Unit]
Description=DB-Backup täglich um 3:00 UTC ausführen

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Das Persistent=true-Flag ist die entscheidende Funktion, die Cron fehlt — wenn der Server zum geplanten Zeitpunkt heruntergefahren war, führt systemd den Job beim nächsten Boot aus. Kombiniert mit RandomizedDelaySec zur Vermeidung von Thundering-Herd-Problemen bei Multi-Server-Setups ist das eine strikt bessere Lösung für geplante Aufgaben.

Gesamte Migrationszeit für 14 Cron-Jobs auf 6 Servern: etwa 4 Stunden. Hätte ich schon vor Jahren machen sollen.

systemd linux devops

14. Februar 2026

Notizen zum Wechsel von Nginx zu Caddy als Reverse Proxy

Das hier begann als schnelles Experiment und wurde dann dauerhaft. Wir hatten eine Staging-Umgebung mit Nginx als Reverse Proxy für 5 Backend-Dienste, und das Zertifikats-Erneuerungs-Setup (certbot + cron + Reload-Hooks) funktionierte nach einem OS-Upgrade nicht mehr. Anstatt es erneut zu debuggen, habe ich beschlossen, Caddy als Drop-in-Ersatz zu testen.

Allein das automatische HTTPS hat den Wechsel gerechtfertigt. Kein certbot, kein Erneuerungs-Cron, keine Reload-Skripte — Caddy erledigt alles intern über ACME. Die Caddyfile-Syntax ist erfrischend einfach im Vergleich zur nginx.conf, obwohl das JSON-Konfigurationsformat mehr Kontrolle für komplexe Setups bietet.

Die Performance war vergleichbar. Unter unserer typischen Last (~2000 Req/s an der Proxy-Schicht) zeigten sowohl Caddy als auch Nginx nahezu identische Latenzverteilungen. Caddy verbrauchte etwas mehr Speicher (~40 MB gegenüber ~25 MB bei Nginx), was für einen Proxy irrelevant ist, aber erwähnenswert.

Der Hauptnachteil: weniger StackOverflow-Antworten, wenn etwas schiefgeht. Die Caddy-Community-Foren sind hilfreich, aber man stößt gelegentlich auf Konfigurationen, bei denen die Nginx-Lösung ein Einzeiler ist und das Caddy-Äquivalent kreatives Denken erfordert.

caddy nginx reverse-proxy

30. Januar 2026

Docker Compose V2: Drei Stolperfallen, die meinen Nachmittag gekostet haben

Letzte Woche haben wir den letzten docker-compose-Aufruf (V1) aus unserer CI-Pipeline entfernt. Die Migration zu docker compose (V2, integriert als Docker-CLI-Plugin) verlief größtenteils reibungslos, aber drei Probleme haben mich zusammen etwa 4 Stunden Debugging gekostet.

Erstens die Container-Benennung. V1 verwendete Unterstriche (project_service_1), V2 verwendet Bindestriche (project-service-1). Jedes Skript, das Container namentlich referenzierte, brach stillschweigend. Zweitens verhält sich das --compatibility-Flag in V2 nicht identisch zur deploy-Sektion in V1 — wir hatten Ressourcenlimits, die stillschweigend ignoriert wurden. Drittens erfordert depends_on mit Health-Checks jetzt ein explizites condition: service_healthy, was in V1 bei definiertem Healthcheck das Standardverhalten war.

Nichts davon sind Bugs — es sind dokumentierte Breaking Changes. Aber zusammengenommen machten sie aus einem 20-minütigen sed-Ersetzungsjob einen ganzen Nachmittag voller Tests.

docker container devops