Drei Prozesse, eine Datenbank
Bot, Dashboard und Verwaltung laufen getrennt und sprechen nie direkt miteinander. Warum diese Architektur für ein Ein-Personen-Projekt genau richtig ist — und wo ihre Grenzen liegen.
935 Wörter · 5 Min. Lesezeit
- yurna
- architektur
- betrieb
Seit dem Frühjahr besteht mein System aus drei Prozessen. Sie haben verschiedene Aufgaben, verschiedene Lebenszyklen und keine direkte Verbindung untereinander. Alles, was sie teilen, ist eine Datenbank.
Das ist keine Microservice-Architektur — dafür fehlen Netzwerkschnittstellen zwischen den Teilen, eigene Datenhaltung und unabhängige Auslieferung. Es ist eher das, was man einen verteilten Monolithen nennen könnte, wenn das nicht abwertend klänge. Ich halte es für den vernünftigsten Aufbau, den ich für dieses Projekt finden konnte.
Wer macht was
Der Bot hält die Verbindung zu Discord, verarbeitet Ereignisse, führt Befehle aus. Er hat keinen eingehenden Port nach außen.
Das Dashboard ist die Weboberfläche für Nutzer. Anmeldung über Discord, Einstellungen, Statistiken.
Die Verwaltung ist meine Schnittstelle. Kein hübsches Frontend, nur JSON. Sie kennt den Zustand der anderen beiden, ohne mit ihnen zu reden.
Alle drei sprechen mit derselben Datenbank. Es gibt keine Aufrufe untereinander, keine Nachrichtenwarteschlange, keinen gemeinsamen Zwischenspeicher als Voraussetzung.
Warum keine direkte Verbindung
Das ist die Entscheidung, die ich am häufigsten erklären musste, auch mir selbst.
Weil jede Verbindung ausfallen kann. Wenn das Dashboard den Bot direkt aufruft, um eine Einstellung zu übernehmen, muss es damit umgehen, dass der Bot nicht antwortet. Dann braucht es Zeitgrenzen, Wiederholungen, einen Umgang mit halb angekommenen Änderungen — und plötzlich ist der Zustand an zwei Orten, die nicht übereinstimmen.
Weil die Startreihenfolge sonst zählt. Bei direkter Kopplung muss etwas zuerst da sein. Bei der Datenbank als einzigem Bindeglied ist es egal, wer wann startet.
Weil es einen Ort der Wahrheit gibt. Was in der Datenbank steht, gilt. Kein Zwischenspeicher, der eine andere Meinung hat, kein Prozess, der etwas Wichtiges nur im Arbeitsspeicher hält.
Wie die Prozesse trotzdem zusammenarbeiten
Drei Muster reichen für alles, was ich brauche.
Zustand melden. Jeder Prozess schreibt regelmäßig eine Zeile mit Zeitstempel und Kennzahlen. Wer wissen will, ob jemand lebt, liest die Zeile und schaut aufs Alter.
Arbeit einreihen. Wer etwas will, das ein anderer tun muss, legt einen Auftrag an. Der andere holt ihn ab, führt ihn aus und schreibt das Ergebnis zurück.
Änderungen bemerken. Jeder Datensatz hat einen Zeitstempel der letzten Änderung. Wer einen Zwischenspeicher hält, prüft beim Zugriff, ob sein Stand noch aktuell ist.
Das dritte Muster ist das unspektakulärste und das, das am meisten Ärger erspart. Es gibt keinen Weg, wie die Zwischenspeicher auseinanderlaufen können, weil niemand darauf angewiesen ist, eine Benachrichtigung zu bekommen.
Infobox
Die Alternative wäre ein Nachrichtensystem gewesen — ein Kanal, über den Ereignisse fließen. Das ist eleganter und hat einen Preis: einen zusätzlichen Dienst, eine neue Fehlerquelle, und die Frage, was passiert, wenn eine Nachricht verloren geht. Für drei Prozesse auf einer Maschine steht der Aufwand in keinem Verhältnis zum Nutzen.
Die Grenzen dieses Aufbaus
Ich beschreibe das ausdrücklich, weil so ein Aufbau nicht überall passt.
Alles muss dieselbe Datenbank sehen. Bei einer Datei-basierten Datenbank heißt das: dieselbe Maschine. Wer über mehrere Rechner verteilen will, braucht etwas anderes.
Verzögerung durch Abfragen. Ein Auftrag wird nicht sofort ausgeführt, sondern beim nächsten Durchlauf. Bei mir sind das Sekunden, und für Verwaltungsaufgaben ist das völlig ausreichend. Für etwas Interaktives wäre es zu langsam.
Die Datenbank ist der einzige Ausfallpunkt. Wenn sie nicht erreichbar ist, steht alles. Das ist bei einer Datei auf derselben Maschine allerdings kein realistischer Teilausfall, sondern gleichbedeutend mit einem Ausfall der Maschine.
Schreibende Zugriffe reihen sich. Solange die Last überwiegend lesend ist, spielt das keine Rolle. Bei einem schreiblastigen System wäre es der erste Engpass.
Was ich beim Betrieb dieses Aufbaus gelernt habe
Nach einiger Zeit im Betrieb sind ein paar Eigenheiten aufgefallen, die man bei diesem Muster kennen sollte.
Die Abrufintervalle summieren sich. Drei Prozesse, die jeweils alle paar Sekunden nachsehen, ob etwas anliegt, erzeugen dauerhaft Grundlast. Bei einer Datei-Datenbank ist das messbar, wenn auch klein. Die Abhilfe ist banal: längere Abstände für alles, was nicht dringend ist.
Schreibende Zugriffe reihen sich, und lange Transaktionen blockieren. Eine Transaktion, die einen externen Aufruf enthält, hält die Sperre so lange, wie der Aufruf dauert. Genau das hatte ich an einer Stelle, und es fiel erst unter Last auf.
Zeitstempel brauchen eine gemeinsame Quelle. Wenn ein Prozess seine eigene Uhr benutzt und ein anderer die der Datenbank, entstehen Abweichungen. Ich lasse deshalb die Datenbank die Zeit setzen, wo es geht.
Wann ich diesen Aufbau nicht mehr benutzen würde
Es gibt drei Signale, bei denen ich umbauen würde — und ich schreibe sie auf, damit ich sie erkenne, wenn sie kommen.
Wenn ein zweiter Rechner dazukommt. Eine Datei-Datenbank ist an eine Maschine gebunden. Sobald etwas woanders laufen soll, ist der gemeinsame Zugriff das Erste, was bricht.
Wenn schreibende Zugriffe die Mehrheit werden. Dann ist die Reihung der Schreiber der Engpass, und kein Zwischenspeicher hilft.
Wenn Verzögerungen von Sekunden zu lang werden. Das Abrufmuster passt für Verwaltungsaufgaben. Für etwas, das sofort wirken muss, braucht es einen anderen Weg.
Keines dieser Signale ist bei mir eingetreten. Aber sie aufzuschreiben hat einen Wert für sich: Ich weiß, worauf ich achten muss, statt eines Tages festzustellen, dass der Aufbau nicht mehr passt, ohne sagen zu können, seit wann.
Was ich daraus mitgenommen habe
Der wichtigste Gedanke ist nicht die Architektur, sondern die Frage dahinter: Welchen Ausfall will ich überleben?
Ich wollte überleben, dass eine Anwendung sich verabschiedet, ohne dass ich blind bin. Genau das leistet dieser Aufbau, mit minimalem Zusatzaufwand.
Was ich ausdrücklich nicht wollte: Ausfälle einzelner Rechner überstehen, horizontal skalieren, hundert Anfragen pro Sekunde bedienen. Für nichts davon gibt es bei mir einen Anlass, und jede Vorkehrung dafür hätte den Alltag komplizierter gemacht.
Diese Frage — welchen Ausfall will ich überleben, und was ist mir das wert — hat mir seitdem mehr Architekturentscheidungen abgenommen als jede Prinzipiendiskussion.