Implementatie en databases zonder downtime

Implementatie en databases zonder downtime

In dit artikel wordt gedetailleerd uitgelegd hoe u databasecompatibiliteitsproblemen tijdens de implementatie kunt oplossen. Wij vertellen u wat er met uw productietoepassingen kan gebeuren als u deze zonder voorafgaande voorbereiding probeert in te zetten. Vervolgens doorlopen we de levenscyclusfasen van de applicatie die nodig zijn om geen downtime te hebben (ca. baan: verder - geen stilstand). Het resultaat van onze operaties zal zijn dat we de achterwaarts incompatibele databasewijziging op een achterwaarts compatibele manier zullen toepassen.

Als je de codevoorbeelden uit het artikel wilt begrijpen, kun je ze vinden op GitHub.

Introductie

Implementatie zonder downtime

Wat een mystiek implementatie zonder downtime? Je zou kunnen zeggen dat dit is wanneer je applicatie zodanig wordt ingezet dat je met succes een nieuwe versie van de applicatie in productie kunt nemen, zonder dat de gebruiker merkt dat deze niet beschikbaar is. Vanuit gebruikers- en bedrijfsperspectief is dit het best mogelijke implementatiescenario, omdat hierdoor nieuwe functies kunnen worden geïntroduceerd en bugs kunnen worden opgelost zonder onderbrekingen.

Hoe dit te bereiken? Er zijn verschillende manieren, hier is er één:

  • versie nr. 1 van uw service implementeren
  • een databasemigratie uitvoeren
  • Implementeer versie #2 van uw service parallel met versie #1
  • Zodra u ziet dat versie nr. 2 naar behoren werkt, verwijdert u versie nr. 1
  • klaar!

Makkelijk, nietwaar? Helaas is het niet zo eenvoudig, en we zullen daar later in detail naar kijken. Laten we nu eens een ander vrij gebruikelijk implementatieproces bekijken: blauwgroene implementatie.

Heb je ooit gehoord van blauwgroene inzet? Cloud Foundry maakt dit uiterst eenvoudig. Kijk maar eens dit artikel, waar we dit in meer detail beschrijven. Om het kort samen te vatten, laten we u eraan herinneren hoe u een blauwgroene implementatie uitvoert:

  • zorg ervoor dat twee exemplaren van uw productiecode (“blauw” en “groen”) werken;
  • leid al het verkeer naar de blauwe omgeving, d.w.z. zodat productie-URL's daarheen wijzen;
  • implementeren en testen van alle applicatiewijzigingen in een groene omgeving;
  • verander URL's van blauwe naar groene omgeving

Blauwgroene implementatie is een aanpak waarmee u eenvoudig nieuwe functies kunt introduceren zonder dat u zich zorgen hoeft te maken over productieonderbrekingen. Dit komt door het feit dat zelfs als er iets gebeurt, je eenvoudig terug kunt gaan naar de vorige omgeving door simpelweg ‘op een schakelaar te drukken’.

Nadat u al het bovenstaande heeft gelezen, kunt u zich de vraag stellen: wat heeft nul downtime te maken met Blue Green-implementatie?

Welnu, ze hebben nogal wat gemeen, aangezien het onderhouden van twee kopieën van dezelfde omgeving dubbele inspanning vergt om ze te onderhouden. Dit is de reden waarom sommige teams beweren Martin FowlerVolg een variatie op deze aanpak:

Een andere optie is om dezelfde database te gebruiken, waarbij blauwgroene schakelaars voor de web- en domeinlagen worden gemaakt. Bij deze aanpak kan de database vaak een probleem vormen, vooral wanneer u het schema ervan moet wijzigen om een ​​nieuwe versie van de software te ondersteunen.

En hier komen we bij het belangrijkste probleem in dit artikel. databank. Laten we nog eens naar deze zin kijken.

een databasemigratie uitvoeren.

Nu moet u uzelf de vraag stellen: wat als de databasewijziging niet achterwaarts compatibel is? Zal mijn eerste versie van de app niet kapot gaan? In feite is dit precies wat er zal gebeuren...

Dus ondanks de enorme voordelen van zero downtime/blauwgroene implementatie, hebben bedrijven de neiging om het volgende, veiligere proces te volgen voor de implementatie van hun applicaties:

  • een pakket voorbereiden met een nieuwe versie van de applicatie
  • sluit een actieve applicatie af
  • voer scripts uit om de database te migreren
  • een nieuwe versie van de applicatie implementeren en starten

In dit artikel leggen we uit hoe u met uw database en code kunt werken om te profiteren van een implementatie zonder downtime.

Database problemen

