Andreas Bruns

Softwareentwicklung für Oldenburg und Bremen

Datenbank-Migration professionell durchführen mit Flyway

Software-Entwicklung im Team ist ohne Einsatz eines Versionsverwaltungssystems (VCS: Version Control System), wie Subversion oder Git, kaum denkbar. Das gemeinsam genutzte Quelltext-Repository erleichtert die Koordinierung zwischen den Entwicklern und es lassen sich problemlos ältere Versionsstände wiederherstellen.

Anhand der Revisionen der Versionsverwaltung wissen wir genau, welcher Programmcode auf unseren unterschiedlichen Stage-Umgebungen (z.B. Entwicklung, Test, Schulung, Produktion) gerade installiert ist. Leider ist es bei vielen Projekten allerdings nicht möglich, die Datenbank auf „Knopfdruck“ zu jeder Revision zu erstellen, weil die Skripte für die Erzeugung der Datenbank nicht gleichwertig revisioniert werden.

Moderne Web-Frameworks beinhalten bereits Werkzeuge für Datenbank-Migrationen, wie beispielsweise Active Record Migrations (Ruby On Rails) oder Evolutions (Play-Framework). Die bekanntesten unabhängigen Vertreter Liquidbase und Flyway werden in dem Heise-Artikel Kontinuierliche Datenbankmigration mit Liquibase und Flyway vorgestellt. In meinem aktuellen Projekt mit einer PostgreSQL-Datenbank habe ich mich für Flyway entschieden, das tatsächlich eine solide Hilfe beim Umgang mit der Datenbank ist.

Flyway auf dem Entwicklungssystem ausführen
Flyway lässt sich auf verschiedene Arten (Command-line, Java-API, Maven, Gradle, Ant, SBT) nutzen. Ich habe zunächst einmal die ‚First Steps‘ der Gradle-Variante durchgeführt, die eine H2-Datenbank verwendet.

Unsere SQL-Skripte werden wegen des Einsatzes von Gradle in dem Verzeichnis src/main/resources/db/migration/ abgelegt. Flyway bietet uns folgende Befehle an, mit der Datenbank zu interagieren:

  • gradle flywayMigrate
    Migriert Schema zur letzten Version und erstellt ggf Metadaten-Tabelle schema_version
  • gradle flywayClean
    Löscht alle Objekte der Datenbank
  • gradle flywayInfo
    Status-Informationen zu den Migrationen ausgeben
  • gradle flywayValidate
    Validiert die durchgeführten Migrationen
  • gradle flywayBaseline
    Anfang der Migration bei nicht leerer Datenbank mit Baseline markieren
  • gradle flywayRepair
    Repariert die Metadaten-Tabelle schema_version

Anpassung für eine neu angelegte PosgreSQL-Datenbank
Nachdem das H2-Beispiel problemlos funktioniert hat, sollen die Migrationskripte jetzt gegen eine PostgreSQL-Datenbank ausgeführt werden. Das Gradle-Skript habe ich folgendermaßen angepasst:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.postgresql:postgresql:9.4-1200-jdbc41'
        classpath 'org.flywaydb:flyway-gradle-plugin:3.2.1'
    }
}

apply plugin: 'org.flywaydb.flyway'
apply plugin: 'java'

flyway {
    url = 'jdbc:postgresql://localhost/myDatabase'
    user = 'myUser'
    password = 'myPassword'
}   

Wenn wir eine frische leere Datenbank haben, dann sollte die Migration mit gradle flywayMigrate funktionieren und folgende Ausgabe in Verbindung mit gradle flywayInfo machen:

bruno@dev:~/$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+---------------------+---------------------+---------+
| Version | Description         | Installed on        | State   |
+---------+---------------------+---------------------+---------+
| 1       | Create person table |                     | Pending |
| 2       | Add people          |                     | Pending |
+---------+---------------------+---------------------+---------+

BUILD SUCCESSFUL

Total time: 2.312 secs

bruno@dev:~/$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate

BUILD SUCCESSFUL

Total time: 2.392 secs

bruno@dev:~/$ gradle flywayInfo   
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+---------------------+---------------------+---------+
| Version | Description         | Installed on        | State   |
+---------+---------------------+---------------------+---------+
| 1       | Create person table | 2016-02-27 09:04:55 | Success |
| 2       | Add people          | 2016-02-27 09:04:55 | Success |
+---------+---------------------+---------------------+---------+

BUILD SUCCESSFUL

Total time: 2.374 secs

Anpassung für eine bestehende PosgreSQL-Datenbank
Falls wir Flyway für eine bestehende Datenbank einsetzen wollen, dann muss zunächst eine Baseline mit gradle flywayBaseline gesetzt werden. Das kann auch nötig sein, wenn eine neu erstellte PostgreSQL-Datenbank bereits eine Extension beinhaltet (wie PostGIS) und damit nicht wirklich leer ist (siehe auch Stackoverflow). Eine Flyway-Migration antwortet bei fehlender Baseline mit:

