Alle Artikel
12. September 2026
7 mins

Testdaten aus der Produktion

Von Tim Rutte, Cloud & Software ArchitectThemaLegacy & Modernisierung

Ein blauer Ausstecher auf einer ausgerollten Teigplatte, eine Form ist bereits herausgetrennt.

Es gibt eine Sorte Fehler, die in keinem Test auftaucht und in Produktion zuverlässig passiert. Ein Name mit einem Apostroph. Eine Bestellung mit null Positionen, die es laut Modell nicht geben kann. Ein Kunde, dessen Konto vor neun Jahren angelegt wurde, als es das Pflichtfeld noch nicht gab.

Diese Fehler haben eine gemeinsame Ursache, und sie liegt nicht im Code. Die Testdaten sind zu sauber. Sie wurden von Menschen erdacht, die das System verstanden haben, und enthalten deshalb genau die Fälle, an die jemand gedacht hat.

Der naheliegende Schluss lautet, die Produktionsdatenbank zu kopieren. Das ist der richtige Gedanke und der falsche Weg. Dieser Artikel beschreibt den Weg dazwischen: einen Testbestand, der die Formen der Wirklichkeit trägt, ohne ihre Inhalte.

Warum der Dump keine Lösung ist

Der Produktionsabzug auf dem Rechner eines Entwicklers ist in vielen Unternehmen gelebte Praxis, und er bringt vier Probleme mit, von denen jedes für sich reicht.

Er ist rechtlich nicht haltbar. Personenbezogene Daten in einer Entwicklungsumgebung sind eine Verarbeitung ohne Zweck, und die Umgebung, in der sie liegen, hat weder die Zugriffskontrolle noch die Löschfristen der Produktion.

Er ist zu groß. Vierhundert Gigabyte laufen nicht auf einem Notebook, und ein Testlauf, der vierzig Minuten zum Einspielen braucht, wird nicht ausgeführt.

Er verschickt E-Mails. Die unangenehmste Variante dieses Problems ist nicht hypothetisch: ein Stapellauf in der Testumgebung, echte Adressen in der Datenbank, und zweitausend Kunden bekommen eine Rechnung von vor drei Jahren ein zweites Mal.

Er enthält Geheimnisse. Zugangsdaten für Drittsysteme, Sitzungsschlüssel, Zahlungsdaten. Ein Abzug wandert von einem Rechner auf den nächsten und ist zwei Jahre später an sieben Orten.

Was tatsächlich gebraucht wird

Der Denkfehler hinter dem Abzug ist die Annahme, man brauche die Daten. Gebraucht werden die Formen, in denen sie vorkommen.

Niemand braucht zwei Millionen Kunden, um eine Zahlungsfunktion zu prüfen. Gebraucht werden der Kunde ohne Rechnungsadresse, der mit drei Adressen, der mit Umlaut im Nachnamen, der aus der Zeit vor der Umstellung von 2019, der mit einer offenen Gutschrift und der, dessen Konto gesperrt ist.

Damit ändert sich die Aufgabe. Sie lautet nicht mehr „wie bekommen wir die Produktionsdaten nach unten", sondern: Welche Fälle gibt es, und wie bekommen wir von jedem ein paar? Das ist eine Auswahlaufgabe, und ihr Ergebnis ist ein Bestand von wenigen hundert Megabyte statt vierhundert Gigabyte.

Der Einstieg ist eine Abfrage auf der Produktionsdatenbank, die nichts kopiert, sondern zählt: Wie viele Kunden haben keine Rechnungsadresse, wie viele mehr als eine, wie viele ein Konto von vor 2019. Die Liste, die dabei entsteht, ist wertvoller als der Abzug, den sie ersetzt, denn sie ist die Liste der Fälle, die das System tatsächlich kennt.

Der Anker und alles, was daran hängt

Die Auswahl selbst geschieht entlang eines Ankers. In den meisten Geschäftssystemen ist das der Kunde, im Handel manchmal die Bestellung, in einer Plattform der Mandant.

Man wählt einige hundert Anker so aus, dass jede bekannte Form mehrfach vorkommt, und nimmt dann alles mit, was daran hängt:

-- Der Anker: bewusst gemischt statt zufaellig. Ein zufaelliger
-- Schnitt trifft den seltenen Fall gerade nicht.
CREATE TEMP TABLE anker AS
  (SELECT id FROM kunde WHERE rechnungsadresse_id IS NULL LIMIT 50)
  UNION
  (SELECT id FROM kunde WHERE angelegt_am < '2019-01-01' LIMIT 50)
  UNION
  (SELECT id FROM kunde WHERE gesperrt = true LIMIT 20)
  UNION
  (SELECT id FROM kunde ORDER BY random() LIMIT 300);

-- Und dann alles, was daran haengt, in der Reihenfolge der
-- Fremdschluessel.
COPY (SELECT * FROM bestellung WHERE kunde_id IN (SELECT id FROM anker))
  TO '/export/bestellung.csv' CSV;

Der zufällige Teil am Ende ist Absicht: Er bringt die Fälle mit, an die niemand gedacht hat, und genau die sind der Grund für die ganze Übung.

Woher die stillen Verweise kommen, die dabei auffallen, steht in Das eigentliche Legacy ist Ihr Datenbankschema.

Das eigentlich schwere Problem

Wer das zum ersten Mal baut, unterschätzt einen Punkt: Ein Ausschnitt aus einer Datenbank ist fast nie in sich stimmig.

