- Beim Neubau eines US-Vergleichssystems für Medicare-Krankenversicherungspläne wurde die Erfahrung geteilt, mit einer einfachen Architektur, die ausschließlich aus bewährten Technologien (Postgres, golang, React usw.) besteht, mehr als 1 Milliarde Webanfragen stabil verarbeitet zu haben
- Die Architektur wurde mit dem Ziel von Einfachheit und Stabilität entworfen und erreichte eine durchschnittliche Antwortzeit von unter 10 ms sowie eine sehr niedrige Ausfallrate
- Innovation Tokens wurden nur minimal dort eingesetzt, wo die strukturelle Trennung entscheidend war (3 große Module, gRPC-Kommunikation); ansonsten wurden bewusst langweilige, aber verlässliche Methoden gewählt
- DB-Schema-Verwaltung, ETL-Pipeline, Tests, Logging, Dokumentation und CLI-Tools wurden durchgängig auf wiederholbare und einfache Weise aufgebaut, sodass ein System entstand, das das ganze Team leicht verstehen und warten kann
- Das Beispiel zeigt anschaulich, dass kontinuierliche Qualitätssicherung und starke Teamarbeit auch bei großen Regierungsprojekten funktionieren
Serving a billion web requests with boring code
High level
- Über zweieinhalb Jahre hinweg leitete der Autor die Entwicklung einer Website der US-Regierung zum Vergleichen und Kaufen von Medicare-Plänen
- Verarbeitung von durchschnittlich 5 Millionen API-Anfragen pro Tag, mit einer mittleren Antwortzeit von unter 10 ms; 95 % der Anfragen blieben unter 100 ms
- Die Ausfallhäufigkeit war extrem niedrig, sodass reale nächtliche Einsätze für Engineers an einer Hand abzuzählen waren
- Ausschließlich mit bewährten Technologien wie Postgres, golang und React, die jeder verstehen kann, wurde schrittweise ein stabiles System aufgebaut
Boring über alles
- Das oberste Prinzip war, nur „langweilige und bewährte Technologien“ zu priorisieren (Choose Boring Technology)
- Versuche mit Innovation wurden nur dort unternommen, wo sie wirklich nötig waren, und Innovation Tokens wurden sparsam eingesetzt
- Statt komplexer und spektakulärer Lösungen wurden stabile und klare Technologien und Prozesse bevorzugt
The boring bits
- Postgres: Kern der Datenspeicherung, erfüllte sowohl Anforderungen an Zuverlässigkeit als auch an Skalierbarkeit. Auch komplexe Suchen (Faceted Search usw.) wurden mit Postgres gelöst
- golang: Schnelle Builds und Deployments, klare Binärartefakte. Error Handling ist direkt nachvollziehbar, und neue Teammitglieder können sich leicht einarbeiten
- React: Das am stärksten bewährte SPA-Framework, mit dem das Team bereits vertraut war. Auch Barrierefreiheit und Unterstützung verschiedener Geräte waren wichtige Faktoren
- Langfristig gab es Probleme bei Bundle-Größe und sinkender Geschwindigkeit, aber unter den damaligen Bedingungen war es die beste Wahl, um rechtzeitig Ergebnisse zu liefern
The innovation tokens
- Modular backend: Das gesamte Backend wurde weder als Microservices noch als Monolith aufgebaut, sondern als 3 große Module (
druginfo, planinfo, beneinfo)
- Jedes Modul nutzt eine eigene Postgres-Datenbank, und Datenaustausch erfolgt ausschließlich über gRPC
druginfo: Indexiert Medikamentenpreisdaten äußerst präzise, bei denen Kombinationen aus Apotheke, Versicherung, Verpackung usw. exponentiell anwachsen; dafür waren komplexe Vorverarbeitung und Performance-Optimierung nötig
planinfo: Empfängt täglich neue CMS-Daten und erstellt daraus jeweils die komplette Datenbank neu, um Unveränderlichkeit zu bewahren
beneinfo: Der einzige Bereich, der echte Teilnehmerdaten speichert; sensible PII (personenbezogene Daten) werden nur minimal gehalten. Design und Betrieb wurden so ausgerichtet, das Risiko von Datenlecks zu minimieren
- gRPC: Bietet den Vorteil, Kommunikationsschnittstellen zwischen Modulen klar im Code definieren zu können. Sehr gut in Automatisierungswerkzeuge integrierbar
- Allerdings wurden auch Nachteile erlebt: Build, Tooling und Debugging sind komplex, und im Vergleich zu JSON-APIs ist es weniger intuitiv
- Über
grpc-gateway wurden Webclients unterstützt und auch große Traffic-Mengen problemlos verarbeitet
Strict backwards compatibility
- Abwärtskompatibilität von API und Datenbank wurde strikt eingehalten
- Felder öffentlicher APIs werden niemals entfernt und bleiben, sofern kein Sicherheitsproblem vorliegt, dauerhaft bestehen
- Auch DB-Spalten können zwar frei hinzugefügt werden, beim Entfernen gilt aber ein mehrstufiges Prüfverfahren (Referenzen entfernen → einige Wochen warten → tatsächlich löschen)
- Diese Disziplin war die zentrale Grundlage für hohe Änderungsgeschwindigkeit sowie stabile Deployments und den laufenden Betrieb
Faceted search
- Faceted Search nur mit Postgres statt mit ElasticSearch umgesetzt
- Die gesamte Suchlogik wurde in einer einzigen Funktion mit 250 Zeilen umgesetzt, die Bedingungen auf eine gut indexierte
plan-Tabelle kombiniert
- Der Fokus lag auf den Geschäftsanforderungen, und die Lösung blieb ohne unnötige Komplexität einfach
Database
-
creation
- Das DB-Schema wurde über nummerierte
.sql-Dateien verwaltet, die in Reihenfolge geladen werden und so Zuverlässigkeit sicherstellen
- Die
planinfo-/beneinfo-Datenbanken werden täglich neu erstellt, Migrationen sind nicht nötig. Bei Konfigurationsfehlern wie Versionsabweichungen startet die Anwendung bewusst gar nicht erst
-
ETL
- Daten werden per Shell-Skript je Quelle nach S3 geladen; per cron holen EC2-Instanzen den neuesten ETL-Code und die Daten ab und erzeugen eine neue RDS-Datenbank
- Die COPY-Anweisung von Postgres wurde aktiv genutzt, um Massendaten effizienter als mit
INSERT zu laden
- Innerhalb von 2 bis 4 Stunden pro Tag konnten so Datenbanken mit hunderten Millionen Zeilen auf neue Versionen umgestellt werden
-
models
- Mit der Bibliothek xo wurden DB-Modelle automatisch erzeugt; per Custom Templates wurde die Codegenerierung ans Team angepasst
-
testing
- Der größte Fehler war, Tests mit
sqlmock übermäßig auszubauen, sodass sie bei häufig wechselnden Daten sehr aufwendig zu pflegen waren
- Bei einer real unveränderlichen Datenbank wären Tests gegen eine echte DB effizienter gewesen
-
Local database for development
- Mit Skripten, die automatisch Teilmengen jeder Tabelle erzeugen, konnte jede Entwicklerin und jeder Entwickler mit einer kleinen lokalen DB auf Basis realer Daten testen und entwickeln
- Solche Werkzeuge früh bereitzustellen, bevor die DB zu groß wird, maximiert die Entwicklungseffizienz des gesamten Teams
Miscellaneous tooling
- Für verschiedenste Betriebs- und Observability-Automatisierungen wurden CLI-Tools als Shell-Skripte umgesetzt und sämtliche Utility-Funktionen zentral gebündelt
- Praxisnahe Werkzeuge wurden aktiv entwickelt und genutzt, etwa um Splunk-Logs direkt per Slack-Befehl als Diagramme zu visualisieren
Logging
- Beim Eintreffen einer Anfrage wird eine Request-ID erzeugt, die an alle Logging-Kontexte angehängt wird und so Nachverfolgung an jeder Stelle ermöglicht
- Mit zerolog wurde ein sicheres und strukturiertes Logging-Design umgesetzt
- An wichtigen Punkten wie Systemeintritt und -austritt oder Ausnahmesituationen werden zwingend Logs geschrieben
Documentation
- GitHub-Markdown-Dokumente wurden mit sphinx-book-theme in ein Wiki-Book umgewandelt
- Das gesamte Team trug aktiv zur Dokumentation bei, sodass sämtliche Systemdokumente an einer zentralen Stelle auffindbar waren
- Eine starke Dokumentationskultur verbesserte Teamwachstum, Wartbarkeit und die Effizienz beim Onboarding neuer Mitarbeitender deutlich
Runtime integrations
- Kundenwünsche, die die Performance des Clients verschlechtern konnten (z. B. das Einfügen von Analyse-Skripten), wurden nach Möglichkeit auf ein Minimum reduziert
- Auch Queries wurden von Browser-Runtime auf Build-Time verlagert, um die Service-Performance zu erhalten
- In der Praxis ließen sich allerdings nicht alle Kundenanforderungen abwehren, sodass einige tatsächlich zu Performance-Verlusten führten
And more
- Es wird betont, dass neben der Technik vor allem eine positive, kooperative Teamatmosphäre und starke Motivation der eigentliche Antrieb für den Erfolg großer Systeme sind
- Der Fall zeigte eindrücklich die Wirkung kleiner, aber wichtiger praktischer Entscheidungen und konsequenter Qualitätsarbeit
18 Kommentare
2,5 Jahre lang so etwas Langweiliges?!
Ich habe mich gefragt, was Faceted Search ist, und bin dem nachgegangen; dort gibt es noch mehr Lesenswertes.
https://www.cybertec-postgresql.com/en/faceting-large-result-sets/
https://roaringbitmap.org/about/
https://github.com/cybertec-postgresql/pgfaceting
Die Meinungen zu „langweilig“ sind ja interessant, haha. Wenn man es anders ausdrücken wollte, was wäre gut? Vorhersehbar, gewöhnlich?
Die Übersetzung von
boringmit „langweilig“ trifft die eigentliche Bedeutung leider überhaupt nicht.boringnessist eine der Go-Designphilosophien.Sooo langweilig …
In Korea ist am Ende doch alles Java-Land, deshalb wirkt das wohl ungewohnt, haha
Ich halte sowohl Golang als auch React für langweilige („boring“) Enterprise-Coding-Sprachen des neuen Zeitalters.
Da sich
boringnicht zu 100 % treffend mit „langweilig“ übersetzen lässt, scheint die Nuance bei koreanischen Lesern nicht richtig anzukommen.Ich möchte in einer langweiligen Welt mit Postgres, golang und React leben.
Stimmt, ich dachte beim Lesen des Titels auch zuerst, das wäre ein Witz.
Im Ausland scheint das ein langweiliger Stack zu sein.
Tatsächlich ist Go einfach die leichteste Wahl, wenn man einen Webserver bauen will …
Offenbar gilt es nur dann nicht als langweilig, wenn man mit Rust oder Sprachen aus dem FP-Bereich entwickelt.
Allzu selbstverständliche Dinge … so selbstverständlich, dass man wichtige Punkte übersieht.
Dieser Stack wirkt auf diesem Niveau gar nicht so langweilig. Wenn es wirklich langweilig wäre, müssten wohl eher Java 1.8 oder älter oder vielleicht VB auftauchen … so ein etwas respektloser Gedanke kommt mir dabei.
>Das Schöne an der Banalität (so eingeschränkt) ist, dass die Fähigkeiten dieser Dinge gut verstanden sind. Aber noch wichtiger: Auch ihre Ausfallmodi sind gut verstanden.
Im Original gibt es einen Link zu
boring; dem Inhalt nach scheintboringhier „so vertraut“ zu bedeuten.Es gäbe passendere Wörter wie
experienced,verifiedoderskillful, aber dass hier unbedingtboringverwendet wurde, wirkt, als hätte das die Absicht gehabt, Aufmerksamkeit zu erregen.Ist damit nicht eher gemeint, dass es kein langweiliger Stack zum Schreiben ist, sondern ein „Gukbap-Stack“, der langweilig wirkt, weil man ihn einfach zu oft geschrieben hat?
Etwa Linux-Kernel 2.6.29 ...
Allein schon die Tatsache, dass gRPC verwendet wurde … haha
Ich dachte auch zuerst: Go soll langweilig sein?
So etwas wie Classic ASP könnte man vielleicht als langweilig bezeichnen.