Es war nie so, und nun wieder!
In unserem nächsten Projekt haben wir beschlossen, Liquibase von Anfang an zu verwenden, um zukünftige Probleme zu vermeiden. Wie sich herausstellte, können nicht alle jungen Teammitglieder es richtig nutzen. Ich habe einen internen Workshop durchgeführt, den ich dann in einen Artikel umwandeln wollte.
Der Artikel enthält nützliche Tipps und eine Beschreibung der drei offensichtlichsten Fallstricke, in die man beim Arbeiten mit Migrationswerkzeugen für relationale Datenbanken, insbesondere Liquibase, geraten kann. Er richtet sich an Junior- und Mittelstufen-Java-Entwickler; für erfahrenere Entwickler könnte er interessant sein, um Strukturen zu organisieren und Bekanntes zu wiederholen.

Liquibase und Flyway sind die beiden Hauptkonkurrenten im Bereich der Versionskontrolle relationaler Strukturen in der Java-Welt. Erstere ist völlig kostenlos und wird in der Praxis häufig bevorzugt, weshalb Liquibase als Held dieser Publikation gewählt wurde. Dennoch können einige der beschriebenen Praktiken je nach Architektur Ihrer Anwendung universell sein.
Migrationen relationaler Strukturen sind eine notwendige Antwort auf die geringe Flexibilität relationaler Datenbankspeicher. In einer Zeit, in der objektorientierte Programmierung in Mode war, betrachtete man die Arbeit mit Datenbanken so, dass man einmal das Schema beschreibt und es dann nicht mehr anfasst. Aber die Realität ist immer so, dass alles sich ändert, und Änderungen an der Tabellenstruktur erforderlich sind, und zwar ziemlich häufig. Natürlich kann der Prozess an sich schmerzhaft und unangenehm sein.
Ich werde nicht näher auf die Technologie und die Anleitung zur Hinzufügung der Bibliothek in Ihr Projekt eingehen; dazu wurden bereits genügend Artikel geschrieben:
Darüber hinaus gab es bereits einen großartigen Artikel mit nützlichen Tipps:
Tipps
Ich möchte meine Tipps und Kommentare teilen, die durch Schweiß, Blut und Schmerz beim Lösen von Migrationsproblemen entstanden sind.
1. Vor der Arbeit sollte man sich mit dem Abschnitt der besten Praktiken auf Liquibase
Es werden einfache, aber sehr wichtige Aspekte beschrieben, ohne die die Nutzung der Bibliothek Ihnen das Leben erschweren kann. Zum Beispiel führt ein unstrukturierter Ansatz bei der Verwaltung von Changesets früher oder später zu Verwirrung und fehlerhaften Migrationen. Wenn Sie abhängige Änderungen in der Struktur der Datenbank und der Logik der Dienste nicht gleichzeitig einführen, besteht eine große Wahrscheinlichkeit, dass dies zu roten Tests oder einer fehlerhaften Umgebung führt. Darüber hinaus enthält die Empfehlung zur Nutzung von Liquibase auf der offiziellen Website einen Punkt zur Entwicklung und Überprüfung von Rollback-Skripten zusammen mit den Hauptmigrationsskripten. Nun, und im Artikel gibt es Codebeispiele zu Migrationen und dem Rollback-Mechanismus.
2. Wenn Sie mit Migrationstools begonnen haben, vermeiden Sie manuelle Änderungen an der Datenbankstruktur.
Wie gesagt: „Einmal Persil – immer Persil“. Wenn die Datenbank Ihrer Anwendung mit Liquibase verwaltet wird, führen manuelle Änderungen sofort zu einem inkonsistenten Zustand, und das Vertrauen in die Changesets wird gleich null. Potentielle Risiken sind mehrere Stunden, die für die Wiederherstellung der Datenbank aufgewendet werden müssen, im schlimmsten Fall ein defekter Server. Wenn in Ihrem Team ein DBA-Architekt „alter Schule“ ist, erklären Sie ihm geduldig und durchdacht, wie schlecht es läuft, wenn er die Datenbank nach seinem eigenen Ermessen mit einem hypothetischen SQL Developer bearbeitet.
3. Wenn ein Changeset bereits in das Repository gepusht wurde, vermeiden Sie Bearbeitungen.
Wenn ein anderer Entwickler einen Pull gemacht hat und ein Changeset angewendet hat, das später bearbeitet wird, wird er Sie bei einem Fehler beim Start der Anwendung sicherlich mit einem netten Wort erwähnen. Wenn die Bearbeitung des Changesets irgendwie in die Entwicklung geleitet wird, wird es mühsam, durch Hotfixes zu navigieren. Das Problem liegt in der Validierung der Änderungen durch die Prüfziffer – der Hauptmechanismus von Liquibase. Bei der Bearbeitung des Codes eines Changesets ändert sich die Prüfziffer. Das Bearbeiten von Changesets ist nur möglich, wenn die Möglichkeit besteht, die gesamte Datenbank ohne Datenverlust von Grund auf neu zu erstellen. In diesem Fall kann das Refactoring von SQL- oder XML-Code im Gegenteil das Leben erleichtern und Migrationen leserlicher machen. Ein Beispiel könnte die Situation sein, wenn zu Beginn der Anwendung das Schema der ursprünglichen Datenbank im Team abgestimmt wurde.
4. Haben Sie, wenn möglich, geprüfte Datenbank-Backups.
Hier sollte alles klar sein. Falls die Migration unerwartet fehlschlägt, kann alles zurückgesetzt werden. Liquibase bietet ein Tool zum Zurücksetzen von Änderungen, aber die Skripte für das Zurücksetzen müssen ebenfalls vom Entwickler geschrieben werden, wobei sie die gleiche Wahrscheinlichkeit für Probleme aufweisen können wie die Skripte des Haupt-Changesets. Das bedeutet, dass es in jedem Fall sinnvoll ist, sich mit Backups abzusichern.
5. Verwenden Sie geprüfte Datenbank-Backups in der Entwicklung, wenn möglich.
Wenn dies nicht gegen Verträge oder Datenschutzbestimmungen verstößt, keine persönlichen Daten in der Datenbank vorhanden sind und sie nicht so viel wie zwei Sonnen wiegt – vor der Anwendung von Migrationen auf Live-Servern kann getestet werden, wie es auf der Maschine des Entwicklers funktioniert, um fast 100% potenzielle Probleme bei der Migration zu erkennen.
6. Kommunizieren Sie mit anderen Entwicklern im Team.
In einem gut organisierten Entwicklungsprozess weiß jeder im Team, wer an was arbeitet. In der Realität ist das oft nicht der Fall, daher ist es ratsam, das gesamte Team zusätzlich zu informieren, wenn Sie Änderungen an der Datenbankstruktur im Rahmen Ihrer Aufgabe vorbereiten. Wenn jemand parallel Änderungen vornimmt, sollten Sie sich vorsichtig organisieren. Es ist auch wichtig, nach Abschluss der Arbeit mit den Kollegen zu kommunizieren, nicht nur zu Beginn. Viele potenzielle Probleme mit Changesets lassen sich in der Phase der Codeüberprüfung lösen.
7. Denken Sie daran, was Sie tun!
Es scheint ein offensichtlicher Ratschlag zu sein, der in allen Situationen anwendbar ist. Viele Probleme könnten vermieden werden, wenn der Entwickler noch einmal analysieren würde, was er tut und welche Auswirkungen dies haben könnte. Die Arbeit mit Migrationen erfordert stets zusätzliche Aufmerksamkeit und Sorgfalt.
Fallen
Betrachten wir nun typische Fallen, in die man geraten kann, wenn man die obigen Ratschläge nicht befolgt, und was man eigentlich tun sollte.
Situation 1. Zwei Entwickler versuchen gleichzeitig, neue Changesets hinzuzufügen.

