Eine Software auf dem eigenen Entwicklungsrechner zum Laufen zu bringen, kann sich sehr befriedigend anfühlen. Der Build ist erfolgreich, die neue Funktion tut das Erwartete und alle offensichtlichen Fehler sind behoben. Aus Sicht der Entwicklung ist damit ein wichtiger Punkt erreicht.
Aus Sicht des Betriebs fängt die eigentliche Geschichte allerdings erst an.
Eine produktive Anwendung muss morgen noch funktionieren, nach dem nächsten Neustart wieder hochkommen, mit wachsenden Datenmengen umgehen und sich auch dann noch sinnvoll diagnostizieren lassen, wenn ein Fehler nicht während der Bürozeit auftritt. Änderungen müssen ausgerollt, Probleme eingegrenzt und im ungünstigsten Fall wieder zurückgenommen werden können.
Genau deshalb gehört der Betrieb für mich zu den Perspektiven, die gute Softwareentwicklung mitprägen sollten. Ich habe dadurch gelernt, dass eine technisch funktionierende Anwendung noch längst kein gut betreibbares System ist.
Auf dem Entwicklungsrechner sind die Bedingungen fast immer zu gut
Während der Entwicklung arbeite ich in einer Umgebung, die ich kenne.
Ich weiß, welche Dienste laufen, welche Abhängigkeiten installiert sind und welche Konfiguration ich selbst verändert habe. Wenn etwas nicht funktioniert, kann ich direkt in den Debugger schauen, einen Breakpoint setzen oder schnell eine Einstellung anpassen.
Im produktiven Betrieb sieht das anders aus.
Dort läuft eine Anwendung möglicherweise monatelang, bekommt Updates, verarbeitet Daten, kommuniziert mit anderen Systemen und wird von Menschen benutzt, die keinen Einblick in ihre interne Funktionsweise haben.
Ein Fehler muss deshalb auch dann analysierbar sein, wenn ich ihn nicht unmittelbar reproduzieren kann.
Das verändert die Anforderungen an Software deutlich.
Logs werden plötzlich sehr wichtig
Während der Entwicklung kann ich einen Fehler oft direkt beobachten.
Im Betrieb bekomme ich möglicherweise nur die Meldung:
„Seit gestern funktioniert Funktion X nicht mehr.“
Dann brauche ich Informationen.
Wann trat das Problem erstmals auf?
Welche Eingaben waren beteiligt?
Welcher Dienst hat den Fehler ausgelöst?
Gab es vorher bereits Warnungen?
Ist nur ein einzelner Vorgang betroffen oder das gesamte System?
Gute Logs werden in diesem Moment zu einem zentralen Bestandteil der Software.
Sie sollten genügend Informationen liefern, um einen Vorgang nachvollziehen zu können, ohne gleichzeitig sensible Daten unnötig zu protokollieren oder so viel Rauschen zu produzieren, dass relevante Meldungen darin verschwinden.
Diese Balance lässt sich während der Entwicklung leicht unterschätzen.
Ein Fehler muss nicht nur erkannt, sondern eingeordnet werden können
Nicht jede Fehlermeldung besitzt dieselbe Bedeutung.
Wenn ein Hintergrunddienst eine externe Ressource für wenige Sekunden nicht erreicht, kann ein automatischer Wiederholungsversuch völlig ausreichend sein. Wenn dagegen Daten inkonsistent werden oder ein sicherheitsrelevanter Dienst ausfällt, brauche ich eine ganz andere Reaktion.
Der Betrieb hat mir deshalb deutlich gemacht, dass Fehlerbehandlung mehr ist als try/catch.
Eine Anwendung sollte möglichst unterscheiden können, welche Probleme vorübergehend sind, welche einen manuellen Eingriff benötigen und welche so kritisch sind, dass ein Prozess sofort gestoppt werden sollte.
Diese Informationen werden auch für Automatisierung wichtig.
Wenn Codex irgendwann technische Probleme selbstständig behandeln soll, muss der Prozess wissen, welche Fehler gefahrlos automatisch bearbeitet werden dürfen und wann die Aufgabe besser blockiert wird.
Neustarts sind ein erstaunlich guter Realitätstest
Eine Anwendung, die nur dann zuverlässig läuft, wenn sie seit der Entwicklungssitzung durchgehend aktiv geblieben ist, hat im Betrieb ein Problem.
Neustarts gehören zum Alltag.
Server werden aktualisiert, virtuelle Maschinen verschoben, Dienste neu gestartet und Stromausfälle passieren ebenfalls.
Deshalb ist für mich wichtig, dass Anwendungen nach einem Neustart selbstständig wieder in einen definierten Zustand kommen.
Welche Dienste müssen automatisch starten?
Welche Abhängigkeiten müssen verfügbar sein?
Was passiert, wenn ein Dienst schneller startet als die Datenbank?
Wie wird ein vorher unterbrochener Vorgang behandelt?
Solche Fragen wirken während der eigentlichen Feature-Entwicklung häufig nebensächlich, entscheiden im Betrieb aber darüber, wie zuverlässig das Gesamtsystem ist.
Updates verändern die Anforderungen an Architektur
Auch die Frage, wie Software aktualisiert wird, beeinflusst bereits ihre Entwicklung.
Wenn jedes Update einen langen manuellen Ablauf benötigt, wird die Pflege mit jeder weiteren Instanz schwieriger. Wenn Datenstrukturen verändert werden, muss geklärt sein, wie alte Daten migriert werden und ob die Anwendung währenddessen weiter verfügbar sein muss.
Gerade beim automatisierten Deployment reicht mir deshalb ein erfolgreicher technischer Ablauf nicht. Nach einem Update muss geprüft werden, ob die Anwendung tatsächlich wieder in einem funktionierenden Zustand angekommen ist.
Dieser Gedanke stammt direkt aus der Betriebsperspektive.
Der Code ist nicht das Endprodukt.
Das laufende System ist es.
Abhängigkeiten fallen häufig erst im Betrieb richtig auf
Moderne Anwendungen bestehen selten vollständig aus sich selbst.
Sie brauchen Datenbanken, Netzwerkverbindungen, externe APIs, Speicher, Authentifizierungsdienste oder andere Systeme.
Während der Entwicklung sind diese Abhängigkeiten häufig zuverlässig verfügbar.
Im Betrieb fällt irgendwann eine davon aus.
Genau dann zeigt sich, wie robust die Anwendung tatsächlich ist.
Muss das gesamte System stehenbleiben, weil ein einzelner externer Dienst gerade nicht antwortet?
Kann eine Aufgabe später nachgeholt werden?
Wie lange wird ein Wiederholungsversuch durchgeführt?
Was passiert mit Daten, die in dieser Zwischenzeit entstehen?
Diese Fragen gehören für mich inzwischen zur Entwicklung dazu und nicht erst zu einer späteren Betriebsphase.
Monitoring verändert ebenfalls die Software
Eine Anwendung kann nur sinnvoll überwacht werden, wenn sie ihren Zustand ausreichend sichtbar macht.
Dazu gehören nicht nur Logs, sondern auch Healthchecks, Metriken und klar definierte Zustände.
Wenn ich lediglich prüfen kann, ob ein Prozess läuft, weiß ich noch nicht, ob die Anwendung ihre Aufgabe erfüllt.
Ein Dienst kann aktiv sein und trotzdem keinerlei Daten mehr verarbeiten.
Eine Datenbankverbindung kann bestehen, während die wichtigste fachliche Funktion längst Fehler produziert.
Gutes Monitoring muss deshalb zumindest teilweise wissen, was „gesund“ für die jeweilige Anwendung überhaupt bedeutet.
Das wiederum erfordert, bereits während der Entwicklung darüber nachzudenken, welche Zustände später beobachtet werden müssen.
Betrieb bedeutet auch, dass Änderungen rückgängig gemacht werden müssen
Während der Entwicklung kann ich eine problematische Änderung relativ bequem verwerfen.
Im produktiven Betrieb kann die gleiche Änderung bereits Daten verändert oder andere Systeme beeinflusst haben.
Rollback ist deshalb nicht immer so einfach wie ein Git-Checkout.
Gerade bei Datenbankänderungen oder neuen Dateiformaten kann eine echte Rückkehr zum vorherigen Zustand schwierig sein.
Das hat meinen Blick auf Änderungen geprägt.
Bei riskanteren Veränderungen interessiert mich nicht nur, ob der geplante Weg funktioniert.
Ich möchte auch wissen, was passiert, wenn er nicht funktioniert.
Welchen Rückweg gibt es?
Welche Daten müssen vorher gesichert werden?
Kann die alte Version mit den neuen Daten überhaupt umgehen?
Solche Fragen gehören für mich zur technischen Qualität einer Lösung.
Diese Betriebsperspektive beeinflusst heute auch meine KI-Arbeit
Mit Codex lassen sich Änderungen sehr schnell erzeugen.
Das macht die Betriebsperspektive noch wichtiger, weil eine technisch plausible Änderung in kurzer Zeit produktionsreif wirken kann, obwohl wichtige Fragen rund um Betrieb, Diagnose und Wiederherstellung noch gar nicht geklärt wurden.
Ausführlichere Spezifikationen helfen mir deshalb dabei, solche Randbedingungen früher sichtbar zu machen. Wenn eine neue Funktion einen Hintergrunddienst benötigt, möchte ich nicht erst nach dem Deployment darüber nachdenken, wie dieser Dienst überwacht oder nach einem Neustart automatisch wiederhergestellt wird.
Wenn neue Daten gespeichert werden, interessiert mich frühzeitig, wie sie gesichert und gegebenenfalls migriert werden.
Wenn eine Funktion von einem externen System abhängt, muss klar sein, was passiert, wenn dieses System nicht erreichbar ist.
Codex kann diese Anforderungen umsetzen.
Aber ich muss sie vorher zumindest als relevant erkennen.
Automatisierung braucht gute Betriebsinformationen
Je weiter ich meine Entwicklungs- und Deployment-Kette automatisieren möchte, desto wichtiger werden genau diese Informationen.
Eine KI kann nur dann sinnvoll auf einen Fehler reagieren, wenn der Zustand ausreichend beobachtbar ist.
Wenn nach einem Deployment lediglich die Meldung „etwas funktioniert nicht“ zurückkommt, bleibt die eigentliche Diagnose weiterhin vollständig bei mir.
Wenn Logs, Healthchecks und klar definierte Systemzustände vorhanden sind, kann Codex wesentlich mehr selbstständig analysieren.
Damit wird gute Betreibbarkeit sogar zu einer Voraussetzung für weitere Automatisierung.
Je besser ein System seinen eigenen Zustand sichtbar macht, desto mehr technische Arbeit kann ich später an automatisierte Prozesse abgeben.
Betrieb ist deshalb kein Thema für später
Eine der wichtigsten Erkenntnisse aus dieser Perspektive ist für mich, dass Betrieb nicht erst beginnt, nachdem die Entwicklung abgeschlossen wurde.
Viele Eigenschaften einer gut betreibbaren Anwendung müssen bereits während der Entwicklung entstehen.
Logging lässt sich nachträglich ergänzen, aber deutlich besser ist es, relevante Zustände von Anfang an mitzudenken.
Ein automatischer Neustart kann später konfiguriert werden, aber die Anwendung muss mit unterbrochenen Vorgängen umgehen können.
Monitoring kann nachgerüstet werden, aber dafür müssen überhaupt sinnvolle Zustände messbar sein.
Ein Rollback kann geplant werden, aber manche Architekturentscheidungen bestimmen bereits vorher, ob er überhaupt möglich ist.
Genau deshalb wollte ich Software nie ausschließlich aus Sicht des Programmierens betrachten.
Gute Software funktioniert nicht nur, sie lässt sich auch betreiben
Für mich gehört Betreibbarkeit inzwischen zur Qualität einer Software.
Eine Anwendung ist nicht besonders gut, wenn sie zwar eine beeindruckende Funktion besitzt, aber nach jedem Update manuell repariert werden muss.
Sie ist auch nicht gut, wenn Fehler nur mit Debugger und Entwicklungsumgebung analysierbar sind oder wenn niemand erkennen kann, ob ein Hintergrundprozess seit drei Tagen stillsteht.
Gute Software muss natürlich zunächst ihre fachliche Aufgabe erfüllen.
Danach kommt aber sofort die Frage, wie zuverlässig sie diese Aufgabe über längere Zeit erfüllen kann und wie schnell sich Probleme erkennen und beheben lassen.
Diese Perspektive hat meine Entwicklung deutlich verändert.
Und je mehr konkrete Implementierung Codex für mich übernimmt, desto wichtiger wird sie für meine eigene Rolle.
Denn Code kann KI inzwischen ziemlich gut erzeugen.
Zu wissen, wie sich dieser Code morgen im echten Betrieb verhalten muss, bleibt trotzdem meine Aufgabe.