Als u een staatloze applicatie heeft die geen gegevens in de database opslaat, kunt u meteen een implementatie zonder downtime realiseren. Helaas moet de meeste software ergens gegevens opslaan. Daarom moet u twee keer nadenken voordat u wijzigingen aan het circuit aanbrengt. Voordat we ingaan op de details van hoe u het schema kunt wijzigen zodat implementatie zonder downtime mogelijk is, concentreren we ons eerst op het versiebeheerschema.

Versiebeheerschema

In dit artikel zullen we gebruiken vliegroute als versiebeheertool (ca. Vertaling: we hebben het over databasemigraties). Uiteraard schrijven we ook een Spring Boot-applicatie die ingebouwde Flyway-ondersteuning heeft en voeren we schemamigratie uit terwijl we de applicatiecontext opzetten. Wanneer u Flyway gebruikt, kunt u migratiescripts opslaan in uw projectenmap (standaard in classpath:db/migration). Hier ziet u een voorbeeld van dergelijke migratiebestanden

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

In dit voorbeeld zien we 4 migratiescripts die, als ze niet eerder zijn uitgevoerd, na elkaar worden uitgevoerd wanneer de applicatie start. Laten we eens naar een van de bestanden kijken (V1__init.sql) als voorbeeld.

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 spreekt voor zich: u kunt SQL gebruiken om te definiëren hoe uw database moet worden aangepast. Voor meer informatie over Spring Boot en Flyway, kijk op Spring Boot-documentatie.

Door een broncontroletool te gebruiken met Spring Boot krijgt u 2 grote voordelen:

  • u scheidt databasewijzigingen van codewijzigingen
  • Databasemigratie vindt plaats samen met de uitrol van uw applicatie, d.w.z. uw implementatieproces wordt vereenvoudigd

Databaseproblemen oplossen

In het volgende gedeelte van het artikel zullen we ons concentreren op twee benaderingen van databasewijzigingen.

  • achterwaartse incompatibiliteit
  • achterwaartse compatibiliteit

De eerste wordt beschouwd als een waarschuwing dat u geen zero-downtime-implementatie mag uitvoeren zonder voorafgaande voorbereiding... De tweede biedt een oplossing voor hoe u een implementatie zonder downtime kunt uitvoeren en tegelijkertijd achterwaartse compatibiliteit kunt behouden.

Ons project waar we aan zullen werken zal een eenvoudige Spring Boot Flyway-applicatie zijn die dat wel heeft Person с first_name и last_name in de databank (ca. vertaling: Person is een tafel en first_name и last_name - dit zijn de velden erin). Wij willen hernoemen last_name в surname.

Aannames

Voordat we op de details ingaan, moeten we een aantal aannames doen over onze toepassingen. Het belangrijkste resultaat dat we willen bereiken zal een vrij eenvoudig proces zijn.

De notitie. Zakelijk PRO-TIP. Het vereenvoudigen van processen kan u veel geld besparen op ondersteuning (hoe meer mensen er voor uw bedrijf werken, hoe meer geld u kunt besparen)!

Het is niet nodig om de database terug te draaien

Dit vereenvoudigt het implementatieproces (sommige database-rollbacks zijn bijna onmogelijk, zoals het terugdraaien van verwijderingen). Wij geven er de voorkeur aan om alleen applicaties terug te draaien. Op deze manier ziet uw implementatiepijplijn er hetzelfde uit, zelfs als u verschillende databases heeft (bijvoorbeeld SQL en NoSQL).

Het moet ALTIJD mogelijk zijn om de applicatie één versie terug te draaien (niet meer)

Terugdraaien mag alleen worden gedaan als dat nodig is. Als er een bug in de huidige versie zit die niet eenvoudig kan worden opgelost, moeten we kunnen terugkeren naar de nieuwste werkende versie. We gaan ervan uit dat deze laatste werkende versie de vorige is. Het onderhouden van code- en databasecompatibiliteit voor meer dan één implementatie zou uiterst moeilijk en duur zijn.

De notitie. Voor een betere leesbaarheid zullen we in dit artikel de hoofdversie van de applicatie wijzigen.

Stap 1: Initiële staat

Applicatieversie: 1.0.0
DB-versie: v1

Commentaar

Dit is de initiële status van de applicatie.

Databasewijzigingen

DB bevat 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');

Codewijzigingen

De applicatie slaat Persoonsgegevens op 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
                + "]";
    }
}

Achterwaarts incompatibele kolomhernoeming

Laten we een voorbeeld bekijken van hoe u een kolomnaam kunt wijzigen:

