Bereitstellung und Datenbanken ohne Ausfallzeiten

Bereitstellung und Datenbanken ohne Ausfallzeiten

In diesem Artikel wird ausführlich erläutert, wie Datenbankkompatibilitätsprobleme bei der Bereitstellung behoben werden. Wir sagen Ihnen, was mit Ihren Produktionsanwendungen passieren kann, wenn Sie versuchen, sie ohne vorherige Vorbereitung bereitzustellen. Anschließend durchlaufen wir die Phasen des Anwendungslebenszyklus, die erforderlich sind, um keine Ausfallzeiten zu verursachen (ca. Spur: weiter – keine Ausfallzeit). Das Ergebnis unserer Operationen wird darin bestehen, die rückwärtsinkompatible Datenbankänderung auf rückwärtskompatible Weise anzuwenden.

Wenn Sie die Codebeispiele aus dem Artikel verstehen möchten, finden Sie sie unter GitHub.

Einführung

Bereitstellung ohne Ausfallzeiten

Was für ein Mystisches Bereitstellung ohne Ausfallzeiten? Man kann sagen, dass dies der Fall ist, wenn Ihre Anwendung so bereitgestellt wird, dass Sie eine neue Version der Anwendung erfolgreich in die Produktion einführen können, ohne dass der Benutzer deren Nichtverfügbarkeit bemerkt. Aus Benutzer- und Unternehmenssicht ist dies das bestmögliche Bereitstellungsszenario, da es die Einführung neuer Funktionen und die Behebung von Fehlern ohne Unterbrechung ermöglicht.

Wie erreicht man das? Es gibt mehrere Möglichkeiten, hier ist eine davon:

  • Stellen Sie Version Nr. 1 Ihres Dienstes bereit
  • Führen Sie eine Datenbankmigration durch
  • Stellen Sie Version 2 Ihres Dienstes parallel zu Version 1 bereit
  • Sobald Sie sehen, dass Version Nr. 2 ordnungsgemäß funktioniert, entfernen Sie Version Nr. 1
  • fertig!

Einfach, nicht wahr? Leider ist es nicht so einfach, und darauf gehen wir später im Detail ein. Schauen wir uns nun einen weiteren recht häufigen Bereitstellungsprozess an: die Blau-Grün-Bereitstellung.

Hast du jemals gehört von blaugrüner Einsatz? Cloud Foundry macht dies extrem einfach. Schau einfach auf dieser Artikel, wo wir dies genauer beschreiben. Um es kurz zusammenzufassen: Lassen Sie uns Sie daran erinnern, wie Sie eine Blau-Grün-Bereitstellung durchführen:

  • Stellen Sie sicher, dass zwei Kopien Ihres Produktionscodes („blau“ und „grün“) funktionieren.
  • Leiten Sie den gesamten Verkehr in die blaue Umgebung, d. h. damit Produktions-URLs dorthin verweisen;
  • Bereitstellen und Testen aller Anwendungsänderungen in einer grünen Umgebung;
  • Wechseln Sie die URLs von der blauen zur grünen Umgebung

Blue Green Deployment ist ein Ansatz, der es Ihnen ermöglicht, neue Funktionen einfach einzuführen, ohne sich Gedanken über Produktionsunterbrechungen machen zu müssen. Dies liegt daran, dass Sie, selbst wenn etwas passiert, problemlos zur vorherigen Umgebung zurückkehren können, indem Sie einfach einen Schalter betätigen.

Nachdem Sie alle oben genannten Punkte gelesen haben, stellen Sie sich möglicherweise die Frage: Was hat Null Ausfallzeit mit der Bereitstellung von Blue Green zu tun?

Nun, sie haben ziemlich viel gemeinsam, da die Pflege zweier Kopien derselben Umgebung den doppelten Aufwand erfordert. Aus diesem Grund behaupten einige Teams Martin Fowler, folgen Sie einer Variation dieses Ansatzes:

