Zero Downtime Deployment og databaser

Zero Downtime Deployment og databaser

I denne artikel forklares det i detaljer, hvordan man løser problemer med databasekompatibilitet ved deployment. Vi vil gennemgå, hvad der kan ske med dine applikationer i produktion, hvis du forsøger at udføre en deployment uden forberedelse. Derefter vil vi gennemgå de faser i applikationens livscyklus, der er nødvendige for at opnå nul nedetid (note fra oversætteren: herefter — nul nedetid). Resultatet af vores operationer vil være at anvende en bagudkompatibel ændring af databasen på en bagudkompatibel måde.

Hvis du vil undersøge kodeeksemplerne fra artiklen, kan du finde dem på GitHub.

Introduktion

Zero downtime deployment

Hvad er denne mystiske zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Hvordan opnår man dette? Der er flere metoder, her er en af dem:

  • udrul version nr. 1 af din tjeneste
  • udfør database-migrering
  • udrul version nr. 2 af din tjeneste parallelt med version nr. 1
  • så snart du ser, at version nr. 2 fungerer som den skal, skal du fjerne version nr. 1
  • Færdig!

Nemt, ikke sandt? Desværre er det ikke så enkelt, og vi vil se nærmere på det senere. Men lad os nu tjekke en anden ret almindelig deployment-proces — blue green deployment.

Har du nogensinde hørt om blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на denne artikel, hvor vi beskriver dette mere detaljeret. Kort opsummeret, lad os minde om, hvordan man laver blue green deployment:

  • sørg for, at der er to kopier af din produktionskode ("blue" og "green");
  • diriger al trafik til blue-miljøet, dvs. så produktions-URL'erne peger derhen;
  • udrul og test alle ændringer i applikationen i green-miljøet;
  • skift URL'erne fra blue til green-miljøet

Blue green deployment er en tilgang, der gør det muligt for dig nemt at implementere nye funktioner uden at bekymre dig om, at produktionen går ned. Dette skyldes, at hvis der sker noget, kan du nemt rulle tilbage til det forrige miljø med et enkelt "switch".

Efter at have læst alt det ovenstående, kan du stille spørgsmålet: Hvad har nul nedetid at gøre med blue green deployment?

Nå, de har en del til fælles, da opretholdelsen af to kopier af samme miljø kræver dobbelt så meget arbejde. Derfor holder nogle teams, som Martin Fowler siger, fast i en variation af denne tilgang: Martin Fowler, overholder variationer af denne tilgang:

En anden mulighed indebærer brug af den samme database, hvor man opretter blue-green switches for web- og domæne-lagene. I denne tilgang kan databaser ofte være problematiske, især når du har brug for at ændre dens schema for at understøtte en ny version af softwaren.

Og her nærmer vi os det centrale problem i denne artikel. Database. Lad os kigge på denne sætning igen.

Udfør database-migrering.

Nu skal du stille dig selv spørgsmålet – hvad hvis ændringen af databasen ikke er bagudkompatibel? Vil min første version af applikationen ikke gå i stykker? Faktisk vil det være tilfældet...

Derfor, selvom der er enorme fordele ved zero downtime / blue-green deployment, er virksomheder tilbøjelige til at følge den næste, mere sikre proces for deployment af deres applikationer:

  • forbered en pakke med den nye version af applikationen
  • slukke den kørende applikation
  • køre scripts til database-migrering
  • udsende og starte den nye version af applikationen

I denne artikel vil vi grundigt beskrive, hvordan du kan arbejde med databasen og koden for at udnytte fordelene ved zero downtime deployment.

Problemer med databasen

Hvis du har en stateless applikation, der ikke gemmer nogen data i databasen, kan du opnå zero downtime deployment med det samme. Desværre skal størstedelen af software gemme data et eller andet sted. Derfor er du nødt til at tænke dig om to gange, før du foretager nogen ændringer i schemaet. Før vi dykker ind i detaljerne om, hvordan man ændrer schemaet på en måde, der muliggør deployment uden nedetid, lad os først fokusere på versionsstyringsschemaet.

