Gutes Systemdesign
(seangoedecke.com)- Gutes Systemdesign ist eine Form, die nicht komplex wirkt und über lange Zeit keine nennenswerten Probleme verursacht
- Der Umgang mit Zustand (state) ist der schwierigste Teil beim Systemdesign, und wichtig ist, die Zahl der Komponenten, die Zustand speichern, möglichst klein zu halten
- Die Datenbank ist meist der Ort, an dem Zustand gespeichert wird; daher ist ein Ansatz nötig, der sich auf Schema-Design und Indizierung sowie auf die Beseitigung von Engpässen konzentriert
- Caching, Event-Verarbeitung und Hintergrundaufgaben sollten für Performance und Wartbarkeit mit Bedacht eingeführt werden; übermäßiger Einsatz ist zu vermeiden
- Statt komplexer Entwürfe ist der angemessene Einsatz hinreichend bewährter einfacher Komponenten und Methoden entscheidend für den Aufbau nachhaltiger und stabiler Systeme
Definition von Systemdesign und allgemeiner Ansatz
- Wenn Softwaredesign das Zusammensetzen von Code ist, dann ist Systemdesign der Prozess, verschiedene Services zu kombinieren
- Zu den wichtigsten Bestandteilen des Systemdesigns gehören App-Server, Datenbanken, Caches, Queues, Event-Busse und Proxys
- Gutes Design ruft Reaktionen hervor wie „es gibt keine besonderen Probleme“, „es war einfacher als gedacht erledigt“ oder „darum muss man sich nicht kümmern“
- Umgekehrt kann ein komplexes und auffälliges Design grundlegende Probleme verdecken oder auf Overengineering hinweisen
- Statt komplexe Systeme von Anfang an direkt einzuführen, ist es vorteilhafter, von einer minimal funktionsfähigen einfachen Struktur aus schrittweise weiterzuentwickeln
Unterscheidung zwischen Zustand (state) und Stateless
- Der schwierigste Teil im Softwaredesign ist das Zustandsmanagement
- Services, die keine Informationen speichern und sofort ein Ergebnis zurückgeben (wie das PDF-Rendering von GitHub), sind stateless
- Dagegen verwalten Services, die Schreibvorgänge in einer Datenbank durchführen, Zustand
- Es ist gut, die Zahl der zustandsspeichernden Komponenten im System so weit wie möglich zu reduzieren. Das senkt die Komplexität des Systems und die Wahrscheinlichkeit von Ausfällen
- Empfehlenswert ist eine Struktur, in der nur ein Service das Zustandsmanagement übernimmt und die übrigen Services sich auf stateless Rollen wie API-Aufrufe oder das Auslösen von Events konzentrieren
Datenbankdesign und Engpässe
Schema- und Index-Design
- Für die Datenspeicherung ist ein für Menschen gut lesbares Schema-Design erforderlich
- Ein zu flexibles Schema (z. B. alles in einer JSON-Spalte zu speichern) kann Anwendungscode und Performance belasten
- Für Spalten, auf denen häufig Queries ausgeführt werden, sollten geeignete Indizes gesetzt werden. Alles zu indizieren erzeugt im Gegenteil unnötigen Overhead
Methoden zur Beseitigung von Engpässen
- Datenbankzugriffe werden oft zu schweren Engpässen
- Nach Möglichkeit ist es aus Performance-Sicht günstiger, komplexe Daten nicht in der Anwendung, sondern innerhalb der Datenbank mit JOINs usw. zu verarbeiten
- Bei der Nutzung von ORM sollte man Fehler vermeiden, bei denen innerhalb von Schleifen Queries ausgelöst werden
- Je nach Bedarf kann es auch eine Methode sein, Queries aufzuteilen, um die Last auf der Datenbank oder die Query-Komplexität zu steuern
- Die Strategie, Lese-Queries auf Replikate (read replicas) zu verteilen, ist wirksam, um die Last auf dem primären Schreibknoten zu senken
- Wenn sich große Mengen an Queries stauen, können Transaktionen und Schreiboperationen die Datenbank leicht überlasten; daher sollte Query-Throttling (Begrenzung) erwogen werden
Trennung langsamer und schneller Aufgaben
- Aufgaben, mit denen Nutzer interagieren, benötigen Antworten innerhalb von einigen hundert Millisekunden
- Bei zeitaufwendigen Aufgaben (z. B. Umwandlung großer PDFs) ist ein Muster effektiv, bei dem im Frontend nur das Nötigste sofort bereitgestellt und der Rest in den Hintergrund verlagert wird
- Hintergrundaufgaben arbeiten in der Regel mit einer Queue (z. B. Redis) und einem Job Runner zusammen
- Für weit im Voraus geplante Aufgaben ist es praktischer, statt Redis eine separate DB-Tabelle zur Verwaltung zu verwenden und die Ausführung über einen Scheduler zu steuern
Caching
- Caching trägt zur Kostensenkung und Leistungssteigerung bei, wenn dieselben oder teuren Berechnungen wiederholt werden
- Junior Engineers, die Caching gerade erst gelernt haben, möchten oft alles cachen; erfahrene Engineers sind bei der Einführung von Caches vorsichtiger
- Ein Cache führt neuen Zustand ein, daher bestehen Risiken wie Synchronisationsprobleme, Fehler und stale Daten
- Sinnvoll ist es, zuerst Performance-Verbesserungen wie zusätzliche Indizes für Queries zu versuchen und erst danach Caching anzuwenden
- Für große Caches kann statt Redis/Memcached auch ein Ansatz genutzt werden, bei dem periodisch in einen Objektspeicher wie S3/Azure Blob Storage geschrieben wird
Event-Verarbeitung
- Die meisten Unternehmen verfügen über einen Event-Hub (z. B. Kafka), und verschiedene Services verarbeiten verteilt auf Event-Basis
- Statt Events wahllos zu verwenden, ist ein einfaches Request-Response-API-Design für Logging und Problemlösung nützlicher
- Event-basierte Verarbeitung eignet sich, wenn sich der Sender nicht um das Verhalten des Empfängers kümmern muss oder für Szenarien mit hohem Volumen und tolerierbarer Latenz
Arten der Datenübertragung: Push und Pull
- Für die Datenübertragung gibt es zwei Methoden: Pull (Anfrage und Antwort) und Push (automatische Zustellung bei Änderungen)
- Der Pull-Ansatz ist einfach, kann aber zu wiederholten Anfragen und Überlastung führen
- Beim Push-Ansatz werden Datenänderungen vom Server sofort an den Client übermittelt; das ist effizient und vorteilhaft, um aktuelle Daten zu halten
- Für die Verarbeitung großer Client-Zahlen muss die Infrastruktur je nach Methode entsprechend ausgebaut werden (Event-Queues, mehrere Cache-Server usw.)
Fokus auf Hot Paths
- Hot Paths bezeichnen die wichtigsten Pfade im System, durch die viele Daten fließen
- Hot Paths lassen wenig Spielraum; bei Designfehlern können sie schwerwiegende Probleme für den gesamten Service verursachen, daher ist sorgfältiges Design unverzichtbar
- Statt Ressourcen auf weniger wichtige Funktionen mit vielen Optionen zu verteilen, ist es effektiver, sich auf Hot Paths zu konzentrieren und dort Design und Tests zu priorisieren
Logging, Metriken, Tracing
- Um bei Störungen die Ursache zu diagnostizieren, sollten detaillierte Logs für ungewöhnliche Fehlerpfade (unhappy path) aktiv aufgezeichnet werden
- Es ist notwendig, grundlegende Observability-Metriken zu erfassen, etwa Systemressourcen (CPU/Speicher), Queue-Größen sowie Request- und Job-Laufzeiten
- Statt nur auf Durchschnittswerte zu schauen, müssen auch Verteilungsmetriken wie p95- und p99-Latenzen unbedingt beobachtet werden. Ein kleiner Anteil besonders langsamer Requests kann das eigentliche Problem wichtiger Nutzer sein
Kill Switch, Retries und Wiederherstellung bei Ausfällen
- Die strategische Nutzung von Kill Switches (temporäre Systemabschaltung) und Retries ist wichtig
- Wahllose Retries belasten nur andere Services; wirksam werden sie erst, wenn Requests im Vorfeld etwa mit einem Circuit Breaker kontrolliert werden
- Durch die Einführung eines Idempotency Key können bei der Wiederverarbeitung identischer Requests doppelte Arbeiten verhindert werden
- In manchen Störungssituationen ist eine Entscheidung zwischen fail open und fail closed nötig. Beispielsweise ist beim Rate Limiting fail open (zulassen) die Variante mit geringerer Auswirkung auf Nutzer. Dagegen ist bei Authentifizierung fail closed zwingend
Abschluss
- Einige Themen wie Service-Trennung, Container, die Einführung von VMs und Tracing wurden ausgelassen, doch der Einsatz gut bewährter Komponenten an der richtigen Stelle führt langfristig zum stabilsten Systemaufbau
- Technisch besonders ausgefallene Entwürfe sind in der Praxis sehr selten; so einfache Entwürfe, dass sie fast langweilig wirken, werden im Arbeitsalltag am häufigsten verwendet
- Im Kern ist gutes Systemdesign ein Prozess, bei dem unauffällige, ausreichend bewährte Methoden sicher miteinander kombiniert werden
1 Kommentare
Hacker-News-Kommentare
Ich habe dabei oft das Gefühl, mit dieser Meinung allein zu sein. Ingenieure sehen komplexe Systeme und finden darin viele interessante Elemente, also denken sie: „Hier findet echtes System Design statt!“ Dabei ist ein komplexes System oft einfach das Ergebnis fehlenden guten Designs. Wenn man auf Jobsuche ist, sollte man diese Tatsache im Interview aber komplett vergessen. Ich habe selbst den Fehler gemacht, diese Sicht in einem System-Design-Interview ehrlich zu äußern. In einem Interview zu einer hypothetischen Startup-App habe ich Dinge gesagt wie: „Bei dieser QPS kann man Backpressure ignorieren“, „Man muss hier keine Queue statt eines cron jobs verwenden, auch wenn es natürlich Trade-offs gibt“ oder „SQL vs. NoSQL? Nimm das, womit das Team am besten vertraut ist.“ Aber genau solche Antworten wollen Interviewer nicht hören. Man muss das Whiteboard komplett füllen und ein Design zeichnen, das so komplex ist, als würde Kubernetes Kubernetes verwalten, um die Signale zu senden, die sie sehen wollen
Ich habe Hunderte System-Design-Interviews geführt und viele Leute trainiert. Die Antworten, die du erwähnt hast, senden nur schwache Signale, mit Ausnahme der Queue-Antwort. Was Interviewer wirklich wissen wollen, ist das „Warum“ hinter deinen Entscheidungen, welche Faktoren du berücksichtigt hast und wie dein Denkprozess aussieht. Wenn du deine Antworten nicht ausführlich erklärst, ist es aus Sicht des Interviewers leicht zu denken: „Da ist nicht viel Information zu holen.“ Deshalb muss ein Kandidat die Informationen, die der Interviewer braucht, aktiv liefern. Selbst ein guter Interviewer wird sich notieren: „Die Erklärung ist vernünftig, aber die Kommunikation ist ineffizient“, wenn er jede Begründung aus dir herausziehen muss. Kommunikationsfähigkeit ist ebenfalls Teil der Bewertung. Und bei SQL/NoSQL stimme ich nicht zu. Teamerfahrung ist wichtig, aber die Unterschiede zwischen den Technologien sind klar, und je nach Situation können die Performance-Unterschiede groß sein. Diese Antwort hinterlässt den Eindruck, dass dir Erfahrung mit verschiedenen Szenarien fehlt
Wie man sagt: „Interviews sind eine Einbahnstraße in beide Richtungen“ — ich halte deine Antworten für sehr vernünftig. Wäre ich der Interviewer gewesen, hätte ich dir eher eine hohe Bewertung gegeben. Wenn dich ein Unternehmen wegen solcher Antworten ablehnt, ist es womöglich eher das Unternehmen, das nicht gut ist. Realistisch gesehen muss man aber oft schnell eine Stelle finden, deshalb braucht es ein gutes Gleichgewicht und manchmal auch Antworten, die stärker auf das ausgerichtet sind, was die andere Seite hören will
Dieser Rat ist nicht gut. Ein einfaches und elegantes Design beginnt nicht damit, potenzielle Probleme zu ignorieren. Nachfragen sind kein Signal dafür, technische Trivia auszuschütten, sondern dass man gemeinsam diskutieren sollte. Deine Antworten zeigen keine Weisheit, sondern wirken eher unreif. Das ist nicht die Schuld des Interviewers
Ich stimme dem Punkt aus dem Nachbarkommentar zu, dass Interviews keine Einbahnstraße sind, aber ein guter Interviewer würde ehrlich sagen: „Das ist auch eine gute Antwort, aber ich teste in diesem Moment dein Wissen zu genau diesem Thema.“ Wenn jemand stattdessen nur weiter an etwas vorbeiredet, ist das eher ein Warnsignal
Ich finde, das zeigt genau, warum es so etwas wie LinkedIn-driven development gibt. In der Realität wirkt es viel beeindruckender, eine lange Liste von Technologien im CV zu haben, als gut erklären zu können, dass man mit einem einzigen Postgres und einem modularen Monolithen sehr gute Arbeit geleistet hat
Ich finde das einen wirklich guten Artikel. Ich möchte aber auch die Grenzen solcher Best Practices ansprechen. Zum Beispiel gibt es den Rat: „Lass nicht fünf verschiedene Services in dieselbe Tabelle schreiben, sondern lass vier per API aufrufen oder Events senden und nur einen Service direkt in die Tabelle schreiben.“ In der Realität ist die Abgrenzung aber oft nicht so sauber. Wenn alle fünf auf die DB zugreifen, baut man zwar bereits ein verteiltes System, aber die DB unterstützt von Haus aus Berechtigungen, Transaktionen und Custom Queries, sodass man vielleicht gar kein separates Interface entwerfen muss. Wenn man dagegen in einem einzelnen Service ein High-Level-Interface baut, muss man Authentifizierung, Transaktionen und Exception-Handling nun selbst implementieren. Dann stellt sich die Frage, ob man nicht in Wirklichkeit nur zusätzliche Failure Modes und den operativen Overhead komplexer Microservices erzeugt. Andererseits kann es natürlich schon ein Code Smell sein, wenn mehrere Services auf dieselbe DB zugreifen. Vielleicht ist diese DB eigentlich ein historisches Konglomerat aus mehreren Datenbanken, und vielleicht ließen sich die Services in Wahrheit auf zwei oder drei reduzieren
Auf die Frage „Was gewinnt man dadurch?“: APIs sind viel anpassungsfähiger gegenüber Veränderungen als ein gemeinsam genutztes DB-Schema. Nachdem ich in mehreren Systemen gearbeitet habe, würde ich eine Architektur, in der mehrere Services dieselbe DB teilen, nicht noch einmal entwerfen. Anfang der 2000er mochte das in kleinen Unternehmen noch funktionieren, aber seitdem habe ich damit fast nur Fehlschläge gesehen — mit Ausnahme von Fällen, in denen innerhalb desselben Services nur Lese- und Schreibpfade getrennt wurden
Ich stimme der Aussage nicht zu, dass eine DB als Interface kein separates Design erfordert. Wenn mehrere Clients dieselbe DB nutzen, unterscheiden sich ihre Zugriffsmuster, und auch Migrationsprobleme werden größer. Am Ende braucht man doch zusätzliche Gestaltung wie Views oder Berechtigungsmanagement, und die Wartungslast steigt ebenfalls. Im Idealfall ist eine API sehr viel sauberer. In der Praxis erlaubt man direkten DB-Zugriff oft aus Zeitdruck bei der schnellen Feature-Auslieferung, aber der eigentliche Grund ist häufig, dass viele vor einer vollständigen Neugestaltung für neue Anforderungen oder Designs zurückschrecken
Wenn Änderungen nötig werden, ist das Ziel, den Umfang der erforderlichen Abstimmung so klein wie möglich zu halten. Wenn man die Struktur eines Datastores ändern muss, muss man alle Teile kontrollieren, die auf diesen Datastore zugreifen. Je weniger Zugriffswege es gibt, desto leichter wird die Änderung. Ich habe zum Beispiel in der Praxis eine DB aufgeteilt, woraufhin mehr als 40 Teams ihren Code anpassen mussten. Wenn so etwas schon wegen einer „Feature-Anforderung“ passiert, ist das die Größenordnung. Wäre der Auslöser ein „Skalierungsproblem“ gewesen, hätte das Produkt selbst daran scheitern können
Du hast die Struktur, bei der mehrere Services an einer DB hängen, als „Code Smell“ bezeichnet. Umgekehrt kann es aber auch sein, dass die Verfügbarkeit real schlechter wird, wenn jeder Service physisch eine eigene DB bekommen muss, weil sich die Verfügbarkeit dann von N auf N hoch M potenziert und das System dadurch fragiler wird — wenn wir auf Ebene von DB-Clustern sprechen
Wenn man eine Datenbank abfragt, ist es am effizientesten, tatsächlich die DB abfragen zu lassen. Wenn Daten aus mehreren Tabellen benötigt werden, sollte man Joins verwenden, statt jede Tabelle einzeln in der Anwendung abzufragen und dort zusammenzufügen. Ich empfehle außerdem ausdrücklich Views oder sogar Stored Procedures. Views bilden eine Datenabstraktionsschicht und helfen daher stark beim Design, und gut geschriebener SQL-Code kann sehr verständlich und wartbar sein
Genau deshalb verursachen ORMs so viele Probleme. In einer SSR-Umgebung ist es für große Webservices effizient und elegant, für jede MVC-View direkt SQL-Views oder Custom Queries zu verwenden. Man lässt das RDBMS die schwere Arbeit erledigen, und der Webserver reicht die SQL-Ergebnisse direkt an die Tabellenansicht weiter. Legacy-RDBMS wie MSSQL oder Oracle haben schließlich eine Menge eingebauter Optimierungen. ORMs dagegen erzwingen ein einzelnes Objektmodell und sind dadurch fast völlig unflexibel
Stored Procedures wirken nützlich, aber in der Praxis machen die Grenzen der Sprache — etwa T-SQL — es schwer, mit modernen Sprachen zu entwickeln, die dem ganzen Team vertraut sind. Ich pflege gerade eine große T-SQL-Codebasis, und Versionsverwaltung oder Diagnose-Tools sind eher schwach. Den Code neuer Kollegen kann man meist noch lesen, aber T-SQL ist ein Albtraum
Ich stimme nicht zu. In modernen, stark auf Skalierbarkeit ausgerichteten Architekturen ist es besser, Joins im Backend vor der DB zu machen. Wenn die DB nur einfache Index-Lookups übernimmt und die Joins im Backend erfolgen, skaliert die DB besser und das System ist oft schneller. Server-Instanzen zu vermehren ist schließlich einfacher als die DB zu skalieren. Erst wenn die Datenmenge so groß wird, dass ein Join zwingend in der DB erfolgen muss, sollte man die Architektur anpassen. Wenn man den Join sogar bis ins Frontend verlagern kann, sind Ergebnis-Caches oft noch vorteilhafter
Ist das wirklich so? Nehmen wir zum Beispiel 10.000 Kunden und 1.000.000 Bestellungen. Wenn man eine Tabelle mit 20 Kundenfeldern und eine mit 5 Bestellfeldern vollständig joined und überträgt, schickt man 25 Millionen Felder durchs Netz. Holt man beides separat in zwei Queries und führt den Join selbst aus, sind es 5 Millionen Bestellfelder plus 200.000 Kundenfelder. Für Bandbreite und Performance ist das deutlich besser
Diese Regel ist ein guter Ausgangspunkt, aber man muss genau wissen, wann Ausnahmen nötig sind. Bei einer App, an der ich gearbeitet habe, führten Joins dazu, dass die Zahl der Datensätze geometrisch explodierte. Als wir die Queries getrennt haben, war das System trotz zusätzlichem Netzwerk-Overhead deutlich schneller, weil die Weiterverarbeitung und das Filtering viel effizienter wurden. Später haben wir sogar alles in JSONB gespeichert, und das war am Ende noch besser
Ich finde es schade, dass man über gutes System Design spricht, ohne die Problem-Domain überhaupt zu erwähnen. Der wichtigste und schwierigste Teil von System Design ist die Schnittstelle, die das System seinen Benutzern anbietet. Letztlich ist ein Softwaresystem immer ein Tauschgeschäft nach dem Motto: „Ich biete dir diese Funktionalität, dafür musst du diese Struktur oder dieses Modell verstehen.“ Fehler im Interface-Design sind die teuersten, und wenn man die meiste Zeit nicht über Interfaces spricht, übersieht man womöglich genau das Wichtigste. Die übrigen Systemelemente kann man später oft beliebig ändern, ohne die Benutzer zu berühren
Die Aussage „Gutes Design fällt nicht auf, schlechtes Design wirkt oft beeindruckender“ trifft für mich die Realität sehr gut. Die Bewertung von Technikern scheint nach wie vor an „Komplexität“ zu hängen, wodurch Overengineering gefördert wird. Das KISS-Prinzip wird schon seit Langem nicht ausreichend gewürdigt
Ich schaue manchmal im Codebase auf Stellen, über die ich beim Arbeiten einfach hinweggehe, ohne groß darüber nachzudenken. Gerade das ist oft ein Zeichen für gutes Design
Das ist leider wahr. Die meisten fühlen sich von komplexen Lösungen stärker angezogen, und wenn man eine einfache Antwort gibt, wirkt man schnell inkompetent. In der Realität trägt aber eine einfache Struktur, die leicht zu betreiben ist, viel mehr zum Erfolg eines Gesamtprojekts bei. Natürlich gibt es zwangsläufig komplexe Probleme, aber das meiste sind am Ende einfach gewöhnliche Web-Apps
Bei Schema-Design ist Flexibilität am wichtigsten. Wenn sich einmal Daten angesammelt haben, werden Schemaänderungen sehr schwierig. Gestaltet man das Schema aber zu flexibel — etwa indem man alles in JSON steckt oder eine EAV-Struktur verwendet — wird der Anwendungscode endlos komplex, und es kommen seltsame Performance-Probleme hinzu. Deshalb bevorzuge ich normalerweise Schemata, bei denen man schon an den Tabellenstrukturen intuitiv erkennen kann, wofür sie gedacht sind und die für Menschen gut lesbar bleiben. Wenn ich ständig EAV oder JSON-Spalten/-Tabellen sehe, möchte ich wirklich mit dem Entwickeln aufhören. Sicher, es gibt sinnvolle Einsatzfälle für EAV, aber in den meisten Fällen stiftet es in der Praxis nur Verwirrung. Das N+1-Problem, dynamisch generierte Queries, Audit-Daten im selben DB-System, die am Ende in die Business-Logik hineinwachsen, komplexe Oracle-Umgebungen und eine falsche Trennung dessen, was in die DB gehört und was in die App — jede einzelne dieser Variablen verschlechtert die Lebensqualität von Entwicklern enorm
Dazu passt Bill Karwins Buch „SQL Antipatterns“, das die Risiken und Grenzen des EAV-Musters gut beschreibt. Trotzdem kann man es manchmal als Übergangslösung verwenden, wenn ein Schema schwer zu modellieren ist — etwa mit JSONB-Spalten in Postgres — aber es darf keine Standardregel werden. Wenn Normalisierung möglich ist, sollte man sich immer dafür entscheiden
Bei der Aussage „Wenn man Audit-Daten in derselben DB speichert, werden sie irgendwann Teil der Business-Logik, und das ist problematisch“ frage ich mich, was dann die „Lehrbuchlösung“ wäre. Eine separate DB? Ein vollständig unabhängiger Storage?
Zu dem Rat „Vermeide, dass fünf Services in dieselbe Tabelle schreiben; vier sollen nur APIs aufrufen oder Events senden, und nur einer schreibt direkt in die DB“: Die beste Lösung ist, die Architektur von vornherein so zu gestalten, dass fünf Services nie in dieselbe Tabelle schreiben müssen. Wenn sie es doch tun, überschneiden sich ihre Logiken womöglich ohnehin stark. Dann sollte man sich fragen, ob diese fünf Services wirklich getrennt sein müssen oder ob man nicht einige zusammenlegen sollte. Praktisch kann man das Problem oft lösen, indem man separate Datentabellen vergibt oder durch Refactoring die Grenzen sauberer zieht
Die Unterscheidung zwischen stateful und stateless ist zentral, um Infrastrukturverantwortung und Entwicklungsverantwortung zu trennen. Wenn man Dinge als stateless in Containern betreibt, kann nicht viel wirklich katastrophal schiefgehen; im Fehlerfall deployed man einfach neu. Solange man keine DB-Fehler macht, die den Datensatzbestand zerstören, lässt sich das meiste schnell wiederherstellen. Bis hierhin können auch Leute mit sehr unterschiedlicher Berufserfahrung, Zeit und Sorgfalt gut arbeiten. Bei Bereichen mit State, etwa Datenbanken oder File Storage, ist die Lage aber komplett anders. Ein einziger Fehler kann das gesamte Unternehmen gefährden, deshalb sollten solche Systeme von erfahrenen Spezialisten mit echter Praxiserfahrung betreut werden. Selbst wenn eine DB fehlerfrei läuft, ist sie ohne Backups bereits ein großes Risiko. Das sind reale Probleme, die sich nicht dadurch lösen lassen, dass man in ein paar Minuten neu deployed
Beim Rat „Markiere lieber mit timestamp als mit bool“ frage ich mich, ob das nicht zu allgemein formuliert ist. Zum Beispiel ist
is_on→true,on_at→1023030klar, aberis_a_bear→true,a_bear_at→12312231231wirkt völlig absurd. Die meisten Bären werden schließlich nicht irgendwann plötzlich zum Bären … Das gilt also wohl nur in bestimmten SituationenIch halte es fast immer für besser, statt Boolean einen timestamp oder integer zu verwenden. Gerade Felder mit nur zwei Zuständen entwickeln sich oft zu einer „Typklassifikation“. Selbst wenn es zunächst nur Bären gibt, ist es sinnvoller, gleich auf Erweiterbarkeit zu einem Enum vorzubereiten. Und Statusfelder wachsen fast immer über simples aktiv/inaktiv hinaus zu Zuständen wie gestoppt, gelöscht, pausiert usw., wodurch immer mehr Booleans die Sache nur komplizierter machen. Ein Integer ist besser
Wörtlich genommen hieße das, dass schon die Verwendung von Boolean in einer DB ein Smell ist, und dem stimme ich zu. Allerdings ist dieser Ansatz — also bool → timestamp — bei Joins oft eher ein praktischer Kniff als eine sogenannte „vollständige Lösung“. Wenn Änderungen in Echtzeit wichtig sind, ist von Anfang an eine Audit-Tabelle die richtige Lösung. Soft Delete ist in meinen Augen ähnlich halbherzig. Die eigentliche Absicht ist, Löschungen zu verhindern, aber Backup und Restore schützen in Wahrheit wirksamer
Der Boolean-Typ belegt weniger Speicher und ist daher für manche Workloads — etwa große analytische Datenmengen — effizienter. Manchmal ist es auch logisch korrekt, einen Boolean zu speichern. Zum Beispiel ist für das Ergebnis eines Prozesses, also Erfolg oder Misserfolg, ein Boolean durchaus praktisch
Ich frage mich, warum man ausgerechnet nur Boolean als timestamp modellieren sollte. Auch bei
isDarkThemeoderpaginationItemskann man wissen wollen, wann etwas geändert wurde. Das wirkt eher wie eine Poor-Man-Changelog-LösungIn so einem Fall wäre ein Enum-Wert wie Bear besser
Wenn du ein Buch suchst, das gutes System Design aus einer etwas abstrakteren Perspektive behandelt, kann ich John Galls Systemantics sehr empfehlen. Ich halte es für Pflichtlektüre für Ingenieure