Eine andere Möglichkeit besteht darin, dieselbe Datenbank zu verwenden und blau-grüne Switches für die Web- und Domänenebene zu erstellen. Bei diesem Ansatz kann die Datenbank häufig ein Problem darstellen, insbesondere wenn Sie ihr Schema ändern müssen, um eine neue Version der Software zu unterstützen.

Und hier kommen wir zum Hauptproblem dieses Artikels. Datenbank. Schauen wir uns diesen Satz noch einmal an.

Führen Sie eine Datenbankmigration durch.

Jetzt müssen Sie sich die Frage stellen: Was passiert, wenn die Datenbankänderung nicht abwärtskompatibel ist? Funktioniert meine erste Version der App nicht? Tatsächlich wird genau das passieren ...

Trotz der enormen Vorteile einer Zero-Downtime-/Blue-Green-Bereitstellung tendieren Unternehmen dazu, den folgenden sichereren Prozess für die Bereitstellung ihrer Anwendungen zu befolgen:

  • Bereiten Sie ein Paket mit einer neuen Version der Anwendung vor
  • Beenden Sie eine laufende Anwendung
  • Führen Sie Skripts aus, um die Datenbank zu migrieren
  • Bereitstellen und Starten einer neuen Version der Anwendung

In diesem Artikel erfahren Sie im Detail, wie Sie mit Ihrer Datenbank und Ihrem Code arbeiten können, um von der Bereitstellung ohne Ausfallzeiten zu profitieren.

Datenbankprobleme

Wenn Sie über eine zustandslose Anwendung verfügen, die keine Daten in der Datenbank speichert, können Sie sofort eine Bereitstellung ohne Ausfallzeiten erzielen. Leider müssen die meisten Programme Daten irgendwo speichern. Deshalb sollten Sie es sich zweimal überlegen, bevor Sie Änderungen an der Schaltung vornehmen. Bevor wir näher darauf eingehen, wie das Schema so geändert wird, dass eine Bereitstellung ohne Ausfallzeiten möglich ist, konzentrieren wir uns zunächst auf das Versionsschema.

Versionierungsschema

In diesem Artikel werden wir verwenden Flyway als Versionskontrolltool (ca. Übersetzung: Wir sprechen über Datenbankmigrationen). Natürlich werden wir auch eine Spring Boot-Anwendung schreiben, die über integrierte Flyway-Unterstützung verfügt und die Schemamigration beim Einrichten des Anwendungskontexts durchführt. Wenn Sie Flyway verwenden, können Sie Migrationsskripte in Ihrem Projektordner speichern (standardmäßig in classpath:db/migration). Hier sehen Sie ein Beispiel für solche Migrationsdateien

└── db
 └── migration
     ├── V1__init.sql
     ├── V2__Add_surname.sql
     ├── V3__Final_migration.sql
     └── V4__Remove_lastname.sql

In diesem Beispiel sehen wir 4 Migrationsskripte, die, wenn sie nicht zuvor ausgeführt wurden, beim Start der Anwendung nacheinander ausgeführt werden. Schauen wir uns eine der Dateien an (V1__init.sql) als Beispiel.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Alles ist vollkommen selbsterklärend: Mit SQL können Sie definieren, wie Ihre Datenbank geändert werden soll. Weitere Informationen zu Spring Boot und Flyway finden Sie hier Spring Boot-Dokumente.

Durch die Verwendung eines Quellcodeverwaltungstools mit Spring Boot erhalten Sie zwei große Vorteile:

  • Sie trennen Datenbankänderungen von Codeänderungen
  • Die Datenbankmigration erfolgt zusammen mit dem Rollout Ihrer Anwendung, d. h. Ihr Bereitstellungsprozess wird vereinfacht

Beheben von Datenbankproblemen

Im nächsten Abschnitt des Artikels konzentrieren wir uns auf die Betrachtung zweier Ansätze für Datenbankänderungen.

  • Rückwärtsinkompatibilität
  • Rückwärtskompatibilität

Die erste wird als Warnung betrachtet, dass Sie keine Bereitstellung ohne Ausfallzeiten ohne vorherige Vorbereitung durchführen sollten. Die zweite bietet eine Lösung, wie Sie eine Bereitstellung ohne Ausfallzeiten durchführen und gleichzeitig die Abwärtskompatibilität aufrechterhalten können.

Unser Projekt, an dem wir arbeiten werden, wird eine einfache Spring Boot Flyway-Anwendung sein Person с first_name и last_name in der Datenbank (ca. Übersetzung: Person ist eine Tabelle und first_name и last_name - das sind die Felder darin). Wir wollen umbenennen last_name в surname.

Annahmen

Bevor wir auf die Details eingehen, müssen wir einige Annahmen über unsere Anwendungen treffen. Das Hauptergebnis, das wir erreichen möchten, wird ein ziemlich einfacher Prozess sein.

Die Notiz. Business-PRO-TIPP. Durch die Vereinfachung von Prozessen können Sie viel Geld für den Support sparen (je mehr Leute in Ihrem Unternehmen arbeiten, desto mehr Geld können Sie sparen)!

Es ist kein Rollback der Datenbank erforderlich

Dies vereinfacht den Bereitstellungsprozess (einige Datenbank-Rollbacks sind nahezu unmöglich, z. B. ein Lösch-Rollback). Wir ziehen es vor, nur Anwendungen zurückzusetzen. Auf diese Weise sieht Ihre Bereitstellungspipeline auch dann gleich aus, wenn Sie über unterschiedliche Datenbanken verfügen (z. B. SQL und NoSQL).

Es muss IMMER möglich sein, die Anwendung um eine Version zurückzusetzen (nicht mehr).

Ein Rollback sollte nur bei Bedarf durchgeführt werden. Wenn es in der aktuellen Version einen Fehler gibt, der nicht einfach zu beheben ist, sollten wir in der Lage sein, zur neuesten funktionierenden Version zurückzukehren. Wir gehen davon aus, dass es sich bei dieser neuesten Arbeitsversion um die vorherige handelt. Die Aufrechterhaltung der Code- und Datenbankkompatibilität für mehr als einen Rollout wäre äußerst schwierig und teuer.

Beachten. Zur besseren Lesbarkeit ändern wir in diesem Artikel die Hauptversion der Anwendung.

Schritt 1: Ausgangszustand

Anwendungsversion: 1.0.0
DB-Version: v1

Kommentar

Dies ist der Ausgangszustand der Anwendung.

Datenbankänderungen

DB enthält last_name.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Codeänderungen

Die Anwendung speichert Personendaten in last_name:

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastname) {
        this.lastName = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
                + "]";
    }
}

Abwärtsinkompatible Spaltenumbenennung

Schauen wir uns ein Beispiel an, wie man einen Spaltennamen ändert:

Achtung. Das folgende Beispiel wird absichtlich Dinge kaputt machen. Wir zeigen dies, um das Problem der Datenbankkompatibilität zu demonstrieren.

Anwendungsversion: 2.0.0.BAD

DB-Version: v2bad

Kommentar

Die aktuellen Änderungen erlauben es uns NICHT, zwei Instanzen (alt und neu) gleichzeitig auszuführen. Daher wird es schwierig sein, eine Bereitstellung ohne Ausfallzeiten zu erreichen (unter Berücksichtigung von Annahmen ist dies tatsächlich unmöglich).

A/B-Tests

Die aktuelle Situation ist, dass wir eine Anwendungsversion haben 1.0.0, in der Produktion und in der Datenbank bereitgestellt v1. Wir müssen eine zweite Instanz der Anwendung, Version, bereitstellen 2.0.0.BAD, und aktualisieren Sie die Datenbank auf v2bad.

Schritte:

  1. Eine neue Instanz der Versionsanwendung wird bereitgestellt 2.0.0.BADwodurch die Datenbank aktualisiert wird v2bad
  2. in der Datenbank v2bad Spalte last_name existiert nicht mehr – es wurde geändert in surname
  3. Das Datenbank- und Anwendungsupdate war erfolgreich und einige Instanzen werden ausgeführt 1.0.0, andere - in 2.0.0.BAD. Alles ist mit der Datenbank verbunden v2bad
  4. alle Instanzen der Version 1.0.0 wird anfangen, Fehler auszulösen, weil sie versuchen, Daten in die Spalte einzufügen last_nameder nicht mehr existiert
  5. alle Instanzen der Version 2.0.0.BAD wird ohne Probleme funktionieren

Wie Sie sehen, sind A/B-Tests nicht möglich, wenn wir rückwärtsinkompatible Änderungen an der Datenbank und der Anwendung vornehmen.

Anwendungs-Rollback

Nehmen wir an, dass nach dem Versuch einer A/B-Bereitstellung (ca. per.: Der Autor meinte hier wahrscheinlich A/B-Tests) haben wir entschieden, dass wir die Anwendung auf die Version zurücksetzen müssen 1.0.0. Nehmen wir an, wir möchten die Datenbank nicht zurücksetzen.

Schritte:

  1. Wir stoppen die Versionsanwendungsinstanz 2.0.0.BAD
  2. Die Datenbank ist noch v2bad
  3. seit der Version 1.0.0 versteht nicht, was es ist surname, wir werden Fehler sehen
  4. Die Hölle ist los, wir können nicht mehr zurück

Wie Sie sehen, können wir nicht zur vorherigen Version zurückkehren, wenn wir rückwärtsinkompatible Änderungen an der Datenbank und der Anwendung vornehmen.

Skriptausführungsprotokolle

Backward incompatible scenario:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

curl: (22) The requested URL returned error: 500 Internal Server Error

Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

Datenbankänderungen

Migrationsskript, das umbenennt last_name в surname

Quell-Flyway-Skript:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Skript, das umbenennt last_name.

-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Codeänderungen

Wir haben den Feldnamen geändert lastName auf surname.

Abwärtskompatibles Umbenennen einer Spalte

Dies ist die häufigste Situation, der wir begegnen können. Wir müssen rückwärtsinkompatible Änderungen vornehmen. Wir haben bereits bewiesen, dass wir für eine Bereitstellung ohne Ausfallzeiten nicht einfach eine Datenbankmigration ohne zusätzliche Schritte durchführen sollten. In diesem Abschnitt des Artikels führen wir drei Bereitstellungen der Anwendung zusammen mit Datenbankmigrationen durch, um das gewünschte Ergebnis zu erzielen und gleichzeitig die Abwärtskompatibilität aufrechtzuerhalten.

Die Notiz. Denken Sie daran, dass wir eine Versionsdatenbank haben v1. Es enthält Spalten first_name и last_name. Wir müssen uns ändern last_name auf surname. Wir haben auch eine App-Version 1.0.0, welches noch nicht genutzt wird surname.

Schritt 2: Nachname hinzufügen

Anwendungsversion: 2.0.0
DB-Version: v2

Kommentar

Durch das Hinzufügen einer neuen Spalte und das Kopieren ihres Inhalts erstellen wir abwärtskompatible Datenbankänderungen. Wenn wir gleichzeitig das JAR zurücksetzen oder ein altes JAR laufen lassen, wird es während der Ausführung nicht kaputt gehen.

Wir führen eine neue Version ein

Schritte:

  1. Führen Sie eine Datenbankmigration durch, um eine neue Spalte zu erstellen surname. Jetzt Ihre DB-Version v2
  2. Daten kopieren von last_name в surname. BeachtenWenn Sie viele dieser Daten haben, sollten Sie eine Batch-Migration in Betracht ziehen!
  3. Schreiben Sie den Code dort, wo sie verwendet werden BEIDE и neueUnd старый Spalte. Jetzt Ihre App-Version 2.0.0
  4. Lesen Sie den Wert aus der Spalte surname, wenn es das nicht ist null, oder von last_name, Aale surname nicht angegeben. Sie können löschen getLastName() aus dem Code, da er ausgegeben wird null beim Zurücksetzen Ihrer Anwendung von 3.0.0 auf 2.0.0.

Wenn Sie Spring Boot Flyway verwenden, werden diese beiden Schritte beim Versionsstart ausgeführt 2.0.0 Anwendungen. Wenn Sie das Datenbankversionierungstool manuell ausführen, müssen Sie dazu zwei verschiedene Dinge tun (zuerst die Datenbankversion manuell aktualisieren und dann die neue Anwendung bereitstellen).

Es ist wichtig. Denken Sie daran, dass die neu erstellte Spalte SOLLTE NICHT sein NICHT NULL. Wenn Sie ein Rollback durchführen, weiß die alte Anwendung nichts von der neuen Spalte und wird sie währenddessen nicht installieren Insert. Aber wenn Sie diese Einschränkung hinzufügen, wird Ihre Datenbank sein v2, dazu muss der Wert der neuen Spalte festgelegt werden. Was zu Verstößen gegen Beschränkungen führen wird.

Es ist wichtig. Sie sollten die Methode entfernen getLastName(), denn in der Version 3.0.0 Im Code gibt es kein Konzept einer Spalte last_name. Das bedeutet, dass dort null gesetzt wird. Sie können die Methode verlassen und Prüfungen hinzufügen null, aber eine viel bessere Lösung wäre, dies in der Logik sicherzustellen getSurname() Sie haben den richtigen Wert ungleich Null ausgewählt.

A/B-Tests

Die aktuelle Situation ist, dass wir eine Anwendungsversion haben 1.0.0, in der Produktion bereitgestellt, und die Datenbank in v1. Wir müssen eine zweite Instanz der Versionsanwendung bereitstellen 2.0.0wodurch die Datenbank aktualisiert wird v2.

Schritte:

  1. Eine neue Instanz der Versionsanwendung wird bereitgestellt 2.0.0wodurch die Datenbank aktualisiert wird v2
  2. In der Zwischenzeit wurden einige Anfragen von Versionsinstanzen bearbeitet 1.0.0
  3. Das Update war erfolgreich und Sie verfügen über mehrere laufende Instanzen der Versionsanwendung 1.0.0 und andere Versionen 2.0.0. Jeder kommuniziert mit der Datenbank in v2
  4. Version 1.0.0 verwendet nicht die Nachnamenspalte in der Datenbank, sondern die Version 2.0.0 Verwendet. Sie stören sich nicht gegenseitig und es sollte keine Fehler geben.
  5. Version 2.0.0 speichert Daten sowohl in der alten als auch in der neuen Spalte und gewährleistet so die Abwärtskompatibilität

Es ist wichtig. Wenn Sie Abfragen haben, die Elemente basierend auf Werten aus der alten/neuen Spalte zählen, sollten Sie bedenken, dass Sie jetzt doppelte Werte haben (höchstwahrscheinlich werden diese noch migriert). Wenn Sie beispielsweise die Anzahl der Benutzer zählen möchten, deren Nachname (wie auch immer die Spalte heißt) mit dem Buchstaben begann A, dann bis die Datenmigration abgeschlossen ist (oldnew Spalte) können inkonsistente Daten vorliegen, wenn Sie eine neue Spalte abfragen.

Anwendungs-Rollback

Jetzt haben wir eine App-Version 2.0.0 und Datenbank in v2.

Schritte:

  1. Setzen Sie Ihre Anwendung auf die Version zurück 1.0.0.
  2. Version 1.0.0 verwendet keine Spalte in der Datenbank surname, daher sollte das Rollback erfolgreich sein

DB-Änderungen

Die Datenbank enthält eine Spalte mit dem Namen last_name.

Flyway-Quellskript:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Skript hinzufügen surname.

Achtung. Denken Sie daran, dass Sie der Spalte, die Sie hinzufügen, KEINE NOT NULL-Einschränkungen hinzufügen können. Wenn Sie das JAR zurücksetzen, hat die alte Version keine Ahnung von der hinzugefügten Spalte und setzt sie automatisch auf NULL. Bei einer solchen Einschränkung geht die alte Anwendung einfach kaputt.

-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Codeänderungen

Wir speichern Daten als last_nameund in surname. Gleichzeitig lesen wir aus last_name, da diese Spalte die relevanteste ist. Während des Bereitstellungsprozesses wurden möglicherweise einige Anforderungen von einer Anwendungsinstanz verarbeitet, die noch nicht aktualisiert wurde.

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * Reading from the new column if it's set. If not the from the old one.
     *
     * When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
     * the surname column is not up to date (during the migration process lastName could have been updated).
     * In this case one can run yet another migration script after all applications have been deployed in the
     * new version to ensure that the surname field is updated.
     *
     * However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
     * have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
     * would be reading from lastName, then we would have very old data (since not a single datum was inserted
     * to lastName in version 3.0.0).
     */
    public String getSurname() {
        return this.surname != null ? this.surname : this.lastName;
    }

    /**
     * Storing both FIRST_NAME and SURNAME entries
     */
    public void setSurname(String surname) {
        this.lastName = surname;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
                + "]";
    }
}

Schritt 3: Nachname aus dem Code entfernen

Anwendungsversion: 3.0.0

DB-Version:v3

Kommentar

Notiz per.: Anscheinend hat der Autor im Originalartikel fälschlicherweise den Text dieses Blocks aus Schritt 2 kopiert. In diesem Schritt sollten Änderungen im Anwendungscode vorgenommen werden, die darauf abzielen, die Funktionalität zu entfernen, die die Spalte verwendet last_name.

Durch das Hinzufügen einer neuen Spalte und das Kopieren ihres Inhalts haben wir abwärtskompatible Datenbankänderungen erstellt. Auch wenn wir das JAR zurücksetzen oder ein altes JAR laufen lassen, wird es während der Ausführung nicht kaputt gehen.

Anwendungs-Rollback

Derzeit haben wir eine App-Version 3.0.0 und Datenbank v3... Ausführung 3.0.0 speichert keine Daten last_name. Das bedeutet, dass in surname Es werden die aktuellsten Informationen gespeichert.

Schritte:

  1. Setzen Sie Ihre Anwendung auf die Version zurück 2.0.0.
  2. Version 2.0.0 verwendet und last_name и surname.
  3. Version 2.0.0 wird nehmen surname, wenn es nicht Null ist, andernfalls -last_name

Datenbankänderungen

Es gibt keine strukturellen Änderungen in der Datenbank. Um die endgültige Migration der alten Daten durchzuführen, wird das folgende Skript ausgeführt:

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Codeänderungen

Notiz per.: Auch die Beschreibung dieses Blocks wurde vom Autor fälschlicherweise aus Schritt 2 kopiert. Gemäß der Logik des Artikels sollten Änderungen im Code in diesem Schritt darauf abzielen, Elemente daraus zu entfernen, die mit der Spalte arbeiten last_name.

Wir speichern Daten als last_nameund in surname. Zusätzlich lesen wir aus der Kolumne last_name, da es am relevantesten ist. Während des Bereitstellungsprozesses werden einige Anfragen möglicherweise von einer Instanz verarbeitet, die noch nicht aktualisiert wurde.

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return this.surname;
    }

    public void setSurname(String lastname) {
        this.surname = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", surname=" + this.surname
                + "]";
    }
}

Schritt 4: Nachname aus der Datenbank entfernen

Anwendungsversion: 4.0.0

DB-Version: v4

Kommentar

Aufgrund der Tatsache, dass der Versionscode 3.0.0 Habe die Spalte nicht verwendet last_name, wird während der Ausführung nichts Schlimmes passieren, wenn wir auf zurücksetzen 3.0.0 nach dem Entfernen einer Spalte aus der Datenbank.

Skriptausführungsprotokolle

We will do it in the following way:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Starting app in version 2.0.0

Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Killing app 1.0.0

Starting app in version 3.0.0

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Killing app 2.0.0

Starting app in version 4.0.0

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

DB-Änderungen

Über v3 Wir entfernen einfach die Spalte last_name und fehlende Einschränkungen hinzufügen.

-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;

-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Codeänderungen

Es gibt keine Änderungen am Code.

Abschluss

Wir haben eine abwärtsinkompatible Änderung des Spaltennamens erfolgreich angewendet, indem wir mehrere abwärtskompatible Bereitstellungen durchgeführt haben. Nachfolgend finden Sie eine Zusammenfassung der durchgeführten Aktionen:

  1. Bereitstellung der Anwendungsversion 1.0.0 с v1 Datenbankschema (Spaltenname = last_name)
  2. Bereitstellung der Anwendungsversion 2.0.0, in dem Daten gespeichert werden last_name и surname. Die Anwendung liest aus last_name. Die Datenbank ist in Version v2Enthält Spalten wie last_nameUnd surname. surname ist eine Kopie von last_name. (HINWEIS: Diese Spalte darf keine Nicht-Null-Einschränkung haben)
  3. Bereitstellung der Anwendungsversion 3.0.0, in dem nur Daten gespeichert werden surname und liest aus dem Nachnamen. Was die Datenbank betrifft, findet gerade die letzte Migration statt last_name в surname. Auch eine Einschränkung NICHT NULL entfernt von last_name. Die Datenbank ist jetzt in Version v3
  4. Bereitstellung der Anwendungsversion 4.0.0 - Es werden keine Änderungen am Code vorgenommen. Datenbankbereitstellung v4, was entfernt last_name. Hier können Sie fehlende Einschränkungen zur Datenbank hinzufügen.

Wenn Sie diesem Ansatz folgen, können Sie jederzeit ein Rollback einer Version durchführen, ohne die Datenbank-/Anwendungskompatibilität zu beeinträchtigen.

Code

Der gesamte in diesem Artikel verwendete Code ist unter verfügbar Github. Nachfolgend finden Sie eine zusätzliche Beschreibung.

Projekte

Nach dem Klonen des Repositorys sehen Sie die folgende Ordnerstruktur.

├── boot-flyway-v1              - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2              - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad          - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3              - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4              - 4.0.0 version of the app with v4 of the schema (app can be rolled back)

Scripts

Sie können die in den folgenden Skripten beschriebenen Skripte ausführen, die abwärtskompatible und inkompatible Änderungen an der Datenbank demonstrieren.

Sehen Dies ist bei abwärtskompatiblen Änderungen der Fall, laufen:

./scripts/scenario_backward_compatible.sh

Und zu sehen Fall mit abwärtsinkompatiblen Änderungen, laufen:

./scripts/scenario_backward_incompatible.sh

Spring Boot Sample Flyway

Alle Beispiele stammen aus Spring Boot Sample Flyway.

Sie können einen Blick darauf werfen http://localhost:8080/flyway, es gibt eine Liste von Skripten.

Dieses Beispiel enthält auch die H2-Konsole (at http://localhost:8080/h2-console), damit Sie den Datenbankstatus anzeigen können (die Standard-JDBC-URL lautet jdbc:h2:mem:testdb).

zusätzlich

Lesen Sie auch andere Artikel in unserem Blog:

Source: habr.com

Kommentar hinzufügen