Die harten Fremdschlüssel sind dabei der einfache Teil; die meldet die Datenbank beim Einspielen. Die stillen Verweise sind das Problem, und in einem gewachsenen System gibt es davon mehr, als die Modellzeichnung zeigt: eine Kennung in einer JSON-Spalte, ein Verweis über zwei Felder mit Typ und Kennung, eine Tabelle, die per Zeichenkette auf eine andere zeigt, und der Klassiker, der Verweis in eine Tabelle, die einer anderen Anwendung gehört.

Das Vorgehen dagegen ist nicht Nachdenken, sondern Ausprobieren: Der Ausschnitt wird eingespielt, die Anwendung wird durch ihre Hauptpfade geschickt, und jeder Verweis ins Leere, der dabei auffällt, wird zur Regel im Auswahlskript. Nach drei Runden ist der Bestand stimmig, und die Liste der stillen Verweise ist nebenbei die beste Dokumentation des Datenmodells, die es in dem Projekt gibt.

Die Alternative, alles über eine Ebene abzuwickeln, die diese Verweise kennt, klingt sauberer und ist es nicht: Das Auswahlskript wäre dann so gut wie das Modell der Anwendung, und dessen Lücken sind genau die Lücken, um die es hier geht. Der Weg über den ETL-Gedanken ist der belastbarere: extrahieren, umformen, laden, und der Umformschritt ist der, in dem die Wirklichkeit zurechtgebogen wird.

Maskieren statt löschen

Erst danach kommt der Teil, den die meisten für die ganze Aufgabe halten. Die Regel dabei ist eine einzige und sie ist wichtiger als die Wahl des Werkzeugs: Maskiert wird deterministisch.

Wird derselbe Name an zwei Stellen zu zwei verschiedenen Namen, zerfällt jede Verknüpfung, und der Testbestand verliert genau die Eigenschaft, wegen der er entstanden ist. Wird er an beiden Stellen zum selben Ersatz, bleibt die Struktur erhalten:

-- Gleicher Eingabewert, gleicher Ersatz, ueberall im Bestand.
-- Das Geheimnis liegt in der Umgebung, nicht im Skript.
CREATE FUNCTION maske(wert text, feld text) RETURNS text AS
  'SELECT substr(encode(hmac(wert || feld,
     current_setting(''mask.key''), ''sha256''), ''hex''), 1, 12)'
  LANGUAGE sql IMMUTABLE;

UPDATE kunde SET
  email    = maske(email, 'email') || '@beispiel.invalid',
  nachname = maske(nachname, 'name'),
  telefon  = '+49 30 ' || substr(maske(telefon, 'tel'), 1, 8);

Zwei Dinge daran sind kein Detail. Die Domäne .invalid ist reserviert und nicht zustellbar, also kann kein Stapellauf sie versehentlich erreichen. Und was das Format angeht: Eine Telefonnummer, die keine Telefonnummer mehr ist, findet den Fehler in der Formatprüfung nicht, um den es eigentlich ging.

Drei Arten von Feldern werden nicht maskiert, sondern gar nicht erst exportiert: Zugangsdaten für Drittsysteme, Zahlungsdaten und alles, was einer besonderen Kategorie nach Artikel 9 unterfällt. Für die wird beim Einspielen ein fester Wert gesetzt.

Die Ausgänge gehören in die Umgebung

Eine Trennung, die regelmäßig verwischt wird und teuer ist: Ob eine E-Mail zugestellt wird, ist keine Eigenschaft der Daten.

Wer versucht, den Versand über die Daten zu verhindern, hat eine Absicherung, die bei jedem neuen Feld und jeder neuen Tabelle wieder löchrig wird. Die belastbare Stelle ist die Umgebung: ein Mailserver, der alles annimmt und nichts weiterleitet, ein Zahlungsanbieter im Testmodus, ausgehende Webhooks, die auf einen Empfänger im eigenen Netz zeigen.

Die Prüfung dafür gehört in die Einrichtung der Umgebung und nicht in eine Anleitung: Wenn die Anwendung startet und der Mailserver nicht der lokale ist, startet sie nicht.

Denselben Ausschnitt braucht die Probe des Wiederanlaufs: Backups, die niemand je wiederhergestellt hat.

Auffrischen, und der Weg zurück

Ein Testbestand veraltet, und zwar nicht an den Daten, sondern am Schema. Vier Wochen sind ein guter Rhythmus: nah genug, um neue Felder mitzunehmen, selten genug, um den Lauf nicht zu einer Last zu machen.

Wichtiger als der Rhythmus ist die Richtung zurück. Jeder Produktionsfehler, der auf einer Datenform beruht, die im Testbestand fehlte, hinterlässt zwei Dinge: die Korrektur und eine neue Regel im Auswahlskript. So wächst der Bestand entlang der Fehler, die tatsächlich passiert sind, statt entlang der Fälle, die jemand sich vorgestellt hat.

Der Aufwand für all das liegt bei ein bis zwei Wochen, und die Grenze ist ehrlich zu benennen: Lasttests bleiben ein anderes Thema, denn zweitausend Kunden verhalten sich nicht wie zwei Millionen. Für alles, was mit Richtigkeit zu tun hat, ist der geformte Ausschnitt dem vollständigen Abzug überlegen, und er darf auf dem Notebook liegen. Wie ich gewachsene Systeme umbaue, steht auf einer eigenen Seite.

Dieser Artikel gehört zu einer Reihe über Systeme, die es schon gibt. Der Rückblick ordnet alle Artikel der Reihe nach Anlass.