Der erste Blick in das Schema war ein DESCRIBE auf die Tabelle, um die sich alles dreht:
kd_auftrag_pos_hist: 62 Spalten
id bigint
kdnr varchar(12)
pos_nr smallint
art_nr varchar(20)
art_nr_alt varchar(20)
menge decimal(12,3)
preis decimal(12,4)
preis2 decimal(12,4)
...
Achtzehn Jahre Wachstum, kein Kommentar, ein Trigger mit dem Namen tr_pos_hist_upd aus dem Jahr 2011 und eine Spalte preis2, zu der mir drei Kollegen drei verschiedene Erklärungen gegeben haben. Ich hatte zwei Wochen und die ausdrückliche Anweisung, nichts zu ändern.
Tag eins: nichts anfassen
Die erste Entscheidung war, einen read-only User anzulegen und alles andere über ihn zu machen. Das klingt nach Vorsicht und war Selbstschutz: In einer Datenbank, in der _alt und _hist als Suffixe existieren, ist ein versehentliches UPDATE ein Datenverlust mit Zeitstempel.
Dann habe ich gezählt. Nicht die Tabellen, die Zeilen – weil die Zeilenzahl sagt, welche Tabelle lebt und welche nur noch existiert.
import psycopg2
SQL = (
"SELECT relname, n_live_tup "
"FROM pg_stat_user_tables "
"ORDER BY n_live_tup DESC"
)
with psycopg2.connect(dsn) as conn, conn.cursor() as cur:
cur.execute(SQL)
for name, rows in cur.fetchall()[:15]:
print(f"{rows:>12,} {name}")
Von 214 Tabellen hatten 61 exakt null Zeilen und 22 weniger als zehn. Das sind keine Tabellen, das sind Grabsteine. Ich habe sie nicht gelöscht, ich habe sie markiert – und in der Dokumentation steht jetzt bei jeder: „leer seit mindestens 2019, nicht löschen, weil ein Report sie referenziert.“
Was die Spaltennamen verraten
_hist heißt Historie, aber nicht bei jeder Tabelle dasselbe: Bei kd_auftrag ist es ein Append-Only-Log, bei kd_auftrag_pos eine Kopie des aktuellen Stands mit Trigger. _alt heißt „vor der Migration 2016“, wird aber in zwei Prozeduren noch geschrieben. _tmp heißt bei drei Tabellen „temporär“ und bei einer „bitte nicht benutzen“, was niemand aufgeschrieben hat.
Die zweite Quelle waren die Fremdschlüssel – oder ihr Fehlen. 38 Spalten heißen kdnr, aber nur elf haben einen Foreign Key. Die anderen 27 sind Join-Felder, die irgendwann funktioniert haben und heute auf Werte zeigen, die es nicht mehr gibt. Ich habe die verwaisten gezählt: 4.112 Zeilen in kd_auftrag_pos_hist mit einer kdnr, die in kd_kunde nicht existiert.

Die Karte, die keiner hatte
Die vierte Quelle war der Anwendungscode, und sie war die langsamste. Ich habe die SQL-Strings aus dem Repo extrahiert und nach Tabellennamen gruppiert, um zu sehen, welche Tabellen überhaupt gelesen werden. Ergebnis: 118 der 214 kommen in keiner Query vor, werden aber von zwei Reports referenziert, die einmal im Monat laufen. Das ist die Sorte Abhängigkeit, die man erst bemerkt, wenn man sie entfernt.
Nach einer Woche hatte ich sechs Tabellen, die den Geschäftsfall tragen, und ein Diagramm, das auf eine Seite passt. Das ist der einzige Teil der Dokumentation, den ich für unverzichtbar halte, weil er die Frage beantwortet, die jeden Neueinsteiger blockiert: Wo fange ich an?
Sechs Entitäten, fünf Beziehungen, und im Absatz darunter steht, was fehlt: die Historientabellen, die Trigger, die 27 Join-Felder ohne Constraint. Ein Diagramm, das vollständig sein will, wird unlesbar. Ein Diagramm, das ehrlich sagt, was es weglässt, wird benutzt.
Dokumentation, die nicht veraltet
Mein erster Impuls war ein Wiki. Mein zweiter war die Erkenntnis, dass ein Wiki nach sechs Monaten genauso falsch ist wie keine Dokumentation, nur überzeugender. Also habe ich drei Dinge gebaut, die sich selbst prüfen.
Kommentare in der Datenbank: COMMENT ON COLUMN für die 40 Spalten, bei denen ich sicher war. Sie stehen dort, wo man sie liest – im DESCRIBE – und sie wandern mit jedem Dump mit.
Ein Test in der CI, der prüft, ob die dokumentierten Spalten noch existieren. Wenn jemand eine Spalte umbenennt, wird der Build rot, und das ist der einzige Mechanismus, der gegen stille Drift hilft.
Ein Wörterbuch, das aus information_schema generiert wird, nicht gepflegt. Es beschreibt Typen, Nullability und Fremdschlüssel – alles, was die Datenbank über sich selbst weiß, und nichts, was ein Mensch behaupten müsste.
Fazit
Achtzehn Jahre ohne Dokumentation sind kein Versäumnis, sie sind eine normale Folge von Prioritäten: Das System lief, also gab es keinen Grund, es zu erklären. Der Preis wird nicht von denen bezahlt, die es gebaut haben, sondern von der nächsten Person, die eine Spalte preis2 findet und raten muss.
Meine Frage an dich: Wenn du morgen in ein Schema mit 200 Tabellen ohne Kommentar geworfen würdest – würdest du zuerst die Zeilenzahlen zählen oder zuerst den Code lesen, der sie schreibt?
nadiabehrens
62 spalten und eine spalte preis2, ich hätte nach der dritten erklärung aufgehört zu fragen 😅
timrademacher
nichts ändern in zwei wochen ist die schwierigste vorgabe, die ein praktikum haben kann
benosei
drei tabellen mit demselben inhalt, das ist die datenbankversion von drei backup-ordnern 😂
hannahweiss
der trigger von 2011 hat mich beruhigt, bei uns steht im schema noch ein name von 2004 😅
yaraosman
ich hätte nach zwei tagen angefangen zu ändern, sonst hätte ich keine karte gebraucht
linabrueckner
wie hast du die karte geschrieben, als diagramm oder als markdown im repo?
sofiamarkovic
als markdown im repo, mit einem diagramm als bild, die spaltenbeschreibung steht direkt daneben
nadiabehrens
drei backup-ordner habe ich auch, einer heißt final_final 😅