Hallo, Kumpel!
Heute wollen wir darüber sprechen, was Datenbankmigrationen sind und warum sie so wichtig sind. In der heutigen Welt ist es keine Überraschung, dass Änderungen an einer Datenbank sorgfältig und gemäß eines spezifischen Prozesses durchgeführt werden sollten. Idealerweise sollten diese Schritte in unseren CI/CD-Pipeline integriert werden, damit alles automatisch abläuft.
Hier ist unsere Agenda:
- Was ist das Problem?
- Wie beheben wir es?
- Ein einfaches Beispiel
- Ein komplexeres Beispiel
- Empfehlungen
- Ergebnisse
- Schlussfolgerung
Was ist das Problem?
Wenn Ihr Team noch nie mit Datenbankmigrationen zu tun hatte und Sie nicht ganz sicher sind, warum sie benötigt werden, wollen wir das klären. Wenn Sie bereits die Grundlagen kennen, können Sie gerne vorspulen.
Hauptproblem
Wenn wir „geplante“ und „reibungslose“ Änderungen an der Datenbank vornehmen, müssen wir die Serviceverfügbarkeit aufrechterhalten und die SLA-Anforderungen erfüllen (damit Benutzer nicht unter Ausfallzeiten oder Verzögerungen leiden). Stellen Sie sich vor, Sie möchten den Spaltentyp in einer Tabelle mit 5 Millionen Benutzern ändern. Wenn Sie dies „frontal“ angehen (z.B. einfach ALTER TABLE
ohne Vorbereitung ausführen), könnte die Tabelle für eine erhebliche Zeit gesperrt sein – und Ihre Benutzer wären ohne Service.
Um solche Kopfschmerzen zu vermeiden, befolgen Sie zwei Regeln:
- Führen Sie Migrationen so durch, dass die Tabelle nicht gesperrt wird (oder minimieren Sie zumindest die Sperrungen).
- Wenn Sie den Typ einer Spalte ändern müssen, ist es oft einfacher, zuerst eine neue Spalte mit dem korrekten Typ zu erstellen und dann die alte anschließend zu löschen.
Ein weiteres Problem: Versionskontrolle und Rollbacks
Manchmal müssen Sie eine Migration rückgängig machen.
Dies manuell zu tun – in die Produktionsdatenbank zu gehen und mit den Daten herumzuspielen – ist nicht nur riskant, sondern wahrscheinlich auch unmöglich, wenn Sie keinen direkten Zugriff haben. Hier kommen dedizierte Migrationswerkzeuge ins Spiel. Sie ermöglichen es Ihnen, Änderungen sauber anzuwenden und sie bei Bedarf zurückzunehmen.
Wie beheben wir das? Verwenden Sie die richtigen Tools
Jede Sprache und jedes Ökosystem hat seine eigenen Migrationswerkzeuge:
- Für Java sind Liquibase oder Flyway gängig.
- Für Go ist eine beliebte Wahl Goose (das werden wir uns hier ansehen).
- Und so weiter.
Goose: Was es ist und warum es nützlich ist
Goose ist ein leichtgewichtiges Go-Dienstprogramm, das Ihnen hilft, Migrationen automatisch zu verwalten. Es bietet:
- Einfachheit. Minimale Abhängigkeiten und eine transparente Dateistruktur für Migrationen.
- Vielseitigkeit. Unterstützt verschiedene DB-Treiber (PostgreSQL, MySQL, SQLite usw.).
- Flexibilität. Schreiben Sie Migrationen in SQL oder Go-Code.
Installation von Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
So funktioniert es: Migrationsstruktur
Nach Vorgabe sucht Goose nach Migrationsdateien in db/migrations
. Jede Migration folgt diesem Format:
NNN_migration_name.(sql|go)
NNN
ist die Migrationsnummer (z. B.001
,002
usw.).- Danach kann ein beliebiger beschreibender Name stehen, z. B.
init_schema
. - Die Erweiterung kann
.sql
oder.go
sein.
Beispiel einer SQL-Migration
Datei: 001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
Unser erstes Beispiel
Ändern eines Spaltentyps (String → Int)
Angenommen, wir haben eine Tabelle users
mit einer Spalte age
vom Typ VARCHAR(255)
. Jetzt möchten wir sie in INTEGER
ändern. So könnte die Migration aussehen (Datei 005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
Was hier passiert:
-
Up-Migration
- Wir ändern die Spalte
age
inINTEGER
. Die KlauselUSING (age::INTEGER)
sagt PostgreSQL, wie vorhandene Daten in den neuen Typ konvertiert werden sollen. - Beachten Sie, dass diese Migration fehlschlägt, wenn in
age
Daten enthalten sind, die nicht numerisch sind. In diesem Fall benötigen Sie eine komplexere Strategie (siehe unten).
- Wir ändern die Spalte
-
Abwärtsmigration
- Wenn wir zurückrollen, wird
age
zuVARCHAR(255)
. - Wir verwenden erneut
USING (age::TEXT)
, um vonINTEGER
zurück zu Text zu konvertieren.
- Wenn wir zurückrollen, wird
Die zweiten und komplexen Fälle: Mehrstufige Migrationen
Wenn die Spalte age
möglicherweise unordentliche Daten enthält (nicht nur Zahlen), ist es sicherer, dies in mehreren Schritten zu tun:
- Fügen Sie eine neue Spalte (
age_int
) vom TypINTEGER
hinzu. - Kopieren Sie gültige Daten in die neue Spalte und behandeln oder entfernen Sie ungültige Einträge.
- Löschen Sie die alte Spalte.
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
Um ein ordnungsgemäßes Rollback zu ermöglichen, spiegelt der Abschnitt Down
nur die Aktionen in umgekehrter Reihenfolge wider.
Automatisierung ist der Schlüssel
Um Zeit zu sparen, ist es wirklich praktisch, Migrationsbefehle zu einem Makefile (oder einem anderen Build-System) hinzuzufügen. Hier ist ein Beispiel-Makefile mit den wichtigsten Goose-Befehlen für PostgreSQL.
Angenommen:
- Die DSN für die Datenbank lautet
postgres://user:password@localhost:5432/dbname?sslmode=disable
. - Die Migrationsdateien befinden sich in
db/migrations
.
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
Wie benutzt man es?
1. Erstellen Sie eine neue Migration (SQL-Datei). Dies erzeugt eine Datei db/migrations/002_add_orders_table.sql
.
make new-migration NAME=add_orders_table
2. Führen Sie alle Migrationen aus. Goose wird eine schema_migrations
Tabelle in Ihrer Datenbank erstellen (falls sie nicht bereits existiert) und alle neuen Migrationen in aufsteigender Reihenfolge anwenden.
make migrate-up
3. Rollen Sie die letzte Migration zurück. Einfach die letzte.
make migrate-down
4. Rollen Sie alle Migrationen zurück (Vorsicht bei der Produktion). Vollständiges Zurücksetzen.
make migrate-reset
5. Überprüfen Sie den Migrationsstatus.
make migrate-status
Beispiel-Ausgabe:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
Zusammenfassung
Durch die Verwendung von Migrationswerkzeugen und einem Makefile können wir:
- Direkten Zugriff auf die Produktionsdatenbank einschränken, Änderungen nur über Migrationen durchführen.
- Datenbankversionen leicht verfolgen und sie bei Bedarf zurückrollen.
- Eine einzige, konsistente Historie von Datenbankänderungen aufrechterhalten.
- „Sanfte“ Migrationen durchführen, die eine laufende Produktionsumgebung in einer Mikroservices-Welt nicht beeinträchtigen.
- Zusätzliche Validierung erhalten – jede Änderung wird einen PR- und Codeüberprüfungsprozess durchlaufen (sofern Sie diese Einstellungen haben).
Ein weiterer Vorteil ist, dass es einfach ist, all diese Befehle in Ihre CI/CD-Pipeline zu integrieren. Und denken Sie daran – Sicherheit geht vor.
Zum Beispiel:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
Abschluss und Tipps
Die Hauptideen sind ganz einfach:
- Halten Sie Ihre Migrationen klein und häufig. Sie sind einfacher zu überprüfen, zu testen und bei Bedarf zurückzusetzen.
- Verwenden Sie dasselbe Tool in allen Umgebungen, damit Entwicklung, Test und Produktion synchron sind.
- Integrieren Sie Migrationen in CI/CD, damit Sie nicht von einer Person abhängig sind, die sie manuell ausführt.
Auf diese Weise haben Sie einen zuverlässigen und kontrollierten Prozess zur Änderung Ihrer Datenbankstruktur – einen, der die Produktion nicht beeinträchtigt und es Ihnen ermöglicht, schnell zu reagieren, wenn etwas schief geht.
Viel Erfolg mit Ihren Migrationen!
Vielen Dank fürs Lesen!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service