Versionsstyringsschema

I denne artikel vil vi bruge Flyway som værktøj til versionsstyring (anm. overs.: dette handler om database-migreringer). Naturligvis vil vi også skrive en Spring Boot applikation, der har indbygget understøttelse af Flyway og udfører migrationsschemaet under opstarten af applikationskonteksten. Når du bruger Flyway, kan du gemme migrationsscripts i din projekts mappe (som standard under classpath:db/migration). Her kan du se et eksempel på sådanne migrationsfiler

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

I dette eksempel ser vi 4 migrationsscenarier, som, hvis de ikke er blevet udført tidligere, vil blive udført én efter én, når applikationen startes. Lad os se på en af filerne (V1__init.sql) som eksempel.

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

Alt taler for sig selv: du kan bruge SQL til at definere, hvordan din database skal ændres. For mere information om Spring Boot og Flyway, se Spring Boot Docs.

Ved at bruge et versionsstyringsværktøj med Spring Boot får du 2 store fordele:

  • du adskiller databaseændringer fra kodeændringer
  • database-migreringen sker sammen med udrulningen af din applikation, dvs. din deploymentsproces forenkles

Løsning af databaseproblemer

I næste del af artiklen vil vi fokusere på to tilgange til databaseændringer.

  • bagudkompatibilitet
  • fremadkompatibilitet

Den første vil blive betragtet som en advarsel om, at man ikke bør udføre zero downtime deployment uden forberedelse... Den anden tilbyder en løsning på, hvordan man kan udføre deployment uden nedetid, mens man opretholder bagudkompatibilitet.

Vores projekt, som vi skal arbejde med, vil være en simpel Spring Boot Flyway-applikation, der har Person med first_name og last_name i databasen (note: Person er en tabel, og first_name og last_name – er felterne i den). Vi ønsker at omdøbe last_name i surname.

Antagelser

Før vi dykker ned i detaljerne, er det vigtigt at angive et par antagelser omkring vores applikationer. Det primære resultat, vi ønsker at opnå, vil være en forholdsvis simpel proces.

Bemærk. Business PRO-TIP. At forenkle processer kan spare dig for mange penge på vedligeholdelse (jo flere personer der arbejder i din virksomhed, desto flere penge kan du spare)!

Undgå at rulle databasen tilbage

Det forenkler deploymentsprocessen (nogle databaseruller er praktisk taget umulige, for eksempel at rulle tilbage efter sletning). Vi foretrækker kun at rulle applikationer tilbage. Derved, selv hvis du har forskellige databaser (f.eks. SQL og NoSQL), vil din deploymentpipeline se ens ud.

Der skal altid være mulighed for at rulle applikationen tilbage til én version før (ikke mere)

Rollbacks should only be performed when necessary. If the current version has a bug that is difficult to fix, we must be able to revert to the last working version. We assume that this last working version is the previous one. Maintaining code and database compatibility for more than one release would be extremely challenging and costly.

Note. For better readability, in this article we will change the major version of the application.

Step 1: Initial State

Application Version: 1.0.0
Database Version: v1

Kommentar

This will be the initial state of the application.

Database Changes

The database contains 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 Changes

The application saves Person data 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
                + "]";
    }
}

Incompatible backward renaming of a column

Let's consider an example of how to change the column name:

Attention. The following example will intentionally lead to a breakage. We demonstrate this to highlight the database compatibility issue.

Application Version: 2.0.0.BAD

Database Version: v2bad

Kommentar

Current changes do NOT allow us to run two instances (old and new) simultaneously. Therefore, zero downtime deployment will be difficult to achieve (given the assumptions, it is practically impossible).

A/B Testing

The current situation is that we have an application version 1.0.0, deployed in production, and the database v1. We need to deploy a second instance of the application, version 2.0.0.BAD, and update the database to v2bad.

