Zero Downtime Deployment e basi di dati

Zero Downtime Deployment e basi di dati

In questo articolo verrà spiegato in dettaglio come affrontare i problemi di compatibilità del database durante il deployment. Parleremo di cosa può succedere alle tue applicazioni in produzione se provi a eseguire un deployment senza una preparazione preventiva. Poi esamineremo le fasi del ciclo di vita dell'applicazione necessarie per avere zero downtime (nota del traduttore: di seguito — zero downtime). Il risultato delle nostre operazioni sarà l'applicazione di una modifica incompatibile in modo retrocompatibile.

Se vuoi approfondire i codici d'esempio dell'articolo, li troverai su GitHub.

Introduzione

Zero downtime deployment

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

Come raggiungerlo? Ci sono diversi modi, ecco uno di essi:

  • distribuisci la versione n. 1 del tuo servizio
  • esegui la migrazione del database
  • distribuisci la versione n. 2 del tuo servizio parallela alla versione n. 1
  • una volta che vedi che la versione n. 2 funziona come dovrebbe, rimuovi la versione n. 1
  • fatto!

Facile, vero? Sfortunatamente, non è così semplice, e ne discuteremo più avanti. Ma ora diamo un'occhiata a un altro processo di deployment piuttosto comune: il blue green deployment.

Hai mai sentito parlare di blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на questo articolo, dove ne descriviamo in dettaglio. Riassumendo brevemente, ricordiamo come fare il blue green deployment:

  • garantire il funzionamento di due copie del tuo codice di produzione ("blue" e "green");
  • inviare tutto il traffico nell'ambiente blue, ovvero affinché gli indirizzi URL di produzione puntino lì;
  • distribuire e testare tutte le modifiche dell'applicazione nell'ambiente green;
  • cambiare gli indirizzi URL dall'ambiente blue a quello green

Il blue green deployment è un approccio che ti consente di introdurre facilmente nuove funzionalità senza preoccuparti che la produzione si interrompa. Questo è dovuto al fatto che anche se succede qualcosa, puoi facilmente tornare all'ambiente precedente semplicemente "cliccando l'interruttore".

Dopo aver letto tutto quanto detto sopra, potresti chiederti: Qual è il legame tra zero downtime e il blue green deployment?

Beh, hanno molto in comune, poiché il supporto di due copie dello stesso ambiente richiede sforzi doppi per la loro manutenzione. Ecco perché alcuni team, come afferma Martin Fowler, seguono una variazione di questo approccio:

Un'altra opzione consiste nell'utilizzare lo stesso database, creando switch blu-verdi per i livelli web e di dominio. In questo approccio, i database possono spesso diventare un problema, specialmente quando è necessario modificare il loro schema per supportare una nuova versione del software.

E qui ci avviciniamo al problema principale di questo articolo. Qui la scelta è stata molto più semplice, Simple SCADA offre due prodotti da utilizzare: MS SQL Server e MySQL. Il secondo mi è sembrato più vicino, poiché avevo già lavorato con esso, quindi mi sono fermato lì.. Diamo un'altra occhiata a questa frase.

esegui la migrazione del database.

Ora devi porti una domanda: e se la modifica del database fosse retrocompatibile? La mia prima versione dell'app non si romperà? In realtà, è esattamente quello che accadrà…

Pertanto, anche se ci sono enormi vantaggi nel zero downtime / blue green deployment, le aziende tendono a seguire il seguente processo di distribuzione più sicuro delle loro applicazioni:

  • preparare un pacchetto con la nuova versione dell'applicazione
  • disattivare l'applicazione in esecuzione
  • eseguire gli script per la migrazione del database
  • distribuire e avviare la nuova versione dell'applicazione

In questo articolo descriveremo in dettaglio come puoi lavorare con il database e il codice per sfruttare i vantaggi del deployment senza downtime.

Problemi con il database

Se hai un'applicazione stateless che non memorizza dati nel database, puoi ottenere immediatamente il zero downtime deployment. Sfortunatamente, gran parte del software deve memorizzare dei dati da qualche parte. Ecco perché devi pensare due volte prima di apportare qualsiasi modifica allo schema. Prima di immergerci nei dettagli su come modificare lo schema in modo che il deployment senza downtime diventi possibile, concentriamoci prima sulla gestione delle versioni dello schema.

Gestione delle versioni dello schema