Aandacht. In het volgende voorbeeld zullen dingen opzettelijk kapot gaan. We laten dit zien om het probleem van databasecompatibiliteit aan te tonen.

Applicatieversie: 2.0.0.BAD

DB-versie: v2bad

Commentaar

De huidige wijzigingen staan ​​ons NIET toe om twee instances (oud en nieuw) tegelijkertijd uit te voeren. Het zal dus moeilijk zijn om een ​​nul-downtime-implementatie te realiseren (als er rekening wordt gehouden met aannames, is dit feitelijk onmogelijk).

A/B-testen

De huidige situatie is dat we een applicatieversie hebben 1.0.0, ingezet in productie en database v1. We moeten een tweede exemplaar van de applicatie, versie 2.0.0.BADen update de database naar v2bad.

stappen:

  1. er wordt een nieuw exemplaar van de versietoepassing geïmplementeerd 2.0.0.BADwaarmee de database wordt bijgewerkt v2bad
  2. in de databank v2bad kolom last_name bestaat niet meer - het is veranderd in surname
  3. De database- en applicatie-update is gelukt en sommige exemplaren zijn actief 1.0.0, anderen - binnen 2.0.0.BAD. Alles is verbonden met de database v2bad
  4. alle exemplaren van de versie 1.0.0 zullen fouten gaan genereren omdat ze zullen proberen gegevens in de kolom in te voegen last_namedie niet meer bestaat
  5. alle exemplaren van de versie 2.0.0.BAD zal zonder problemen werken

Zoals u kunt zien, is A/B-testen onmogelijk als we achterwaarts incompatibele wijzigingen aanbrengen in de database en de applicatie.

Toepassing terugdraaien

Laten we aannemen dat na een poging tot A/B-implementatie (ca. per.: de auteur bedoelde hier waarschijnlijk A/B-testen) hebben we besloten dat we de applicatie moeten terugdraaien naar de versie 1.0.0. Stel dat we de database niet willen terugdraaien.

stappen:

  1. we stoppen de versie-applicatie-instantie 2.0.0.BAD
  2. de database staat nog steeds v2bad
  3. sinds de versie 1.0.0 begrijpt niet wat het is surname, zullen we fouten zien
  4. De hel is losgebroken, we kunnen niet meer terug

Zoals u kunt zien, kunnen we niet teruggaan naar de vorige versie als we achterwaarts incompatibele wijzigingen aanbrengen in de database en de applicatie.

Logboeken voor scriptuitvoering

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"}

Databasewijzigingen

Migratiescript dat hernoemt last_name в surname

Bron Flyway-script:

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');

Script dat hernoemt last_name.

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

Codewijzigingen

We hebben de veldnaam gewijzigd lastName op surname.

De naam van een kolom op een achterwaarts compatibele manier wijzigen

Dit is de meest voorkomende situatie die we kunnen tegenkomen. We moeten achterwaarts onverenigbare veranderingen doorvoeren. We hebben al bewezen dat we voor een zero-downtime-implementatie niet simpelweg databasemigratie moeten toepassen zonder extra stappen. In dit gedeelte van het artikel zullen we drie implementaties van de applicatie uitvoeren, samen met databasemigraties, om het gewenste resultaat te bereiken met behoud van achterwaartse compatibiliteit.

De notitie. Bedenk dat we een versiedatabase hebben v1. Het bevat kolommen first_name и last_name. Wij moeten veranderen last_name op surname. We hebben ook een app-versie 1.0.0, die nog niet wordt gebruikt surname.

Stap 2: Voeg achternaam toe

Applicatieversie: 2.0.0
DB-versie: v2

Commentaar

Door een nieuwe kolom toe te voegen en de inhoud ervan te kopiëren, creëren we achterwaarts compatibele databasewijzigingen. Tegelijkertijd, als we de JAR terugdraaien of een oude JAR laten draaien, zal deze tijdens de uitvoering niet kapot gaan.

We rollen een nieuwe versie uit

stappen:

  1. voer een databasemigratie uit om een ​​nieuwe kolom te maken surname. Nu uw DB-versie v2
  2. gegevens kopiëren van last_name в surname. Nootdat als u veel van deze gegevens heeft, u batchmigratie moet overwegen!
  3. schrijf de code waar ze worden gebruikt BEIDE и nieuwEn старый kolom. Nu uw app-versie 2.0.0
  4. lees de waarde uit de kolom surname, als het niet is null, of van last_name, paling surname niet gespecificeerd. Je kunt verwijderen getLastName() uit de code, omdat deze wordt uitgevoerd null wanneer u uw toepassing terugdraait van 3.0.0 naar 2.0.0.