Steps:

  1. a new instance of the application version has been deployed 2.0.0.BAD, which updates the database to v2bad
  2. in the database v2bad column last_name no longer exists — it has been changed to surname
  3. the update of the database and application was successful, and some instances are running in 1.0.0, others are in 2.0.0.BAD. All are connected to the database v2bad
  4. all instances of version 1.0.0 will start throwing errors because they will attempt to insert data into the column last_name, which no longer exists
  5. all instances of version 2.0.0.BAD will work without issues

As you can see, if we make incompatible changes to the database and application, A/B testing becomes impossible.

Application Rollback

Let's assume that after attempting to perform an A/B deployment (note: the author likely meant A/B testing here) we decided that we need to rollback the application to version 1.0.0. Let’s assume we do not want to rollback the database.

Steps:

  1. vi stopper applikationsinstansen med version 2.0.0.BAD
  2. databasen er stadig v2bad
  3. da versionen 1.0.0 forstår ikke, hvad der er surname, vil vi se fejl
  4. helvede er sluppet løs, vi kan ikke længere vende tilbage

Som du kan se, hvis vi foretager bagudkompatible ændringer i DB og applikationen, kan vi ikke rulle tilbage til den tidligere version.

Udførelseslogfiler

Bagudkompatibelt scenarie:

01) Kør 1.0.0
02) Vent på, at appen (1.0.0) starter
03) Generer en person ved at kalde POST localhost:9991/person til version 1.0.0
04) Kør 2.0.0.BAD
05) Vent på, at appen (2.0.0.BAD) starter
06) Generer en person ved at kalde POST localhost:9991/person til version 1.0.0 <-- dette skal fejle
07) Generer en person ved at kalde POST localhost:9992/person til version 2.0.0.BAD <-- dette skal lykkes

Starter appen i version 1.0.0
Generer en person i version 1.0.0
Sender en POST til 127.0.0.1:9991/person. Dette er svaret:

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

Starter appen i version 2.0.0.BAD
Generer en person i version 1.0.0
Sender en POST til 127.0.0.1:9991/person. Dette er svaret:

curl: (22) Den anmodede URL returnerede fejl: 500 Intern serverfejl

Generer en person i version 2.0.0.BAD
Sender en POST til 127.0.0.1:9995/person. Dette er svaret:

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

Database Changes

Migreringsscriptet, der omdøber last_name i surname

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

Scriptet, der omdøber last_name.

-- Denne ændring er bagudkompatibel - du kan ikke lave A/B test
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Code Changes

Vi har ændret navnet på feltet lastName og få en fungerende kommando. surname.

Omdøbning af kolonnen på en bagudkompatibel måde

Dette er den mest almindelige situation, vi kan støde på. Vi skal foretage bagudkompatible ændringer. Vi har allerede bevist, at for at implementere uden nedetid, må vi ikke blot anvende database-migreringen uden yderligere tiltag. I denne del af artiklen vil vi udføre 3 implementeringer af applikationen sammen med database-migreringer for at opnå det ønskede resultat og samtidig bevare bagudkompatibiliteten.

Bemærk. Husk, at vi har en DB version v1. Den indeholder kolonner first_name og last_name. Vi skal ændre last_name og få en fungerende kommando. surname. Vi har også en applikation version 1.0.0, som endnu ikke bruger surname.

Trin 2: Tilføj surname

Application Version: 2.0.0
Database Version: v2

Kommentar

Ved at tilføje en ny kolonne og kopiere dens indhold skaber vi bagudkompatible ændringer af databasen. Samtidig, hvis vi ruller JAR tilbage eller har en fungerende gammel JAR, vil den ikke gå i stykker under udførelsen.

Udrul den nye version

Steps:

  1. foretag databasen migrering for at oprette den nye kolonne surname. Nu er din DB version v2
  2. kopier data fra last_name i surname. Vær opmærksom på, hvilket, hvis du har mange af disse data, skal du overveje batchmigration!
  3. skriv koden, hvor BEGGE og ny, og gammel kolonne. Nu vil din applikation version 2.0.0
  4. læse værdien fra kolonnen surname, hvis den ikke er null, eller fra last_name, hvis surname ikke er angivet. Du kan fjerne getLastName() fra koden, da den vil returnere null ved tilbageførsel af din applikation fra 3.0.0 op til 2.0.0.

Hvis du bruger Spring Boot Flyway, vil disse to trin blive udført ved opstart af versionen 2.0.0 af applikationen. Hvis du kører databasanversionsstyringsværktøjet manuelt, skal du udføre to separate handlinger (først opdater databasen manuelt og derefter deploye den nye applikation).

Vigtigt. Husk, at den nyoprettede kolonne IKKE MÅ være NOT NULL. Hvis du laver en rollback, kender den gamle applikation ikke til den nye kolonne og vil ikke sætte den under Insert. Men hvis du tilføjer denne begrænsning, og din DB er v2, vil det kræve at sætte en værdi for den nye kolonne. Dette vil føre til brud på begrænsninger.

Vigtigt. Du bør fjerne metoden getLastName(), da der i versionen 3.0.0 ikke er noget begreb om kolonnen last_name. Det betyder, at der vil blive indsat null. Du kan beholde metoden og tilføje kontroller for null, men en meget bedre løsning vil være at sikre, at du i logikken getSurname() valgte den rette ikke-null værdi.

A/B Testing

The current situation is that we have an application version 1.0.0, deployet i produktionen, og DB i v1. Vi skal deploye den anden instans af applikationen version 2.0.0, som vil opdatere databasen til v2.

Steps:

  1. a new instance of the application version has been deployed 2.0.0, which updates the database to v2
  2. i mellemtiden behandlede nogle forespørgsler instanserne af versionen 1.0.0
  3. opdateringen blev gennemført med succes, og du har flere fungerende instanser af applikationen version 1.0.0 og andre versioner 2.0.0. Alle kommunikerer med DB i v2
  4. version 1.0.0 bruger ikke kolonnen surname i DB, mens versionen 2.0.0 bruger. De forstyrrer ikke hinanden, og der bør ikke være fejl.
  5. version 2.0.0 gemmer data i både den gamle og den nye kolonne, hvilket sikrer bagudkompatibilitet

Vigtigt. Hvis du har nogen forespørgsler, der tæller elementer baseret på værdier fra den gamle / nye kolonne, skal du huske, at du nu har duplikation af værdier (de migrerer sandsynligvis stadig). For eksempel, hvis du vil tælle antallet af brugere, hvis efternavn (uanset hvad kolonnen hedder) begyndte med bogstavet A, så kan du have inkonsistente data, hvis du udfører forespørgslen til den nye kolonne, indtil datamigreringen er afsluttet (old → ny kolonnen).

Application Rollback

Nu har vi applikationen version 2.0.0 og databasen i v2.

Steps:

  1. tilbagefør din applikation til version 1.0.0.
  2. version 1.0.0 bruger ikke kolonnen i DB surname, så tilbageførslen skal være vellykket

DB Ændringer

DB'en indeholder en kolonne med navnet last_name.

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

Tilføjelsesscript surname.

Attention. Husk, at det IKKE MÅTTE TILFØJES nogen NOT NULL begrænsninger til den tilføjede kolonne. Hvis du ruller JAR tilbage, har den gamle version ikke kendskab til den tilføjede kolonne og sætter automatisk værdien til NULL. I tilfælde af en sådan begrænsning vil den gamle applikation blot gå i stykker.

-- NOTE: Dette felt kan ikke have NOT NULL begrænsningen, for hvis du ruller tilbage, vil den gamle version ikke vide om dette felt
-- og vil altid sætte det til NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- VI ANTAGER, AT DET ER EN HURTIG MIGRATION - ELLERS SKULLE VI HAVE MIGRERET I PARTIER
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Code Changes

Vi gemmer data både som last_name. Vi kan gøre dette, da vi har ledige porte (eller vi frigør dem på forhånd) i switchene surname. I mellemtiden læser vi fra last_name, da denne kolonne er den mest relevante. Under udrulningen kunne nogle forespørgsler være blevet behandlet af en applikationsinstans, der endnu ikke var opdateret.

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

Trin 3: Fjernelse af last_name fra koden

Application Version: 3.0.0

Database Version:v3

Kommentar

Bem.: Det ser ud til, at forfatteren ved en fejltagelse har kopieret teksten i denne blok fra trin 2 i den oprindelige artikel. På dette trin skal der foretages ændringer i applikationskoden rettet mod at fjerne funktionalitet, der benytter kolonnen last_name.

Ved at tilføje en ny kolonne og kopiere dens indhold har vi skabt bagudkompatible ændringer i DB. Desuden, hvis vi ruller JAR tilbage, eller hvis vi har en fungerende gammel JAR, vil den ikke gå itu under udførelsen.

Application Rollback

I øjeblikket har vi applikationen version 3.0.0 og databasen v3. Versionen 3.0.0 gemmer ikke data i last_name. Det betyder, at surname indeholder de mest aktuelle oplysninger.

Steps:

  1. tilbagefør din applikation til version 2.0.0.
  2. version 2.0.0 bruger og last_name og surname.
  3. version 2.0.0 vil tage surname, hvis den ikke er nul, ellers —last_name

Database Changes

Der er ingen strukturelle ændringer i DB. Det følgende script udfører den endelige migration af gamle data:

-- VI ANTAGER, AT DET ER EN HURTIG MIGRATION - ELLERS SKULLE VI HAVE MIGRERET I PARTIER
-- VI KONTROLLERER OGSÅ IKKE, OM VI IKKE OVERRIDER EXISTENDE ENTRYS. VI SKULLE SAMMENLIGNE
-- ENTRYS VERSIONER FOR AT SIKRE, AT HVIS DER ALLEREDE ER EN ENTRY MED ET HØJERE VERSIONNUMMER
-- VIL VI IKKE OVERRIDE DET.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- FJERNER NOT NULL BEGRÆNSNINGEN, ELLERS SKAL DU PRØVE AT INDSÆTTE NULL-VÆRDIEN FOR LAST_NAME
-- MED EN NOT NULL BEGRÆNSNING.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Code Changes

Bem.: Beskrivelsen af denne blok blev også fejlagtigt kopieret af forfatteren fra trin 2. I overensstemmelse med artikelens logik bør ændringerne i koden på dette trin rettes mod at fjerne elementer, der arbejder med kolonnen last_name.

Vi gemmer data både som last_name. Vi kan gøre dette, da vi har ledige porte (eller vi frigør dem på forhånd) i switchene surname. Derudover læser vi fra kolonnen last_name, da den er den mest relevante. Under udrulningen kan nogle forespørgsler blive behandlet af en instans, der ikke er blevet opdateret endnu.

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

Trin 4: Fjern last_name fra databasen

Application Version: 4.0.0

Database Version: v4

Kommentar

På grund af at versionskoden 3.0.0 ikke brugte kolonnen last_name, under udførelsen sker der ikke noget dårligt, hvis vi ruller tilbage til 3.0.0 efter at kolonnen er fjernet fra databasen.

Udførelseslogfiler

Vi vil gøre det på følgende måde:

01) Kør 1.0.0
02) Vent på, at appen (1.0.0) starter
03) Generér en person ved at kalde POST localhost:9991/person til version 1.0.0
04) Kør 2.0.0
05) Vent på, at appen (2.0.0) starter
06) Generér en person ved at kalde POST localhost:9991/person til version 1.0.0
07) Generér en person ved at kalde POST localhost:9992/person til version 2.0.0
08) Dræb appen (1.0.0)
09) Kør 3.0.0
10) Vent på, at appen (3.0.0) starter
11) Generér en person ved at kalde POST localhost:9992/person til version 2.0.0
12) Generér en person ved at kalde POST localhost:9993/person til version 3.0.0
13) Dræb appen (3.0.0)
14) Kør 4.0.0
15) Vent på, at appen (4.0.0) starter
16) Generér en person ved at kalde POST localhost:9993/person til version 3.0.0
17) Generér en person ved at kalde POST localhost:9994/person til version 4.0.0

Starter appen i version 1.0.0
Genererer en person i version 1.0.0
Sender et post til 127.0.0.1:9991/person. Dette er svaret:

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

Starter appen i version 2.0.0

Genererer en person i version 1.0.0
Sender et post til 127.0.0.1:9991/person. Dette er svaret:

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

Genererer en person i version 2.0.0
Sender et post til 127.0.0.1:9992/person. Dette er svaret:

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

Dræber appen 1.0.0

Starter appen i version 3.0.0

Genererer en person i version 2.0.0
Sender et post til 127.0.0.1:9992/person. Dette er svaret:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Genererer en person i version 3.0.0
Sender et post til 127.0.0.1:9993/person. Dette er svaret:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Dræber appen 2.0.0

Starter appen i version 4.0.0

Genererer en person i version 3.0.0
Sender et post til 127.0.0.1:9993/person. Dette er svaret:

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

Genererer en person i version 4.0.0
Sender et post til 127.0.0.1:9994/person. Dette er svaret:

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

DB ændringer

Angående v3 fjerner vi blot kolonnen last_name og tilføjer de manglende begrænsninger.

-- FJERN KOLONNEN
ALTER TABLE PERSON DROP last_name;

-- TILFØJ BEGRÆNSNINGER
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Code Changes

Der er ingen ændringer i koden.

Output

Vi har med succes anvendt et tilbagekompatibelt ændring af kolonnenavnet ved at udføre flere bagudkompatible udrulninger. Her er et resumé af de udførte handlinger:

  1. udrulningen af applikationen version 1.0.0 med v1 DB schema (kolonnenavn = last_name)
  2. udrulningen af applikationen version 2.0.0, som gemmer data i last_name og surname. Applikationen læser fra last_name. DB'en er i version v2, som indeholder kolonner som last_name, og efternavn. efternavn er en kopi af last_name. (BEMÆRK: denne kolonne må ikke have en not null begrænsning)
  3. udrulningen af applikationen version 3.0.0, som gemmer data kun i surname og læser fra efternavn. Med hensyn til DB'en sker den sidste migration last_name i surname. Begrænsningen NOT NULL fjernes fra last_name. DB'en er nu i version v3
  4. udrulningen af applikationen version 4.0.0 — der foretages ingen ændringer i koden. Udrulning af databasen v4, som fjerner last_name. Her kan du tilføje eventuelle manglende begrænsninger i DB'en.

Ved at følge denne tilgang kan du altid rulle tilbage til en version tidligere, uden at bryde databasens / applikationens kompatibilitet.

Kode

Al koden, der bruges i denne artikel, er tilgængelig på Github. Her er en yderligere beskrivelse.

Projekter

Efter kloning af repositoryet vil du se følgende mappestruktur.

├── boot-flyway-v1              - 1.0.0 version af appen med v1 af skemaet
├── boot-flyway-v2              - 2.0.0 version af appen med v2 af skemaet (bagudkompatibel - appen kan rulles tilbage)
├── boot-flyway-v2-bad          - 2.0.0.BAD version af appen med v2bad af skemaet (bagudkompatibel - appen kan ikke rulles tilbage)
├── boot-flyway-v3              - 3.0.0 version af appen med v3 af skemaet (appen kan rulles tilbage)
└── boot-flyway-v4              - 4.0.0 version af appen med v4 af skemaet (appen kan rulles tilbage)

Skripter

Du kan køre script der er beskrevet i nedenstående scripts, som vil demonstrere bagudkompatible og inkompatible ændringer i DB'en.

For at se tilfældet med bagudkompatible ændringer, kør:

.\/scripts\/scenario_backward_compatible.sh

Og for at se tilfældet med bagud inkompatible ændringer, kør:

.\/scripts\/scenario_backward_incompatible.sh

Spring Boot Sample Flyway

Alle eksempler er taget fra Spring Boot Sample Flyway.

Du kan kigge på http://localhost:8080/flyway, der har en liste over scripts.

Dette eksempel inkluderer også H2-konsollen (på adressen http://localhost:8080/h2-console), så du kan se status på databasen (standard jdbc-URL — jdbc:h2:mem:testdb).

Yderligere

Læs også andre artikler på vores blog:

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster