Datenbankmigrationen¶
Wie Evey bei einem Update den Aufbau seiner Datenbank (das Schema) ändert, was
Sie vorher und nachher prüfen, und wie Sie den Fehler beheben, wenn eine Migration
auf halbem Weg stehen bleibt. Für Betreiber der Standardinstallation, in der
die Datenbank im Container evey-server läuft. Wer selbst eine Migration
schreibt, findet die Regeln am Ende.
Wie Migrationen laufen¶
Die Migrationen sind in das Programm eingebaut und werden von
golang-migrate angewendet, wenn der Server startet. Der Stand steht in der
Tabelle schema_migrations, eine Zeile {version, dirty}. Bevor der Server
etwas anwendet, schreibt er einen vollständigen Dump nach EVEY_BACKUP_DIR
(/data/backups, also ./evey-data/backups auf dem Rechner) mit dem Namen
pre-migration-<version>-<UTC timestamp>.sql.gz. Er behält den neuesten
Dump jeder der 3 jüngsten Schemaversionen.
Dieselben Schritte gibt es auch von Hand:
docker exec evey-server evey admin migrate version # Code 1, wenn das Schema „dirty“ ist
docker exec evey-server evey admin migrate up # alle ausstehenden Migrationen anwenden
docker exec evey-server evey admin migrate down # genau einen Schritt zurückrollen
up und down legen zuerst einen Dump an; --no-backup lässt ihn weg. So
öffnen Sie eine SQL-Shell auf der eingebauten Datenbank:
docker exec -it evey-server sh -c \
'PGPASSWORD=$(cat /data/keys/postgres.password) psql -h 127.0.0.1 -U evey evey'
Sicher aktualisieren¶
-
Legen Sie eine Sicherung an unter Verwaltung → Sicherung & Wiederherstellung (Jetzt ausführen), oder aus einem Skript:
Der automatische Dump vor der Migration ist eine zweite Kopie, kein Ersatz. -
Prüfen Sie die aktuelle Version:
docker exec evey-server evey admin migrate versiongibtschema version: Naus. Steht dortDIRTY, lesen Sie vor dem Update Eine Migration, die auf halbem Weg stehen blieb. -
Bei einem großen Katalog schauen Sie sich die größten Tabellen an: Eine Migration, die eine davon neu schreibt, dauert länger. Die Gesundheitsprüfung des Containers lässt dafür 300 Sekunden Startzeit.
-
Starten Sie das neue Image und verfolgen Sie das Protokoll, bis die Migrationen angewendet sind und der Server antwortet:
Ein erfolgreiches Update zeigt: eine Zeilemigrations: pre-migration backup written, wenn es etwas anzuwenden gab, dann das Banner des Servers, dann antwortetcurl -sf http://localhost:9091/healthz. -
Danach bestätigen Sie die Version und suchen nach Fehlern:
Leeren Sie den Transkodierungs-Cache (Verwaltung → Transkodierungs-Cache → Alle leeren), wenn die neue Version ändert, wie Videos kodiert werden.
Zur vorherigen Version zurückkehren¶
docker exec evey-server evey admin rollback gibt die laufende Version, die
Schemaversion, den neuesten Dump von vor einer Migration und die genauen
Befehle aus, die Sie auf dem Rechner ausführen. Er ändert nichts: Der
Container hat keinen Zugriff auf Docker.
Der übliche Weg zurück ist das vorherige Image plus der Dump, der vor dem
Update angelegt wurde: siehe Zurückkehren.
Spielen Sie den Dump ein, statt nach unten zu migrieren, wenn das Update
Daten entfernt hat (eine gelöschte Spalte, ein geänderter Typ).
evey admin migrate down geht dagegen eine Migration nach der anderen
zurück, wenn die .down.sql dieses Schritts ausreicht.
Achtung
Bearbeiten Sie schema_migrations nie von Hand, außer um einen
„dirty“-Zustand zu beheben (unten), und legen Sie vorher einen Dump an.
Eine Migration, die auf halbem Weg stehen blieb¶
Zeigt das Protokoll Dirty database version N. Fix and force version., ist
eine Migration mittendrin abgestürzt, und golang-migrate macht nicht weiter.
Bringen Sie das Schema in den gewünschten Zustand und löschen Sie dann die
Markierung:
SELECT version, dirty FROM schema_migrations;
-- Den Rest der up.sql von Hand anwenden oder mit der down.sql zurücknehmen,
-- dann die Version eintragen, die Sie tatsächlich erreicht haben:
UPDATE schema_migrations SET version = <N>, dirty = false;
Starten Sie den Container neu; die Migrationen machen von dort aus weiter. Den Dump von vor der Migration wiederherzustellen (Wiederherstellung) ist die andere Möglichkeit.
Eine Migration schreiben¶
Für Mitwirkende. Die Dateien liegen unter
internal/adapters/driven/postgres/migrations/NNNNNN_<name>.{up,down}.sql.
Beispiele, die schon ausgeliefert wurden:
000066_cascade_and_email_unique.up.sql-ALTER TABLE ... DROP CONSTRAINT ... ADD CONSTRAINT ... FOREIGN KEY. Setzt für die Dauer des ADDACCESS EXCLUSIVEauf beide Tabellen. Günstig bei kleinen Tabellen, problematisch beimedia_items/watch_progress.000067_sort_title_index.up.sql,000068_watch_progress_composite_index.up.sql,000069_quick_connect_shortcode_index.up.sql- einfachesCREATE INDEX, das bei großen Tabellen das Schreiben sperrt.CREATE INDEX CONCURRENTLYkann nicht in einer Transaktion von migrate laufen.000072_syncplay_queue.up.sql-ALTER TABLE ... ADD COLUMN ... NOT NULL DEFAULT '[]'. Ab PG 11 ändert das nur Metadaten, hält aber trotzdem kurzACCESS EXCLUSIVE.
Richtlinien:
- Ein langes ALTER, das die Tabelle neu schreibt (Typänderung, ein nicht konstanter Standardwert auf einem alten PG), bedeutet bei einem großen Katalog eine Ausfallzeit.
- DROP COLUMN ändert nur Metadaten, aber ohne Sicherung kommt die Spalte nicht zurück. Löschen Sie sie nie in der Version, die aufhört, sie zu lesen: erst nicht mehr lesen, ausrollen, dann in der nächsten Version löschen.
- Fremdschlüssel auf großen Tabellen:
ADD CONSTRAINT ... NOT VALID, dannVALIDATE CONSTRAINTin einer späteren Migration (validate nimmt nurSHARE UPDATE EXCLUSIVE). - UPDATE in Stapeln / Nachfüllen von Daten: nie in einer einzigen Migration auf einer großen Tabelle. Liefern Sie eine Schemamigration aus, die nichts tut, plus eine Aufgabe im Hintergrund.
- Eine Spalte ersetzen (erweitern, migrieren, zusammenziehen): Packen Sie
ADD COLUMN+ Nachfüllen +SET NOT NULL+DROP COLUMNnicht in eine Datei. Diese Form funktioniert nur, weil Evey als einzelne Instanz läuft und die Migrationen beim Start der Reihe nach angewendet werden; bei einem schrittweisen Ausrollen würden die noch nicht aktualisierten Instanzen Fehler liefern.000077und000098_playlists_profile_id.up.sqlwurden so geschrieben und bleiben so, weil sie schon ausgeliefert sind. Verteilen Sie eine neue auf drei Versionen: - erweitern - die neue Spalte als nullable hinzufügen, beide Spalten schreiben, weiter die alte lesen;
- migrieren - die neue Spalte nachfüllen (Aufgabe im Hintergrund, nicht die Migration), dann das Lesen auf sie umstellen;
- zusammenziehen - sobald kein ausgerollter Code die alte Spalte mehr
verwendet,
SET NOT NULLund die alte Spalte löschen.