Als u Spring Boot Flyway gebruikt, worden deze twee stappen uitgevoerd tijdens het opstarten van de versie 2.0.0 toepassingen. Als u het hulpprogramma voor databaseversiebeheer handmatig uitvoert, moet u hiervoor twee verschillende dingen doen (eerst de databaseversie handmatig bijwerken en vervolgens de nieuwe applicatie implementeren).

Het is belangrijk. Vergeet niet dat de nieuw gemaakte kolom ZOU NIET быть NIET NUL. Als u een rollback uitvoert, is de oude toepassing niet op de hoogte van de nieuwe kolom en wordt deze tijdens de installatie niet geïnstalleerd Insert. Maar als u deze beperking toevoegt, zal uw database dat zijn v2, hiervoor moet de waarde van de nieuwe kolom worden ingesteld. Wat zal leiden tot schendingen van de beperkingen.

Het is belangrijk. U moet de methode verwijderen getLastName(), omdat in de versie 3.0.0 Er is geen concept van een kolom in de code last_name. Dit betekent dat daar null wordt ingesteld. U kunt de methode verlaten en controles toevoegen voor null, maar een veel betere oplossing zou zijn om dat in de logica te garanderen getSurname() u hebt de juiste waarde anders dan nul geselecteerd.

A/B-testen

De huidige situatie is dat we een applicatieversie hebben 1.0.0, geïmplementeerd op productie, en de database in v1. We moeten een tweede exemplaar van de versietoepassing implementeren 2.0.0waarmee de database wordt bijgewerkt v2.

stappen:

  1. er wordt een nieuw exemplaar van de versietoepassing geïmplementeerd 2.0.0waarmee de database wordt bijgewerkt v2
  2. in de tussentijd zijn enkele verzoeken verwerkt door versie-instanties 1.0.0
  3. de update is geslaagd en u beschikt over meerdere actieve exemplaren van de versietoepassing 1.0.0 en andere versies 2.0.0. Iedereen communiceert met de database in v2
  4. versie 1.0.0 gebruikt niet de achternaamkolom in de database, maar de versie 2.0.0 toepassingen. Ze interfereren niet met elkaar en er mogen geen fouten optreden.
  5. versie 2.0.0 slaat gegevens op in zowel de oude als de nieuwe kolom, waardoor achterwaartse compatibiliteit wordt gegarandeerd

Het is belangrijk. Als u zoekopdrachten heeft die items tellen op basis van waarden uit de oude/nieuwe kolom, moet u er rekening mee houden dat u nu dubbele waarden heeft (hoogstwaarschijnlijk zijn deze nog steeds aan het migreren). Als u bijvoorbeeld het aantal gebruikers wilt tellen waarvan de achternaam (hoe de kolom ook heet) met de letter begint A, en vervolgens totdat de gegevensmigratie is voltooid (oldnew kolom) heeft u mogelijk inconsistente gegevens als u een query uitvoert op een nieuwe kolom.

Toepassing terugdraaien

Nu hebben we een app-versie 2.0.0 en database erin v2.

stappen:

  1. draai uw applicatie terug naar de versie 1.0.0.
  2. versie 1.0.0 gebruikt geen kolom in de database surname, dus het terugdraaien zou succesvol moeten zijn

DB-wijzigingen

De database bevat een kolom met de naam last_name.

Flyway-bronscript:

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');

Script toevoegen surname.

Aandacht. Houd er rekening mee dat u GEEN NOT NULL-beperkingen kunt toevoegen aan de kolom die u toevoegt. Als u de JAR terugdraait, heeft de oude versie geen idee van de toegevoegde kolom en wordt deze automatisch ingesteld op NULL. Als er een dergelijke beperking is, zal de oude applicatie eenvoudigweg kapot gaan.

-- 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

Codewijzigingen

Wij slaan gegevens op als last_nameen surname. Tegelijkertijd lezen we uit last_name, aangezien deze kolom het meest relevant is. Tijdens het implementatieproces zijn sommige aanvragen mogelijk verwerkt door een toepassingsexemplaar dat nog niet is bijgewerkt.

