Backfilling von Legacy-Daten ohne die Produktionsdatenbank zu gefährden
Sie haben gerade eine neue Migration deployed, die eine Spalte last_login_at zur Benutzertabelle hinzufügt. Die Schemaänderung verlief reibungslos. Aber jetzt sehen Sie sich die Daten an: Jeder vorhandene Benutzer hat in dieser Spalte einen Nullwert. Die gesamte Login-Historie von letzter Woche, letztem Monat, letztem Jahr – all das ist für das neue Feld unsichtbar.
Dies ist der Moment, in dem Sie ein Backfill benötigen.
Was Backfill eigentlich bedeutet
Backfill ist der Prozess, bei dem Daten befüllt werden, die bereits existierten, bevor eine Migration angewendet wurde. Es geht nicht darum, Daten in eine neue Struktur zu verschieben – dafür sind Migrationsskripte zuständig. Backfill geht es darum, alte Daten auf den neuesten Stand der Regeln zu bringen, die Ihr System jetzt befolgt.
Die Situationen, in denen ein Backfill notwendig wird, sind häufig:
- Sie haben eine neue Spalte hinzugefügt, aber vorhandene Zeilen haben keine Werte dafür.
- Sie haben geändert, wie Adressen gespeichert werden, von einem einzelnen Textfeld zu separaten Spalten für Straße, Stadt und Postleitzahl.
- Sie haben eine neue Berechnung eingeführt, wie einen Risikoscore für Transaktionen, aber vergangene Transaktionen wurden nie bewertet.
In jedem Fall sitzen die Daten da, gültig, aber unvollständig. Das System weiß, was es mit neuen eingehenden Daten tun soll, aber die alten Daten stecken im vorherigen Format fest.
Warum Sie nicht alles auf einmal verarbeiten können
Der naive Ansatz ist, eine einzelne Abfrage auszuführen, die alle Zeilen auf einmal aktualisiert. Für eine kleine Tabelle mit ein paar hundert Zeilen funktioniert das gut. Für eine Tabelle mit Millionen von Zeilen ist es eine Katastrophe, die nur darauf wartet, zu passieren.
Ein einzelnes massives Update sperrt Zeilen, verbraucht Transaktionslogs und verlangsamt jede andere Abfrage, die auf dieselbe Tabelle zugreift. Wenn Ihre Anwendung während des Backfills Benutzer bedient, werden diese Benutzer Timeouts, langsame Antworten oder fehlgeschlagene Anfragen erleben. Der Datenbank könnte sogar der Speicher oder Festplattenplatz ausgehen, während sie versucht, die Operation zu bewältigen.
Die Lösung besteht darin, Daten in kleinen, kontrollierten Blöcken zu verarbeiten.
Batch-Verarbeitung: Die Kernmethode
Anstatt eine Million Zeilen auf einmal zu aktualisieren, aktualisieren Sie zehntausend Zeilen auf einmal, machen eine Pause und verarbeiten dann den nächsten Batch. Dies wird als Batch-Verarbeitung bezeichnet und ist die Grundlage für sichere Backfills.
So funktioniert es in der Praxis:
Das folgende Flussdiagramm veranschaulicht die vollständige Backfill-Schleife, einschließlich Idempotenzprüfungen und Drosselung:
-- Verarbeitet einen Batch von Zeilen, die noch ein Backfill benötigen
UPDATE users
SET last_login_at = (
SELECT MAX(login_time)
FROM login_history
WHERE login_history.user_id = users.id
)
WHERE last_login_at IS NULL
LIMIT 10000;
Nachdem dies ausgeführt wurde, überprüfen Sie, wie viele Zeilen betroffen waren. Wenn es der Batch-Größe entspricht, warten Sie ein paar Sekunden und führen es erneut aus. Wenn es weniger Zeilen zurückgibt, ist das Backfill fast abgeschlossen.
Die richtige Batch-Größe wählen
Es gibt keine universelle Batch-Größe, die für jede Datenbank funktioniert. Die richtige Größe hängt ab von:
- Wie leistungsfähig Ihr Datenbankserver ist.
- Wie stark die Anwendung die Datenbank belastet.
- Wie komplex die Aktualisierungslogik ist.
- Wie viel Transaktionslog-Speicherplatz verfügbar ist.
Beginnen Sie mit einer konservativen Größe, wie 5.000 Zeilen. Führen Sie ein paar Batches aus und beobachten Sie die Datenbankmetriken: CPU-Auslastung, Festplatten-I/O, Abfragelatenz von der Anwendungsseite. Wenn die Datenbank es problemlos bewältigt, verdoppeln Sie die Batch-Größe. Wenn Sie Spitzen in der Latenz oder Sperrkonflikte sehen, halbieren Sie die Größe.
Das Ziel ist, eine Batch-Größe zu finden, die in ein paar Sekunden abgeschlossen ist, ohne spürbare Auswirkungen auf andere Abfragen. Ein Batch, der dreißig Sekunden dauert, ist für ein Produktionssystem unter normaler Last wahrscheinlich zu groß.
Drosselung: Der Datenbank Zeit zum Atmen geben
Die Batch-Größe steuert, wie viel Arbeit in einer Einheit erledigt wird. Die Drosselung steuert, wie viel Zeit zwischen den Einheiten vergeht.
Fügen Sie nach Abschluss jedes Batches eine bewusste Pause ein, bevor Sie mit dem nächsten beginnen. Diese Pause gibt der Datenbank Zeit, ausstehende Schreibvorgänge zu leeren, Sperren freizugeben und andere Abfragen ohne Konkurrenz durch Ihr Backfill zu bedienen.
Eine typische Drosselung könnte zwei bis fünf Sekunden zwischen den Batches betragen. Während der Hauptgeschäftszeiten könnten Sie sie auf zehn oder fünfzehn Sekunden erhöhen. Während Wartungsfenstern könnten Sie sie auf eine Sekunde reduzieren oder ganz entfernen.
Die Drosselung ist Ihr Sicherheitsventil. Wenn etwas schiefgeht – ein plötzlicher Anstieg des Anwendungsverkehrs, eine langsame Abfrage von einem anderen Team, eine Warnung zur Replikationsverzögerung – können Sie die Pause erhöhen und dem System Zeit geben, sich zu stabilisieren, bevor Sie fortfahren.
Backfills idempotent machen
Ein Backfill-Skript muss sicher mehrmals ausgeführt werden können. Wenn ein Batch auf halbem Weg fehlschlägt oder Sie den gesamten Prozess neu starten müssen, sollte das erneute Ausführen desselben Skripts keine doppelten Daten oder Fehler erzeugen.
Idempotenz für Backfills bedeutet normalerweise eines von zwei Dingen:
- Prüfen vor dem Schreiben: Aktualisieren Sie nur Zeilen, die noch Nullwerte oder alte Werte haben.
- Upsert-Logik verwenden: Einfügen oder aktualisieren, basierend darauf, ob die Zeile bereits die neuen Daten hat.
Für das last_login_at-Beispiel ist die obige Abfrage bereits idempotent, da sie nur Zeilen anvisiert, in denen die Spalte noch null ist. Wenn ein Batch nach dem Aktualisieren von 5.000 Zeilen fehlschlägt, überspringt der nächste Durchlauf diese Zeilen und fährt mit den verbleibenden fort.
Für komplexere Backfills, wie die Neuberechnung eines abgeleiteten Werts, könnten Sie eine processed_at-Zeitstempelspalte hinzufügen. Das Backfill-Skript prüft, ob processed_at null ist, bevor es jede Zeile verarbeitet. Nach der Verarbeitung wird der Zeitstempel gesetzt, und nachfolgende Durchläufe überspringen diese Zeile.
Protokollierung: Das Detail, an das niemand denkt, bis es kaputtgeht
Wenn ein Backfill stundenlang läuft, müssen Sie wissen, wo es steht und ob es noch korrekt funktioniert. Protokollieren Sie jeden Batch:
- Batch-Nummer und Zeitbereich.
- Anzahl der verarbeiteten Zeilen.
- Dauer des Batches.
- Alle aufgetretenen Fehler.
- Aktueller Fortschritt als Prozentsatz oder Zeilenanzahl.
Dieses Protokoll dient zwei Zwecken. Erstens: Wenn das Backfill unerwartet stoppt, können Sie vom letzten abgeschlossenen Batch fortsetzen, anstatt von vorne zu beginnen. Zweitens: Wenn das Backfill abgeschlossen ist, haben Sie eine Aufzeichnung dessen, was genau passiert ist, was beim Debuggen und bei Audits hilft.
Ein einfacher Protokolleintrag könnte so aussehen:
2025-03-15 14:32:01 | Batch 47 | 10.000 Zeilen verarbeitet | Dauer 3,2s | Keine Fehler
2025-03-15 14:32:06 | Batch 48 | 10.000 Zeilen verarbeitet | Dauer 3,1s | Keine Fehler
2025-03-15 14:32:11 | Batch 49 | 10.000 Zeilen verarbeitet | Dauer 3,5s | Keine Fehler
Eine praktische Backfill-Checkliste
Bevor Sie ein Backfill in der Produktion ausführen, gehen Sie diese Liste durch:
- Batch-Größe wurde in einer Staging-Umgebung mit ähnlichem Datenvolumen getestet.
- Drosselungsintervall ist konfiguriert und ohne Codeänderungen anpassbar.
- Skript ist idempotent – zweimaliges Ausführen führt zum gleichen Ergebnis.
- Protokollierung erfasst Batch-Fortschritt, Fehler und Zeitmessung.
- Rollback-Plan existiert: Sie können das Backfill rückgängig machen, wenn etwas schiefgeht.
- Monitoring ist eingerichtet, um eine Verschlechterung der Datenbankleistung zu erkennen.
- Ein Trockenlauf wurde auf einer Kopie der Produktionsdaten durchgeführt.
Das Fazit
Backfilling ist kein einmaliges Skript, das Sie schreiben und vergessen. Es ist eine kontrollierte Operation, die der Tatsache Rechnung trägt, dass Ihre Datenbank Benutzer bedient, während Sie deren Daten ändern. Batch-Verarbeitung und Drosselung sind keine Optimierungen – sie sind die Mindestanforderungen, um diese Arbeit sicher durchzuführen. Ohne sie sind Sie nur eine große Abfrage von einem Produktionsvorfall entfernt.