12. März 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
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
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
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