/*
 * 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
                + "]";
    }
}

Stap 3: Achternaam uit de code verwijderen

Applicatieversie: 3.0.0

DB-versie:v3

Commentaar

Opmerking per.: Blijkbaar heeft de auteur in het originele artikel per abuis de tekst van dit blok uit stap 2 gekopieerd. Bij deze stap moeten wijzigingen worden aangebracht in de applicatiecode die gericht zijn op het verwijderen van de functionaliteit die de kolom gebruikt last_name.

Door een nieuwe kolom toe te voegen en de inhoud ervan te kopiëren, hebben we achterwaarts compatibele databasewijzigingen gemaakt. Ook als we de JAR terugdraaien of een oude JAR laten draaien, zal deze tijdens de uitvoering niet kapot gaan.

Toepassing terugdraaien

Momenteel hebben we een app-versie 3.0.0 en databank v3. Versie 3.0.0 slaat geen gegevens op last_name. Dit betekent dat binnen surname de meest actuele informatie wordt opgeslagen.

stappen:

  1. draai uw applicatie terug naar de versie 2.0.0.
  2. versie 2.0.0 gebruikt en last_name и surname.
  3. versie 2.0.0 zal nemen surname, als het niet nul is, anders -last_name

Databasewijzigingen

Er zijn geen structurele wijzigingen in de database. Het volgende script wordt uitgevoerd om de definitieve migratie van de oude gegevens uit te voeren:

-- 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;

Codewijzigingen

Opmerking per.: De beschrijving van dit blok is ook per ongeluk door de auteur gekopieerd uit stap 2. In overeenstemming met de logica van het artikel moeten wijzigingen in de code bij deze stap gericht zijn op het verwijderen van elementen die met de kolom werken last_name.

Wij slaan gegevens op als last_nameen surname. Daarnaast lezen we uit de kolom last_name, omdat dit het meest relevant is. Tijdens het implementatieproces kunnen sommige verzoeken worden verwerkt door een exemplaar dat nog niet is geüpgraded.

/*
 * 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
                + "]";
    }
}

Stap 4: Achternaam uit de database verwijderen

Applicatieversie: 4.0.0

DB-versie: v4

Commentaar

Vanwege het feit dat de versiecode 3.0.0 heb de kolom niet gebruikt last_name, er zal niets ergs gebeuren tijdens de uitvoering als we teruggaan naar 3.0.0 na het verwijderen van een kolom uit de database.

Logboeken voor scriptuitvoering

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-wijzigingen

over v3 we verwijderen gewoon de kolom last_name en voeg ontbrekende beperkingen toe.

-- 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;

Codewijzigingen

Er zijn geen wijzigingen in de code.

Uitgang

We hebben met succes een achterwaarts incompatibele kolomnaamwijziging toegepast door verschillende achterwaarts compatibele implementaties uit te voeren. Hieronder vindt u een overzicht van de uitgevoerde acties:

  1. implementatie van de applicatieversie 1.0.0 с v1 databaseschema (kolomnaam = last_name)
  2. implementatie van de applicatieversie 2.0.0, waarin gegevens worden opgeslagen last_name и surname. De applicatie leest van last_name. De database is in versie v2met kolommen zoals last_nameEn surname. surname is een kopie van last_name. (LET OP: deze kolom mag geen niet-null-beperking hebben)
  3. implementatie van de applicatieversie 3.0.0, waarin alleen gegevens worden opgeslagen surname en leest van achternaam. Wat de database betreft, vindt de laatste migratie plaats last_name в surname. Ook een beperking NIET NUL verwijderd van last_name. De database is nu in versie v3
  4. implementatie van de applicatieversie 4.0.0 - er worden geen wijzigingen in de code aangebracht. Database-implementatie v4, die verwijdert last_name. Hier kunt u ontbrekende beperkingen aan de database toevoegen.

Door deze aanpak te volgen, kunt u altijd één versie terugdraaien zonder dat de database-/applicatiecompatibiliteit wordt verbroken.

code

Alle code die in dit artikel wordt gebruikt, is beschikbaar op GitHub. Hieronder vindt u een aanvullende beschrijving.

Projecten

Na het klonen van de repository ziet u de volgende mappenstructuur.

├── 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

U kunt de scripts uitvoeren die in de onderstaande scripts worden beschreven, waarmee achterwaarts compatibele en incompatibele wijzigingen in de database worden gedemonstreerd.

Zien het geval is met achterwaarts compatibele wijzigingen, loop:

./scripts/scenario_backward_compatible.sh

En om te zien geval met achterwaarts incompatibele wijzigingen, loop:

./scripts/scenario_backward_incompatible.sh

Lentelaars Voorbeeld Flyway

Alle voorbeelden zijn overgenomen uit Spring Boot Sample Flyway.

Je kunt er eens naar kijken http://localhost:8080/flyway, er is een lijst met scripts.

Dit voorbeeld omvat ook de H2-console (op http://localhost:8080/h2-console) zodat u de databasestatus kunt bekijken (de standaardjdbc-URL is jdbc:h2:mem:testdb).

bovendien

Lees ook andere artikelen op onze blog:

Bron: www.habr.com

Voeg een reactie