Ein Feld, ein Default, ein nächtlicher Batch. Der Batch lief ein Jahr lang gut, dann kam eine Spalte dazu, und die Buchhaltung bekam null Zeilen statt vierzigtausend. Niemand hatte etwas kaputt gemacht. Genau das war das Problem.
Der Abend, an dem das Schema mitredete
Viertel nach zwei, ein Anruf, ein Job rot. Die Meldung im Log war die Höflichkeitsform: Datenqualität. Gemeint war, dass der Join auf einer Spalte lief, die seit drei Wochen existierte und in allen alten Zeilen denselben Platzhalter enthielt. Der Job hat nicht versagt. Er hat sauber nichts gefunden und das als Erfolg gemeldet, mit grünem Haken und Laufzeit 41 Minuten. Nachts um zwei ist ein grüner Haken das Schlimmste, was dir passieren kann, weil dann niemand mehr nachsieht.
Ein Schema ist ein Vertrag, den niemand unterschrieben hat
Vier Abteilungen lesen aus derselben Tabelle. Eine schreibt hinein. Keine besitzt sie. Das ist der Normalzustand in gewachsenen Systemen, und er funktioniert erstaunlich lange, weil alle vorsichtig sind. Bis jemand eine Spalte braucht, für die es keinen Termin gibt, und dann ALTER TABLE tippt.
Das Datenmodell dazu ist sechs Jahre alt und liegt als PDF im Wiki. Vier der acht Tabellen darauf existieren nicht mehr, zwei fehlen. Niemand hat es aktualisiert, weil niemand dafür zuständig ist, und das ist derselbe Grund, aus dem die Tabelle keinen Owner hat. Ein Dokument ohne Owner veraltet so zuverlässig wie ein Feld ohne Leser.
ALTER TABLE ist eine einseitige Vertragsänderung. Kein Leser wird gefragt, kein Leser erfährt davon, und die Änderung ist in dreißig Sekunden durch. Genau deshalb passiert sie nachts, kurz vor dem Deployment, wenn niemand mehr widerspricht.
Die Migration, die drei Wochen später weh tat
So sah die Änderung aus, die den Abend verursacht hat. Sie ist technisch korrekt. Sie hat einen Default, sie hat keine Sperre über Minuten, sie läuft in einem Fenster, in dem niemand arbeitet. Sie ist nur fachlich falsch.
-- Einkauf, Migration "schnell noch ein Feld"
ALTER TABLE bestellung
ADD COLUMN lieferanten_nr VARCHAR(20) NOT NULL DEFAULT 'UNBEKANNT';
-- drei Wochen spaeter, Job der Buchhaltung
SELECT k.nr, b.lieferanten_nr
FROM konto k
JOIN bestellung b ON b.konto_nr = k.nr
WHERE b.lieferanten_nr != 'UNBEKANNT'; -- 0 Zeilen, 41 Minuten
Im Ticket stand: Feld hinzufügen, ein Aufwandspunkt. Es war ein Aufwandspunkt. Die Migration lief in elf Sekunden, der Review bestand aus zwei Kommentaren zur Benennung, und der Freigabeprozess bestand aus einem Haken in einem Formular. Für die Frage, wer die Spalte später liest, gab es kein Feld im Formular.
Der Platzhalter war die Absicht. Er sollte signalisieren, dass hier noch nichts steht. Er hat stattdessen signalisiert, dass hier nichts steht, und zwar in jeder Zeile, die vor der Migration entstanden ist. Wer den Filter schreibt, denkt an fehlende Daten und nicht an die Vergangenheit.
Die Reparatur hat zwei Tage gedauert: Backfill aus der Lieferantenstammdatentabelle, ein zusätzlicher Index, ein Rückbau des Filters. Die Erklärung im Betriebshandbuch hat drei Wochen gedauert, weil sie durch vier Abteilungen musste, von denen zwei die Tabelle nicht kannten und eine sie besaß, ohne es zu wissen.
Wie ein Vertrag aussieht, den man prüfen kann
Ein Data Contract ist kein Dokument, das jemand unterschreibt und einrahmt. Es sind vier bis sechs Regeln, die im Migrationswerkzeug stehen und den Build rot machen. Alles andere ist Absichtserklärung.
| Klausel | Was sie verhindert | Kosten |
|---|---|---|
| Spalten nur additiv, nie umbenennen | stille Brüche bei Lesern | null |
| Neue Pflichtspalte nur mit Backfill | Platzhalter in Altdaten | ein Skript |
| Bedeutung einer Spalte ist unveränderlich | Umdeutung ohne Migration | eine neue Spalte |
| Löschen erst nach zwei Quartalen ohne Leser | Löschen des falschen Feldes | ein Zähljob |
Die vierte Zeile ist die einzige, die mich in vier Jahren einmal vor einem Datenverlust bewahrt hat. Sie kostet einen Zähljob auf den Zugriffsstatistiken, den man einmal eingerichtet hat und danach nie wieder anschaut, bis er etwas findet.
Was ich in den Vertrag schreibe
Ich schreibe inzwischen fünf Zeilen in jede Schema-Beschreibung, und ich bestehe darauf, dass sie im Repository steht und nicht im Wiki. Das Wiki ist der Ort, an dem Zusagen hingehen, wenn sie niemand mehr prüft.
- Owner: eine Abteilung, ein Name, eine Vertretung. Nicht ein Gremium.
- Leser: wer liest welche Spalte und mit welcher Frequenz. Einmal im Quartal aus den Zugriffsstatistiken erneuert.
- Garantien: welche Spalten nie NULL sind, welche Wertebereiche gelten, was ein Platzhalter bedeutet.
- Änderungsweg: wer eine Spalte hinzufügen darf und wer sie freigibt. Zwei Namen, nicht ein Prozess.
- Depot: wo die Migration liegt und wie man sie zurückrollt. Ein Rollback, den niemand geübt hat, ist eine Hoffnung.
Das klingt nach Papierkram. Es ist Papierkram, der einen Anruf um Viertel nach zwei verhindert, und damit die billigste Form von Betrieb, die ich kenne.
Fazit
Ein Schema ohne Owner ist ein Vertrag ohne Partei: jeder darf ihn ändern, niemand haftet. Der Preis zeigt sich nicht beim Deployment, sondern drei Wochen später, wenn ein Job null Zeilen findet und Erfolg meldet. Das kostet zwei Tage Reparatur, drei Wochen Dokumentation und eine Nacht, in der du darüber nachdenkst, warum grün nicht grün bedeutet.
Schreib den Owner hin. Zähl die Leser. Und wenn du eine Spalte mit Platzhalter anlegst, frag vorher, wer sie in einem Jahr als Wahrheit liest.
Nenn mir die Spalte in deinem Schema, die drei Teams liest und keiner besitzt — wie lange steht sie schon da?
danielroth
ein default ist kein vertrag, das ist eine annahme. wenn vier abteilungen lesen und eine schreibt, gehört eine zusicherung in die spalte und kein platzhalter, der wie ein wert aussieht.
annakeller
der grüne haken ist der eigentliche vorfall. ein job, der null zeilen findet und erfolg meldet, hat keine datenqualitätsprüfung. bei uns wäre der lauf über eine erwartete zeilenzahl rot geworden.
felixwagner
alter table ist eine einseitige vertragsänderung. der audit trail dazu ist ein git commit von jemandem, der die fachabteilung nie getroffen hat. 😬
lukaswerner
not null hilft nur, wenn der wert stimmt.
mariehoffmann
zeilenzahl als schwellwert ist billig und fängt genau diesen fall. bei uns steht sie seit einem jahr im job und hat dreimal ausgelöst, zweimal zu recht.
sophieneumann
was ein data contract bei uns gekostet hat? ein workshop, zwei wochen und ein dokument, das nach sechs monaten keiner liest. der test im batch war billiger. 😂