Wanja und Petja wollen ein Changeset der Version 4 erstellen, ohne voneinander zu wissen. Sie haben Änderungen an der Datenbankstruktur vorgenommen und einen Pull-Request mit unterschiedlichen Changeset-Dateien erstellt. Der folgende Aktionsmechanismus wird vorgeschlagen:
Wie man es löst.
- Irgendwie sollten die Kollegen sich einigen, in welcher Reihenfolge ihre Changesets erfolgen sollen, z. B. sollte Petjas zuerst angewendet werden.
- Jemand muss das zweite zu sich hinzufügen und den Änderungsdatensatz von Wanja mit Version 5 kennzeichnen. Dies kann über Cherry Pick oder einen sorgfältigen Merge erfolgen.
- Nach den Änderungen sollte unbedingt die Gültigkeit der durchgeführten Aktionen überprüft werden.
Tatsächlich ermöglichen die Mechanismen von Liquibase, zwei Änderungsdatensätze der Version 4 im Repository zu haben, daher kann alles so bleiben, wie es ist. Das bedeutet, dass es einfach zwei Änderungen der Version 4 mit unterschiedlichen Bezeichnungen geben wird. Bei diesem Ansatz wird es später sehr schwierig, in den Versionen der Datenbank den Überblick zu behalten.
Darüber hinaus birgt Liquibase, wie das Zuhause der Hobbits, viele Geheimnisse. Eines davon ist der Schlüssel validCheckSum, der seit Version 1.7 existiert und es ermöglicht, einen gültigen Hashwert für einen bestimmten Änderungsdatensatz anzugeben, unabhängig von dem, was in der Datenbank gespeichert ist. Dokumentation besagt Folgendes:
Fügen Sie einen Prüfziffer hinzu, der für diesen Änderungsdatensatz als gültig angesehen wird, unabhängig davon, was in der Datenbank gespeichert ist. Wird hauptsächlich verwendet, wenn Sie einen Änderungsdatensatz ändern müssen und keine Fehler auf Datenbanken auslösen möchten, auf denen er bereits ausgeführt wurde (nicht empfohlene Vorgehensweise)
Ja, ja, ein solches Verfahren wird nicht empfohlen. Aber manchmal beherrscht ein mächtiger heller Magier auch dunkle Techniken.
Situation 2. Migration, die von Daten abhängt.