In questo articolo useremo Flyway come strumento per la gestione delle versioni (nota del traduttore: si parla di migrazioni del database). Naturalmente, scriveremo anche un'applicazione Spring Boot che ha il supporto integrato per Flyway e eseguirà la migrazione dello schema durante l'impostazione del contesto dell'applicazione. Utilizzando Flyway, puoi memorizzare gli script di migrazione nella cartella dei tuoi progetti (per impostazione predefinita in classpath:db/migration). Qui puoi vedere un esempio di tali file di migrazione:

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

In questo esempio vediamo 4 scenari di migrazione che, se non sono stati eseguiti in precedenza, saranno eseguiti uno dopo l'altro all'avvio dell'applicazione. Esaminiamo uno dei file (V1__init.sql) come esempio.

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

Tutto parla da sé: puoi usare SQL per definire come dovrebbe essere modificata la tua base di dati. Per ulteriori informazioni su Spring Boot e Flyway, dai un'occhiata a Documentazione di Spring Boot.

Usando uno strumento di gestione delle versioni con Spring Boot, ottieni 2 grandi vantaggi:

  • separi le modifiche del database dalle modifiche al codice
  • la migrazione del database avviene insieme al rilascio della tua applicazione, cioè il tuo processo di deployment viene semplificato

Risoluzione dei problemi del database

Nella prossima sezione dell'articolo ci concentreremo sull'esaminare due approcci alle modifiche del database.

  • incompatibilità retroattiva
  • compatibilità retroattiva

Il primo verrà considerato come un avvertimento sul fatto che non dovresti effettuare deployment senza downtime senza preparazioni... Il secondo propone una soluzione su come effettuare il deployment senza interruzioni mantenendo al contempo la compatibilità retroattiva.

Il progetto su cui lavoreremo sarà una semplice applicazione Spring Boot Flyway che contiene Person con first_name e last_name nel database (nota del traduttore: Person è una tabella e first_name e last_name è uno dei campi al suo interno). Vogliamo rinominare last_name in surname.

Assunzioni

Prima di addentrarci nei dettagli, è necessario definire alcune assunzioni riguardo alle nostre applicazioni. Il risultato principale che vogliamo raggiungere sarà un processo piuttosto semplice.

Nota. PRO-TIP aziendale. Semplificare i processi può farti risparmiare molti soldi nella manutenzione (più persone lavorano nella tua azienda, più soldi puoi risparmiare)!

Non è necessario eseguire il rollback del database

Questo semplifica il processo di deployment (alcuni rollback del database sono praticamente impossibili, ad esempio il rollback di un'operazione di eliminazione). Preferiamo fare rollback solo delle applicazioni. Così, anche se hai database diversi (ad esempio SQL e NoSQL), il tuo pipeline di deployment apparirà lo stesso.

È necessario che ci sia SEMPRE la possibilità di riportare l'applicazione a una versione precedente (non oltre)

Il rollback deve essere effettuato solo se necessario. Se nella versione attuale c'è un errore difficile da risolvere, dobbiamo avere la possibilità di tornare all'ultima versione funzionante. Supponiamo che quest'ultima versione funzionante sia quella precedente. Mantenere la compatibilità del codice e del database per più di un rilascio sarebbe estremamente difficile e costoso.

Nota. Per una maggiore leggibilità, nell'ambito di questo articolo modificheremo la versione principale dell'applicazione.

Passo 1: Stato iniziale

Versione dell'applicazione: 1.0.0
Versione del DB: v1

Commento

Questo sarà lo stato iniziale dell'applicazione.

Modifiche al DB

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

Modifiche al codice

L'applicazione memorizza i dati Person 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
                + "]";
    }
}

Ridenominazione di colonna non retrocompatibile

Vediamo un esempio di come modificare il nome di una colonna:

Attenzione. Il seguente esempio causerà intenzionalmente un errore. Mostriamo questo per dimostrare il problema di compatibilità del database.

Versione dell'applicazione: 2.0.0.BAD

Versione del DB: v2bad

Commento

Le modifiche attuali NON ci consentono di eseguire due istanze (vecchia e nuova) contemporaneamente. Pertanto, un deployment senza downtime sarà difficile da raggiungere (considerando le assunzioni, è praticamente impossibile).

A/B testing

La situazione attuale è che abbiamo un'applicazione di versione 1.0.0, distribuita in produzione, e il DB v1. Dobbiamo distribuire una seconda istanza dell'applicazione, versione 2.0.0.BAD, e aggiornare il database a v2bad.

Passi:

  1. è stata distribuita una nuova istanza dell'applicazione versione 2.0.0.BAD, che aggiorna il database a v2bad
  2. nel database v2bad colonna last_name non esiste più — è stata cambiata in surname
  3. l'aggiornamento del database e dell'applicazione è avvenuto con successo, e alcune istanze funzionano in 1.0.0, altre in 2.0.0.BAD. Tutti collegati al DB v2bad
  4. tutte le istanze della versione 1.0.0 inizieranno a restituire errori, perché tenteranno di inserire dati nella colonna last_name, che non esiste più
  5. tutte le istanze della versione 2.0.0.BAD funzioneranno senza problemi

Come potete vedere, se apportiamo modifiche al DB e all'applicazione che non sono retrocompatibili, l'A/B testing diventa impossibile.

Rollback dell'applicazione

Supponiamo che dopo aver tentato di eseguire un A/B deployment (nota dell'autore: probabilmente, qui si voleva intendere A/B testing) abbiamo deciso che dobbiamo effettuare il rollback dell'applicazione alla versione 1.0.0. Supponiamo che non vogliamo effettuare il rollback del database.

Passi:

  1. stiamo fermando l'istanza dell'applicazione versione 2.0.0.BAD
  2. il database è ancora v2bad
  3. poiché la versione 1.0.0 non comprende cosa sia surname, vedremo errori
  4. l'inferno è stato liberato, non possiamo più tornare indietro

Come potete vedere, se apportiamo modifiche al database e all'applicazione che non sono retrocompatibili, non possiamo tornare alla versione precedente.

Log di esecuzione dello script

Scenario retrocompatibile:

01) Esegui 1.0.0
02) Aspetta che l'app (1.0.0) si avvii
03) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0
04) Esegui 2.0.0.BAD
05) Aspetta che l'app (2.0.0.BAD) si avvii
06) Genera una persona chiamando POST localhost:9991/person per la versione 1.0.0 <--- questo dovrebbe fallire
07) Genera una persona chiamando POST localhost:9992/person per la versione 2.0.0.BAD <--- questo dovrebbe avere successo

Avviando l'app nella versione 1.0.0
Genera una persona nella versione 1.0.0
Inviando un post a 127.0.0.1:9991/person. Questa è la risposta:

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

Avviando l'app nella versione 2.0.0.BAD
Genera una persona nella versione 1.0.0
Inviando un post a 127.0.0.1:9991/person. Questa è la risposta:

curl: (22) L'URL richiesto ha restituito errore: 500 Errore Interno del Server

Genera una persona nella versione 2.0.0.BAD
Inviando un post a 127.0.0.1:9995/person. Questa è la risposta:

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

Modifiche al DB

Script di migrazione che rinomina last_name in surname

Script Flyway originale:

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 che rinomina last_name.

-- Questa modifica non è retrocompatibile - non puoi fare A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Modifiche al codice

Abbiamo cambiato il nome del campo lastName in surname.

Rinominare la colonna in modo retrocompatibile

Questa è la situazione più comune che possiamo incontrare. Dobbiamo apportare modifiche non retrocompatibili. Abbiamo già dimostrato che per il deploy senza interruzioni non dobbiamo semplicemente applicare la migrazione del database senza ulteriori azioni. In questa sezione dell'articolo faremo 3 deploy dell'applicazione insieme alle migrazioni del database, per raggiungere il risultato desiderato e mantenere la retrocompatibilità.

Nota. Ricordiamo che abbiamo un database della versione v1. Contiene colonne first_name e last_name. Dobbiamo cambiare last_name in surname. Abbiamo anche un'applicazione della versione 1.0.0, che al momento non utilizza surname.

Passaggio 2: Aggiungi surname

Versione dell'applicazione: 2.0.0
Versione del DB: v2

Commento

Aggiungendo una nuova colonna e copiando il suo contenuto, creiamo modifiche al database retrocompatibili. Allo stesso tempo, se facciamo un rollback del JAR o abbiamo un vecchio JAR funzionante, non si romperà durante l'esecuzione.

Rilasciamo una nuova versione

Passi:

  1. effettua la migrazione del database per creare una nuova colonna surname. Ora il tuo database è della versione v2
  2. copia i dati da last_name in surname. Si prega di notare, se hai molti di questi dati, dovresti considerare la migrazione in batch!
  3. scrivi il codice dove sono usati ENTRAMBI e sezione, e vecchio colonna. Ora la tua applicazione versione 2.0.0
  4. leggi il valore dalla colonna surname, se non è null, o da last_name, se surname non è specificato. Puoi rimuovere getLastName() dal codice, poiché restituirà null in caso di rollback della tua applicazione, con 3.0.0 fino a 2.0.0.

Se stai utilizzando Spring Boot Flyway, questi due passaggi verranno eseguiti all'avvio della versione 2.0.0 dell'applicazione. Se esegui manualmente lo strumento di gestione delle versioni del database, dovrai eseguire due azioni distinte (prima aggiorna manualmente la versione del db e poi distribuisci la nuova applicazione).

Importante. Ricorda che la nuova colonna NON DEVE essere NOT NULL. Se esegui un rollback, la vecchia applicazione non conosce la nuova colonna e non la imposterà durante l'Inserimento. Ma se aggiungi questo vincolo, e il tuo DB sarà v2, ciò richiederà di impostare un valore per la nuova colonna. Questo porterà a violazioni dei vincoli.

Importante. Dovresti rimuovere il metodo getLastName(), poiché nella versione 3.0.0 non esiste il concetto di colonna last_name. Questo significa che verranno impostati a null. Puoi lasciare il metodo e aggiungere controlli su null, ma sarebbe molto meglio assicurarsi che nella logica getSurname() hai scelto il valore non nullo corretto.

A/B testing

La situazione attuale è che abbiamo un'applicazione di versione 1.0.0, distribuito in produzione, e il DB in v1. Dobbiamo distribuire un secondo istanza dell'applicazione versione 2.0.0, che aggiornerà il database a v2.

Passi:

  1. è stata distribuita una nuova istanza dell'applicazione versione 2.0.0, che aggiorna il database a v2
  2. nel frattempo alcune richieste sono state elaborate dalle istanze della versione 1.0.0
  3. l'aggiornamento è andato a buon fine, e hai diverse istanze funzionanti dell'applicazione versione 1.0.0 e altre versioni 2.0.0. Tutti comunicano con il DB in v2
  4. versione 1.0.0 non utilizza nella DB la colonna cognome, mentre la versione 2.0.0 utilizza. Non si ostacolano a vicenda e non ci dovrebbero essere errori.
  5. versione 2.0.0 mantiene i dati sia nella vecchia che nella nuova colonna, garantendo la retrocompatibilità

Importante. Se hai richieste che contano gli elementi in base ai valori dalla vecchia / nuova colonna, devi ricordare che ora hai una duplicazione dei valori (probabilmente continuano a migrare). Ad esempio, se vuoi contare il numero di utenti il cui cognome (qualunque sia il nome della colonna) inizia con la lettera A, fino a quando la migrazione dei dati non sarà completata (oldnew colonna) potresti avere dati incoerenti se esegui una query sulla nuova colonna.

Rollback dell'applicazione

Attualmente abbiamo l'applicazione versione 2.0.0 e il database in v2.

Passi:

  1. rollback della tua applicazione alla versione 1.0.0.
  2. versione 1.0.0 non utilizza nella DB la colonna surname, quindi il rollback deve avere successo

Modifiche DB

Il DB contiene una colonna di nome last_name.

Script Flyway originale:

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 di aggiunta surname.

Attenzione. Ricorda che NON PUOI AGGIUNGERE alcun vincolo NOT NULL alla colonna aggiunta. Se esegui un rollback del JAR, la vecchia versione non conosce la colonna aggiunta e la imposterà automaticamente su NULL. In presenza di tale vincolo, la vecchia applicazione si romperà.

-- NOTA: Questo campo non può avere il vincolo NOT NULL perché se esegui il rollback, la vecchia versione non conoscerà questo campo
-- e lo imposterà sempre su NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- PRESUMIAMO CHE SIA UNA MIGRAZIONE RAPIDA - ALTRIMENTI DOVREMMO MIGRARE IN BATCH
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Modifiche al codice

Conserviamo i dati sia in last_name, sia in surname. Allo stesso tempo leggiamo da last_name, poiché questa colonna è la più aggiornata. Durante il processo di deployment, alcune query potrebbero essere state elaborate da un'istanza dell'applicazione che non era ancora stata aggiornata.

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

Passaggio 3: Rimozione di last_name dal codice

Versione dell'applicazione: 3.0.0

Versione del DB:v3

Commento

Nota del traduttore: Apparentemente, nell'articolo originale, l'autore ha erroneamente copiato il testo di questo blocco dal passaggio 2. In questo passaggio, devono essere effettuate modifiche al codice dell'applicazione per rimuovere la funzionalità che utilizza la colonna last_name.

Aggiungendo una nuova colonna e copiando il suo contenuto, abbiamo creato modifiche DB compatibili con le versioni precedenti. Inoltre, se eseguiamo un rollback del JAR o abbiamo un vecchio JAR funzionante, non si romperà durante l'esecuzione.

Rollback dell'applicazione

Al momento abbiamo un'applicazione di versione 3.0.0 e un database v3. La versione 3.0.0 non conserva dati in last_name. Ciò significa che in surname si trova l'informazione più recente.

Passi:

  1. rollback della tua applicazione alla versione 2.0.0.
  2. versione 2.0.0 utilizza e last_name e surname.
  3. versione 2.0.0 prenderà surname, se non è nullo, altrimenti -last_name

Modifiche al DB

Non ci sono modifiche strutturali nel DB. Viene eseguito il seguente script, che realizza l'ultima migrazione dei dati precedenti:

-- PRESUMIAMO CHE SIA UNA MIGRAZIONE RAPIDA - ALTRIMENTI DOVREMMO MIGRARE IN BATCH
-- Inoltre NON STIAMO VERIFICANDO SE NON STIAMO SOVRASCRIVENDO ENTRATE ESISTENTI. DOVREMMO CONFRONTARE
-- LE VERSIONI DELLE ENTRATE PER ASSICURARCI CHE SE C'È GIÀ UN'ENTRATA CON UN NUMERO DI VERSIONE SUPERIORE
-- NON LA SOVRASCRIVIAMO.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- RIMOZIONE DEL VINCOLO NOT NULL; ALTRIMENTI PROVERAI A INSERIRE UN VALORE NULL PER LAST_NAME
-- CON UN VINCOLO NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Modifiche al codice

Nota del traduttore: La descrizione di questo blocco è stata anch'essa copiata erroneamente dall'autore dal passaggio 2. Secondo la logica della narrazione dell'articolo, le modifiche al codice in questo passaggio devono essere dirette a rimuovere gli elementi che lavorano con la colonna last_name.

Conserviamo i dati sia in last_name, sia in surname. Inoltre, leggiamo dalla colonna last_name, poiché è la più pertinente. Durante il processo di distribuzione, alcune richieste possono essere elaborate da un'istanza che non è ancora stata aggiornata.

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

Passaggio 4: Rimozione di last_name dal DB

Versione dell'applicazione: 4.0.0

Versione del DB: v4

Commento

A causa del fatto che il codice della versione 3.0.0 non ha utilizzato la colonna last_name, durante l'esecuzione non accadrà nulla di male se tornassimo a 3.0.0 dopo la rimozione della colonna dal database.

Log di esecuzione dello script

Lo faremo nel seguente modo:

01) Eseguire 1.0.0
02) Attendere che l'app (1.0.0) si avvii
03) Generare una persona chiamando POST localhost:9991/person alla versione 1.0.0
04) Eseguire 2.0.0
05) Attendere che l'app (2.0.0) si avvii
06) Generare una persona chiamando POST localhost:9991/person alla versione 1.0.0
07) Generare una persona chiamando POST localhost:9992/person alla versione 2.0.0
08) Terminare l'app (1.0.0)
09) Eseguire 3.0.0
10) Attendere che l'app (3.0.0) si avvii
11) Generare una persona chiamando POST localhost:9992/person alla versione 2.0.0
12) Generare una persona chiamando POST localhost:9993/person alla versione 3.0.0
13) Terminare l'app (3.0.0)
14) Eseguire 4.0.0
15) Attendere che l'app (4.0.0) si avvii
16) Generare una persona chiamando POST localhost:9993/person alla versione 3.0.0
17) Generare una persona chiamando POST localhost:9994/person alla versione 4.0.0

Avviando l'app nella versione 1.0.0
Genera una persona nella versione 1.0.0
Invio di un post a 127.0.0.1:9991/person. Questa è la risposta:

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

Avviando l'app nella versione 2.0.0

Genera una persona nella versione 1.0.0
Invio di un post a 127.0.0.1:9991/person. Questa è la risposta:

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

Genera una persona nella versione 2.0.0
Invio di un post a 127.0.0.1:9992/person. Questa è la risposta:

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

Terminazione dell'app 1.0.0

Avviando l'app nella versione 3.0.0

Genera una persona nella versione 2.0.0
Invio di un post a 127.0.0.1:9992/person. Questa è la risposta:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Genera una persona nella versione 3.0.0
Invio di un post a 127.0.0.1:9993/person. Questa è la risposta:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Terminazione dell'app 2.0.0

Avviando l'app nella versione 4.0.0

Genera una persona nella versione 3.0.0
Invio di un post a 127.0.0.1:9993/person. Questa è la risposta:

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

Genera una persona nella versione 4.0.0
Invio di un post a 127.0.0.1:9994/person. Questa è la risposta:

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

Modifiche al DB

Riguardo a v3 stiamo semplicemente rimuovendo la colonna last_name e aggiungendo i vincoli mancanti.

-- RIMUOVERE LA COLONNA
ALTER TABLE PERSON DROP last_name;

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

Modifiche al codice

Non ci sono modifiche nel codice.

Conclusione

Abbiamo ripristinato con successo la modifica incompatibile del nome della colonna, eseguendo alcune distribuzioni retrocompatibili. Di seguito è riportato un riepilogo delle azioni eseguite:

  1. distribuzione dell'app nella versione 1.0.0 con v1 schema del DB (nome della colonna = last_name)
  2. distribuzione dell'app nella versione 2.0.0, che conserva i dati in last_name e surname. L'applicazione legge da last_name. Il DB si trova nella versione v2, contenente colonne come last_name, sia cognome. cognome è una copia di last_name. (NOTA: questa colonna non deve avere la restrizione not null)
  3. distribuzione dell'app nella versione 3.0.0, che salva i dati solo in surname e legge da cognome. Per quanto riguarda il DB, si verifica l'ultima migrazione last_name in surname. Inoltre, la restrizione NOT NULL viene rimossa da last_name. Il DB è ora nella versione v3
  4. distribuzione dell'app nella versione 4.0.0 — nel codice non vengono effettuate modifiche. Il deploy del database v4, che rimuove last_name. Qui puoi aggiungere eventuali restrizioni mancanti nel DB.

Seguendo questo approccio, puoi sempre tornare indietro di una versione senza rompere la compatibilità del database / dell'applicazione.

Codice

Tutto il codice utilizzato in questo articolo è disponibile su Github. Di seguito una descrizione aggiuntiva.

Progetti

Dopo aver clonato il repository, vedrai la seguente struttura delle cartelle.

├── boot-flyway-v1              - versione 1.0.0 dell'app con v1 dello schema
├── boot-flyway-v2              - versione 2.0.0 dell'app con v2 dello schema (compatibile con la retrocompatibilità - l'app può essere ripristinata)
├── boot-flyway-v2-bad          - versione 2.0.0.BAD dell'app con v2bad dello schema (non compatibile con la retrocompatibilità - l'app non può essere ripristinata)
├── boot-flyway-v3              - versione 3.0.0 dell'app con v3 dello schema (l'app può essere ripristinata)
└── boot-flyway-v4              - versione 4.0.0 dell'app con v4 dello schema (l'app può essere ripristinata)

Script

Puoi eseguire gli script descritti di seguito, che dimostreranno le modifiche compatibili e non compatibili con il DB.

Per vedere un caso di modifiche compatibili con la retrocompatibilità, esegui:

.\/scripts\/scenario_backward_compatible.sh

E per vedere un caso di modifiche non compatibili con la retrocompatibilità, esegui:

.\/scripts\/scenario_backward_incompatible.sh

Esempio di Spring Boot Flyway

Tutti gli esempi sono tratti da Spring Boot Sample Flyway.

Puoi dare un'occhiata a http://localhost:8080/flyway, lì troverai l'elenco degli script.

Questo esempio include anche la console H2 (all'indirizzo http://localhost:8080/h2-console), così puoi visualizzare lo stato del database (l'URL jdbc predefinito è — jdbc:h2:mem:testdb).

Inoltre

Leggi anche altri articoli nel nostro blog:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster