Wenn alte Daten auf neues Schema treffen: Backfilling und Verifikation von Legacy-Datensätzen

Sie haben gerade eine neue Spalte zu einer Datenbanktabelle hinzugefügt, die seit drei Jahren in Produktion läuft. Die Anwendung schreibt jetzt sowohl in die alte als auch in die neue Struktur. Aber was ist mit den Millionen von Zeilen, die vor dieser Änderung erstellt wurden? Sie sitzen mit NULL-Werten in der neuen Spalte und enthalten Daten, die technisch unvollständig sind.

In diesem Moment wird vielen Teams klar, dass eine Schema-Migration nicht nur das Hinzufügen von Spalten bedeutet. Es geht darum, sicherzustellen, dass jeder vorhandene Datensatz mit den neuen Regeln Schritt hält. Der Prozess, alte Daten in eine neue Struktur zu füllen, wird als Backfill bezeichnet. Und wenn Sie ihn nachlässig durchführen, können Sie Ihre Produktionstabelle für Stunden sperren, Ihre Anwendung verlangsamen und alle frustrieren, die auf die Rückgabe von Abfragen warten.

Warum Backfill kein einfaches UPDATE ist

Der naive Ansatz ist, eine einzige große UPDATE-Abfrage auszuführen, die die neue Spalte für jede Zeile setzt, in der sie NULL ist. Wenn Ihre Tabelle ein paar tausend Zeilen hat, könnte dies in Sekunden erledigt sein. Aber wenn Sie es mit zig Millionen Zeilen zu tun haben, kann diese einzelne Abfrage eine Tabellensperre für lange Zeit halten. Andere Abfragen stellen sich in die Warteschlange. Die Anwendung wird langsamer. Benutzer sehen Timeouts.

Backfill muss mit der gleichen Sorgfalt geplant werden, die Sie jeder Produktionsoperation zukommen lassen würden. Der erste Schritt besteht darin, genau zu definieren, welche Daten gefüllt werden müssen. Angenommen, Sie haben eine Spalte status hinzugefügt, deren Wert aus zwei vorhandenen Spalten abgeleitet wird: is_active und deleted_at. Ihre Backfill-Logik muss denselben Regeln entsprechen, die der neue Anwendungscode beim Schreiben neuer Datensätze verwendet. Wenn die Anwendung status auf 'active' setzt, wenn is_active wahr ist und deleted_at null ist, muss Ihr Backfill dasselbe tun.

Batch-Verarbeitung: Der sichere Weg

Verarbeiten Sie die Daten in kleinen Batches, anstatt eine einzige massive Abfrage durchzuführen. Nehmen Sie tausend oder zehntausend Zeilen, die in der neuen Spalte noch NULL-Werte haben, aktualisieren Sie sie, pausieren Sie dann kurz, bevor Sie zum nächsten Batch übergehen. Dies gibt der Datenbank Zeit zum Durchatmen und ermöglicht anderen Abfragen, fortzufahren, ohne warten zu müssen.

Einige Datenbanken haben integrierte Unterstützung für Batch-Updates. PostgreSQL erlaubt es Ihnen beispielsweise, UPDATE ... WHERE ... LIMIT mit einer Unterabfrage zu verwenden. MySQL unterstützt LIMIT nicht direkt in UPDATE, aber Sie können eine Schleife in einer gespeicherten Prozedur verwenden oder das Batching von Ihrem Migrationsskript aus handhaben. Der Schlüssel ist, jeden Batch klein genug zu halten, damit die Sperrdauer für Ihre Arbeitslast akzeptabel bleibt.

Hier ist ein grobes Muster, das über viele Datenbanken hinweg funktioniert:

while there are rows with NULL in new_column:
    select a batch of primary keys where new_column IS NULL
    update those rows with the computed value
    sleep for a short interval

Das Schlafintervall dient nicht dazu, langsam zu sein. Es geht darum, anderen Operationen eine Chance zu geben, ausgeführt zu werden. Selbst 100 Millisekunden zwischen den Batches können einen großen Unterschied darin machen, wie sich die Datenbank unter Last verhält.

Das folgende Flussdiagramm veranschaulicht diesen sicheren iterativen Ansatz:

flowchart TD A[Start] --> B{Any rows with NULL?} B -- Yes --> C[Select batch of 1000 PKs where new_column IS NULL] C --> D[Compute new column value for batch] D --> E[UPDATE rows WHERE id IN (batch)] E --> F[Sleep 100ms] F --> B B -- No --> G[Done]

Für ein konkretes Beispiel: Angenommen, Sie haben eine Spalte status hinzugefügt, die aus is_active und deleted_at abgeleitet wird. Ein Batch-UPDATE in PostgreSQL könnte so aussehen:

UPDATE your_table
SET status = CASE
    WHEN is_active = true AND deleted_at IS NULL THEN 'active'
    WHEN is_active = false AND deleted_at IS NULL THEN 'inactive'
    ELSE 'deleted'
END
WHERE id IN (
    SELECT id
    FROM your_table
    WHERE status IS NULL
    LIMIT 1000
);

Die innere Unterabfrage wählt einen kleinen Batch von Primärschlüsseln aus, bei denen die neue Spalte noch NULL ist. Das äußere UPDATE berührt nur diese Zeilen und hält die Sperre kurz. Wiederholen Sie dies in einer Schleife, bis keine NULLs mehr vorhanden sind.

Zwei Wahrheitsquellen während des Backfills

Während der Backfill läuft, schreibt die Anwendung weiterhin neue Daten sowohl in die alte als auch in die neue Struktur. Das bedeutet, dass Sie jetzt zwei Wahrheitsquellen für dieselben logischen Daten haben:

  • Daten, die von der Anwendung geschrieben wurden (immer konsistent, da die Anwendung beide Stellen gleichzeitig beschreibt)
  • Daten, die durch den Backfill-Prozess gefüllt wurden (müssen exakt der Logik der Anwendung entsprechen)

Beide müssen für dieselbe Eingabe dieselben Werte liefern. Wenn Ihre Backfill-Logik einen Fehler hat oder wenn sich die Anwendungslogik zwischen dem Start und dem Ende des Backfills geändert hat, erhalten Sie inkonsistente Daten. Deshalb ist die Verifikation nicht optional.

Verifikation: Mehr als nur auf NULL prüfen

Nach Abschluss des Backfills ist der natürliche Instinkt, eine schnelle Prüfung durchzuführen: "Sind noch NULL-Werte übrig?" Das sagt Ihnen, dass die Spalte befüllt ist, aber es sagt Ihnen nicht, ob die Werte korrekt sind.

Eine ordnungsgemäße Verifikation bedeutet, den berechneten Wert mit dem erwarteten Wert für jede Zeile zu vergleichen. Sie lesen die alten Spalten, berechnen, was der neue Wert sein sollte, und vergleichen ihn mit dem, was tatsächlich in der neuen Spalte gespeichert ist. Jede Abweichung muss untersucht werden.

Eine Abweichung kann verschiedene Ursachen haben:

  • Ein Fehler in der Backfill-Logik, die den Wert anders berechnet als die Anwendung
  • Daten, die sich zwischen dem Zeitpunkt der Backfill-Verarbeitung und der Verifikation geändert haben
  • Die Anwendung schreibt einen anderen Wert, weil ihre Logik während der Migration aktualisiert wurde
  • Race Conditions, bei denen eine Zeile nach dem Backfill, aber vor der Verifikation von der Anwendung aktualisiert wurde

Jede Abweichung muss auf ihre Ursache zurückgeführt werden. Manchmal ist es eine einfache Korrektur. Manchmal offenbart es ein tieferes Problem im Migrationsplan.

Progressive Verifikation

Sie müssen nicht die gesamte Tabelle in einem Durchgang verifizieren. Beginnen Sie mit einer Zufallsstichprobe. Wählen Sie tausend Zeilen aus, vergleichen Sie die Werte und prüfen Sie, ob alles übereinstimmt. Wenn die Stichprobe bestanden wird, gehen Sie zu einer größeren Stichprobe über. Wenn auch diese bestanden wird, führen Sie eine vollständige Verifikation durch.

Bei großen Tabellen bedeutet eine vollständige Verifikation nicht, jede Zeile einzeln zu lesen. Sie können eine Abfrage schreiben, die Abweichungen zählt, ohne alle Daten abzurufen. Etwa so:

SELECT COUNT(*) FROM table
WHERE computed_value != actual_new_value

Wenn die Anzahl Null ist, ist alles sauber. Wenn sie nicht Null ist, wissen Sie, wie viele Zeilen Aufmerksamkeit benötigen, und Sie können sie in Batches untersuchen.

Wenn die Verifikation bestanden ist

Sobald die Verifikation bestätigt, dass jede Zeile in der neuen Struktur dem erwarteten Wert entspricht, haben Sie Vertrauen, dass die Daten konsistent sind. Aber die Migration ist noch nicht abgeschlossen. Die Anwendung schreibt immer noch in beide Strukturen. Der nächste Schritt besteht darin, das Lesen aus dem alten Schema zu stoppen und sich vollständig auf das neue zu verlassen. Dieser Übergang ist eine separate Phase mit eigenen Risiken und Verifikationsschritten.

Backfill und Verifikation sind die Grundlage, die diesen Übergang sicher macht. Ohne sie wechseln Sie zu einem neuen Schema mit unvollständigen oder falschen Daten. Mit ihnen wissen Sie genau, was Sie haben.

Praktische Checkliste

  • Definieren Sie die genaue Logik zur Berechnung des neuen Werts, die dem Anwendungscode entspricht
  • Wählen Sie eine Batch-Größe, die die Sperrdauer für Ihre Datenbank akzeptabel hält
  • Implementieren Sie Batching mit einer Pause zwischen den Batches
  • Führen Sie den Backfill in einem Wartungsfenster oder in einer verkehrsarmen Zeit durch
  • Verifizieren Sie mit einer Zufallsstichprobe vor der vollständigen Verifikation
  • Untersuchen Sie jede Abweichung, bevor Sie fortfahren
  • Dokumentieren Sie die Backfill-Logik und die Verifikationsergebnisse für zukünftige Referenzen

Die konkrete Erkenntnis

Backfill ist keine technische Pflichtaufgabe, die Sie schnell hinter sich bringen. Es ist der Schritt, in dem Sie beweisen, dass Ihre Migrationslogik mit echten Daten in großem Maßstab funktioniert. Wenn Sie die Verifikation überspringen, vertrauen Sie darauf, dass Ihr Backfill-Code perfekt ist und dass sich während des Prozesses keine Daten geändert haben. Dieses Vertrauen ist selten gerechtfertigt. Führen Sie den Backfill in Batches durch, verifizieren Sie die Ergebnisse systematisch und gehen Sie erst zur nächsten Phase über, wenn Sie Beweise haben, dass jeder Datensatz korrekt ist.