ERROR: Found non-empty schema "public" without metadata table! Use baseline() or set baselineOnMigrate to true to initialize the metadata table.

Alternativ zu dem Befehl gradle flywayBaseline können wir in unserer Flyway-Konfiguration auch den Parameter flyway.baselineOnMigrate=true setzen. Bei Einsatz einer Baseline müssen wir darauf achten, dass damit die Version 1 der SQL-Skripte vergeben ist und wir bei einer folgenden Version, wie 1.1 oder 2, starten müssen:

bruno@dev:~/$ ls -Al src/main/resources/db/migration/                     
total 16
-rw-r--r--  1 bruno  staff   77 27 Feb 01:10 V1.1__Create_person_table.sql
-rw-r--r--  1 bruno  staff  156 27 Feb 01:11 V1.2__Add_people.sql
bruno@dev:~/$ gradle flywayBaseline
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayBaseline

BUILD SUCCESSFUL

Total time: 2.42 secs

bruno@dev:~/$ gradle flywayMigrate 
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate

BUILD SUCCESSFUL

Total time: 2.438 secs

bruno@dev:~/$ gradle flywayInfo    
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-----------------------+---------------------+---------+
| Version | Description           | Installed on        | State   |
+---------+-----------------------+---------------------+---------+
| 1       | << Flyway Baseline >> | 2016-02-27 10:36:42 | Baselin |
| 1.1     | Create person table   | 2016-02-27 10:36:59 | Success |
| 1.2     | Add people            | 2016-02-27 10:36:59 | Success |
+---------+-----------------------+---------------------+---------+

BUILD SUCCESSFUL

Total time: 2.387 secs

Flyway auf Server ausführen
Auf den Servern wird nicht die gesamte Gradle-Installation benötigt, sondern lediglich Flyway als Command-line Tool. Nach den folgenden Schritten ist Flyway auch schon auf der Kommandozeile einsatzbereit:

  • Herunterladen von Flyway (ohne JRE 12 MB, mit JRE 154 MB)
  • Entpacken der Flyway-Datei
  • Datenbank-Parameter in Konfiguration eintragen: conf/flyway.conf
    • flyway.url=jdbc:postgresql://localhost/myDatabase
    • flyway.user=myUser
    • flyway.password=myPassword
    • flyway.baselineOnMigrate=true
  • SQL-Skripte kopieren in das Verzeichnis: sql/
  • Migration durchführen: ./flyway migrate
wget https://bintray.com/artifact/download/business/maven/flyway-commandline-3.2.1-linux-x64.tar.gz
tar -zxvf flyway-commandline-3.2.1-linux-x64.tar.gz 
cd flyway-3.2.1/
bruno@xyz:~/flyway-3.2.1$ ls -Al
total 148
drwxrwxr-x 2 bruno bruno   4096 Feb 22 20:33 conf
drwxrwxr-x 2 bruno bruno   4096 Feb 22 20:33 drivers
-rwxr--r-- 1 bruno bruno   1441 Mar 20  2015 flyway
-rw-r--r-- 1 bruno bruno   1132 Mar 20  2015 flyway.cmd
drwxrwxr-x 2 bruno bruno   4096 Feb 22 20:33 jars
drwxr-xr-x 4 bruno bruno   4096 Mar 20  2015 jre
drwxrwxr-x 2 bruno bruno   4096 Feb 22 20:33 lib
-rw-r--r-- 1 bruno bruno 107852 Mar 20  2015 LICENSES-THIRD-PARTY.txt
-rw-r--r-- 1 bruno bruno    562 Mar 20  2015 LICENSE.txt
-rw-r--r-- 1 bruno bruno    950 Mar 20  2015 README.txt
drwxrwxr-x 2 bruno bruno   4096 Feb 22 20:33 sql
nano conf/flyway.conf 

Die eigentliche Migration führe ich dann mit SCP und SSH durch, indem zuerst die SQL-Dateien auf den Server kopiert werden und anschließend auf dem Server Flyway ausgeführt wird.

scp src/main/resources/db/migration/* bruno@123.134.156.167:flyway-3.2.1/sql/ 
ssh bruno@123.134.156.167 "cd flyway-3.2.1; ./flyway migrate"

Gerade bei Einsatz von Continuous Integration und Continuous Delivery sind SQL-Skripte für Datenbank-Migration unerlässlich. Ein professionelles Werkzeug, wie Flyway, ist glücklicherweise auch leicht in ein bestehendes Projekt zu integrieren.

Kommentare sind geschlossen.