Angenommen, Sie haben keine Möglichkeit, Sicherungskopien von Datenbanken von Live-Servern zu verwenden. Petja hat einen Änderungsdatensatz erstellt, ihn lokal überprüft und mit voller Überzeugung seiner Richtigkeit einen Pull-Request in die Entwicklung gestellt. Der Projektleiter hat zur Sicherheit nachgefragt, ob Petja ihn überprüft hat, und hat dann die Änderungen gemerged. Aber die Bereitstellung auf dem Entwicklungserver ist gescheitert.
Tatsächlich ist so etwas möglich, und niemand ist davor geschützt. Dies geschieht, wenn Modifikationen der Tabellenstruktur in irgendeiner Weise an bestimmten Daten in der Datenbank gebunden sind. Offensichtlich ist es so, dass wenn die Datenbank von Petja nur mit Testdaten gefüllt ist, sie möglicherweise nicht alle problematischen Fälle abdeckt. Zum Beispiel ergibt sich beim Löschen einer Tabelle, dass es Einträge in anderen Tabellen mit Foreign Key gibt, die mit den Einträgen in der gelöschten Tabelle verbunden sind. Oder beim Ändern des Datentyps einer Spalte stellt sich heraus, dass nicht 100% der Daten in den neuen Typ konvertiert werden können.
Wie man es löst.
- Speziell entwickelte Skripte zu erstellen, die einmalig zusammen mit der Migration angewendet werden und die Daten in den richtigen Zustand bringen. Dies ist ein allgemeiner Ansatz zur Lösung des Problems der Datenmigration in neue Strukturen nach der Anwendung von Migrationen, aber etwas Ähnliches kann auch in bestimmten Fällen vorher angewendet werden. Dieser Weg ist natürlich nicht immer verfügbar, da das Bearbeiten von Daten auf aktiven Servern gefährlich und sogar verheerend sein kann.
- Ein weiterer komplizierter Weg besteht darin, das bestehende Change-Set zu bearbeiten. Die Schwierigkeit besteht darin, dass alle Datenbanken, in denen es in der aktuellen Form bereits angewendet wurde, wiederhergestellt werden müssen. Es ist gut möglich, dass das gesamte Backend-Team gezwungen sein wird, die Datenbank lokal von Grund auf neu zu installieren.
- Und der universellste Weg ist, das Problem mit den Daten auf die Umgebung des Entwicklers zu übertragen, wobei die gleiche Situation wiederhergestellt wird, und ein neues Change-Set bis zu dem defekten hinzuzufügen, das das Problem umgeht.

Im Allgemeinen gilt: Je ähnlicher die Datenbasis der Produktionsumgebung ist, desto geringer ist die Wahrscheinlichkeit, dass Probleme mit Migrationen weitreichend sind. Und natürlich sollte man, bevor man das Change-Set in das Repository hochlädt, mehrmals überlegen, ob es nicht etwas kaputt macht.
Situation 3. Liquibase wird bereits nach dem Go-Live in der Produktion angewendet
Angenommen, der Teamleiter hat Petja gebeten, Liquibase in das Projekt einzubinden, jedoch ist das Projekt bereits in der Produktion und es existiert bereits eine bestehende Datenbankstruktur.
Dementsprechend besteht das Problem darin, dass auf allen neuen Servern oder Entwickler-Maschinen die Daten dieser Tabellen von Grund auf neu erstellt werden müssen, während die bereits bestehende Umgebung konsistent bleiben muss und bereit ist, neue Change-Sets zu akzeptieren.
Wie man es löst.
Es gibt auch hier mehrere Möglichkeiten:
- Der erste und offensichtlichste Weg ist, ein separates Skript zu haben, das manuell bei der Initialisierung einer neuen Umgebung angewendet werden muss.
- Der zweite, weniger offensichtliche Weg besteht darin, eine Liquibase-Migration zu haben, die sich in einem anderen Liquibase-Kontext befindet und angewendet wird. Mehr über den Liquibase-Kontext kann hier gelesen werden: . Insgesamt ist dies ein interessantes Konzept, das beispielsweise erfolgreich für Tests angewendet werden kann.
- Der dritte Weg besteht aus mehreren Schritten. Zunächst muss eine Migration für bereits vorhandene Tabellen erstellt werden. Anschließend muss sie in einer bestimmten Umgebung angewendet werden, wodurch ihr Hash-Wert erhalten wird. Der nächste Schritt besteht darin, auf unserem nicht leeren Server leere Liquibase-Tabellen zu initialisieren, und in die Tabelle mit der Historie der angewendeten Changesets kann man manuell einen Eintrag über das "angeblich angewendete" Changeset mit den bereits vorhandenen Änderungen in der Datenbank hinzufügen. Auf diese Weise beginnt die Historie auf dem bereits existierenden Server mit Version 2, während sich alle neuen Umgebungen identisch verhalten.

Situation 4. Migrationen werden riesig und schaffen es nicht, rechtzeitig ausgeführt zu werden.
Zu Beginn der Entwicklung des Dienstes wird Liquibase in der Regel als externe Abhängigkeit verwendet, und alle Migrationen werden beim Start der Anwendung verarbeitet. Mit der Zeit können jedoch die folgenden Szenarien auftreten:
- Migrationen werden riesig und benötigen lange für ihre Ausführung.
- Es entsteht die Notwendigkeit, Migrationen in verteilten Umgebungen durchzuführen, beispielsweise auf mehreren Instanzen von Datenbankservern gleichzeitig.
In einem solchen Fall kann die zu lange Ausführung von Migrationen zu einem Timeout beim Start der Anwendung führen. Zudem kann die Anwendung von Migrationen für jede Instanz der Anwendung separat dazu führen, dass verschiedene Server in unsynchronisiertem Zustand enden.
Wie man es löst.
In solchen Fällen ist Ihr Projekt bereits groß, möglicherweise sogar volljährig, und Liquibase beginnt als separates externes Tool zu fungieren. Liquibase wird als Bibliothek in eine JAR-Datei gepackt und kann sowohl als Abhängigkeit innerhalb des Projekts als auch autonom arbeiten.
Im autonomen Modus können die Migrationen von Ihrer CI/CD-Umgebung oder von den erfahrenen Systemadministratoren, die für die Bereitstellung zuständig sind, in die Hand genommen werden. Dazu wird die Befehlszeile von Liquibase benötigt. In diesem Modus besteht die Möglichkeit, die Anwendung bereits zu starten, nachdem alle erforderlichen Migrationen durchgeführt wurden.
Ausgabe
Tatsächlich kann es bei der Arbeit mit Datenbankmigrationen viel mehr Fallstricke geben, und viele von ihnen erfordern einen kreativen Ansatz. Es ist wichtig zu verstehen, dass die korrekte Verwendung des Werkzeugs es ermöglicht, die meisten dieser Fallstricke zu vermeiden. Konkrett habe ich in unterschiedlichen Formen mit allen genannten Problemen zu tun gehabt, und einige davon waren das Ergebnis meiner eigenen Fehler. In der Regel passiert dies, natürlich, durch Unachtsamkeit, aber manchmal auch aus einer kriminellen Unfähigkeit, das Werkzeug zu nutzen.
Quelle: habr.com


