Déploiement sans temps d'arrêt et bases de données

Déploiement sans temps d'arrêt et bases de données

Cet article explique en détail comment résoudre les problèmes de compatibilité des bases de données lors du déploiement. Nous vous expliquerons ce qui peut arriver à vos applications de production si vous tentez de les déployer sans préparation préalable. Nous passerons ensuite en revue les étapes du cycle de vie des applications qui sont nécessaires pour n'avoir aucun temps d'arrêt (environ. voie : plus loin - zéro temps d'arrêt). Le résultat de nos opérations sera d’appliquer la modification de base de données rétrocompatible de manière rétrocompatible.

Si vous souhaitez comprendre les exemples de code de l'article, vous pouvez les trouver sur GitHub.

introduction

Déploiement sans temps d'arrêt

Quel mystique déploiement sans temps d'arrêt? Vous pouvez dire que c'est à ce moment-là que votre application est déployée de telle manière que vous pouvez introduire avec succès une nouvelle version de l'application en production, sans que l'utilisateur ne remarque son indisponibilité. Du point de vue de l'utilisateur et de l'entreprise, il s'agit du meilleur scénario de déploiement possible car il permet d'introduire de nouvelles fonctionnalités et de corriger des bugs sans interruption.

Comment y parvenir ? Il existe plusieurs façons, en voici une :

  • déployer la version n°1 de votre service
  • effectuer une migration de base de données
  • Déployez la version #2 de votre service en parallèle avec la version #1
  • dès que vous voyez que la version n°2 fonctionne comme il se doit, supprimez la version n°1
  • prêt!

Facile, n'est-ce pas ? Malheureusement, ce n’est pas si simple et nous y reviendrons en détail plus tard. Vérifions maintenant un autre processus de déploiement assez courant : le déploiement bleu-vert.

Avez-vous déjà entendu parler de déploiement bleu vert? Cloud Foundry rend cela extrêmement simple. Il suffit de regarder cet article, où nous décrivons cela plus en détail. Pour résumer brièvement, rappelons comment effectuer un déploiement bleu vert :

  • assurez-vous que deux copies de votre code de production (« bleu » et « vert ») fonctionnent ;
  • diriger tout le trafic vers l'environnement bleu, c'est-à-dire pour que les URL de production pointent là ;
  • déployer et tester toutes les modifications d'application dans un environnement vert ;
  • changer les URL de l'environnement bleu à l'environnement vert

Le déploiement bleu vert est une approche qui vous permet d’introduire facilement de nouvelles fonctionnalités sans vous soucier des interruptions de production. Cela est dû au fait que même si quelque chose se produit, vous pouvez facilement revenir à l'environnement précédent en « appuyant simplement sur un interrupteur ».

Après avoir lu tout ce qui précède, vous vous poserez peut-être la question : qu'est-ce que le zéro temps d'arrêt a à voir avec le déploiement Blue Green ?

Eh bien, ils ont beaucoup en commun, puisque la maintenance de deux copies du même environnement nécessite le double d’efforts pour les maintenir. C'est pourquoi certaines équipes prétendent Martin Fowler, suivez une variante de cette approche :

Une autre option consiste à utiliser la même base de données, en créant des commutateurs bleu-vert pour les couches Web et de domaine. Dans cette approche, la base de données peut souvent poser problème, notamment lorsque vous devez modifier son schéma pour prendre en charge une nouvelle version du logiciel.

Et nous arrivons ici au problème principal de cet article. База данных. Jetons un autre regard sur cette phrase.

effectuer une migration de base de données.

Vous devez maintenant vous poser la question : que se passe-t-il si la modification de la base de données n'est pas rétrocompatible ? Ma première version de l'application ne va-t-elle pas tomber en panne ? En fait, c'est exactement ce qui va se passer...

Ainsi, même malgré les énormes avantages d’un déploiement sans temps d’arrêt/bleu vert, les entreprises ont tendance à suivre le processus plus sûr suivant pour déployer leurs applications :

  • préparer un package avec une nouvelle version de l'application
  • arrêter une application en cours d'exécution
  • exécuter des scripts pour migrer la base de données
  • déployer et lancer une nouvelle version de l'application

Dans cet article, nous détaillerons comment vous pouvez utiliser votre base de données et votre code pour profiter d'un déploiement sans temps d'arrêt.

Problèmes de base de données

Si vous disposez d'une application sans état qui ne stocke aucune donnée dans la base de données, vous pouvez immédiatement obtenir un déploiement sans temps d'arrêt. Malheureusement, la plupart des logiciels doivent stocker les données quelque part. C'est pourquoi vous devez y réfléchir à deux fois avant d'apporter des modifications au circuit. Avant d'entrer dans les détails sur la façon de modifier le schéma afin qu'un déploiement sans temps d'arrêt soit possible, concentrons-nous d'abord sur le schéma de gestion des versions.

Schéma de gestion des versions

Dans cet article, nous utiliserons Voie de migration comme outil de contrôle de version (environ. Traduction : nous parlons de migrations de bases de données). Naturellement, nous écrirons également une application Spring Boot prenant en charge Flyway intégrée et effectuerons la migration du schéma lors de la configuration du contexte de l'application. Lorsque vous utilisez Flyway, vous pouvez stocker les scripts de migration dans votre dossier de projets (par défaut dans classpath:db/migration). Ici vous pouvez voir un exemple de tels fichiers de migration

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

Dans cet exemple, nous voyons 4 scripts de migration qui, s'ils n'ont pas été exécutés auparavant, seront exécutés les uns après les autres au démarrage de l'application. Regardons l'un des fichiers (V1__init.sql) par exemple.

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

Tout est parfaitement explicite : vous pouvez utiliser SQL pour définir comment votre base de données doit être modifiée. Pour plus d'informations sur Spring Boot et Flyway, consultez Documentation Spring Boot.

En utilisant un outil de contrôle de source avec Spring Boot, vous bénéficiez de 2 grands avantages :

  • vous séparez les modifications de la base de données des modifications du code
  • La migration de la base de données s'effectue parallèlement au déploiement de votre application, c'est-à-dire votre processus de déploiement est simplifié

Dépannage des problèmes de base de données

Dans la section suivante de l'article, nous nous concentrerons sur deux approches de modification des bases de données.

  • incompatibilité ascendante
  • rétrocompatibilité

Le premier sera considéré comme un avertissement indiquant que vous ne devez pas effectuer de déploiement sans temps d'arrêt sans préparation préalable... Le second propose une solution sur la façon dont vous pouvez effectuer un déploiement sans temps d'arrêt et en même temps maintenir la compatibilité ascendante.

Notre projet sur lequel nous allons travailler sera une simple application Spring Boot Flyway qui a Person с first_name и last_name dans la base de données (environ. traduction: Person est une table et first_name и last_name - ce sont les champs qu'il contient). Nous voulons renommer last_name в surname.

Hypothèses

Avant d'entrer dans les détails, nous devons formuler quelques hypothèses concernant nos applications. Le principal résultat que nous souhaitons obtenir sera un processus assez simple.

La note. CONSEIL DE PRO pour les entreprises. Simplifier les processus peut vous faire économiser beaucoup d’argent en matière d’assistance (plus vous avez de personnes travaillant pour votre entreprise, plus vous pouvez économiser d’argent) !

Pas besoin de restaurer la base de données

Cela simplifie le processus de déploiement (certaines restaurations de bases de données sont presque impossibles, comme la restauration de suppression). Nous préférons restaurer uniquement les applications. De cette façon, même si vous disposez de bases de données différentes (par exemple, SQL et NoSQL), votre pipeline de déploiement aura le même aspect.

Il doit TOUJOURS être possible de restaurer l'application d'une version en arrière (pas plus)

La restauration ne doit être effectuée que lorsque cela est nécessaire. S'il y a un bug dans la version actuelle qui n'est pas facilement corrigé, nous devrions pouvoir revenir à la dernière version de travail. Nous supposons que cette dernière version fonctionnelle est la précédente. Maintenir la compatibilité du code et des bases de données pour plusieurs déploiements serait extrêmement difficile et coûteux.

Заметка. Pour une plus grande lisibilité, dans cet article nous changerons la version majeure de l'application.

Étape 1 : État initial

Version de l'application: 1.0.0
Version de la base de données : v1

commentaire

Ce sera l'état initial de l'application.

Modifications de la base de données

La base de données contient 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');

Modifications des codes

L'application stocke les données de la personne dans 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
                + "]";
    }
}

Renommer une colonne rétrocompatible

Regardons un exemple de la façon de modifier le nom d'une colonne :

Attention L'exemple suivant va intentionnellement casser des choses. Nous montrons cela pour démontrer le problème de compatibilité des bases de données.

Version de l'application: 2.0.0.BAD

Version de la base de données : v2bad

commentaire

Les changements actuels ne nous permettent PAS d'exécuter deux instances (ancienne et nouvelle) en même temps. Ainsi, un déploiement sans temps d’arrêt sera difficile à réaliser (si des hypothèses sont prises en compte, c’est en fait impossible).

Tests A/B

La situation actuelle est que nous avons une version d'application 1.0.0, déployé en production et en base de données v1. Nous devons déployer une deuxième instance de l'application, version 2.0.0.BAD, et mettez à jour la base de données avec v2bad.

Étapes:

  1. une nouvelle instance de la version application est déployée 2.0.0.BADqui met à jour la base de données pour v2bad
  2. dans la base de données v2bad colonne last_name n'existe plus - il a été remplacé par surname
  3. La mise à jour de la base de données et de l'application a réussi et certaines instances sont en cours d'exécution 1.0.0, d'autres - dans 2.0.0.BAD. Tout est connecté à la base de données v2bad
  4. toutes les instances de la version 1.0.0 commenceront à générer des erreurs car ils essaieront d'insérer des données dans la colonne last_namequi n'existe plus
  5. toutes les instances de la version 2.0.0.BAD fonctionnera sans problème

Comme vous pouvez le constater, si nous apportons des modifications rétrocompatibles à la base de données et à l’application, les tests A/B sont impossibles.

Restauration de l'application

Supposons qu'après avoir essayé d'effectuer un déploiement A/B (environ. per. : l'auteur voulait probablement dire ici des tests A/B) nous avons décidé que nous devions restaurer l'application vers la version 1.0.0. Disons que nous ne voulons pas restaurer la base de données.

Étapes:

  1. on arrête l'instance d'application de version 2.0.0.BAD
  2. la base de données est toujours v2bad
  3. depuis la version 1.0.0 ne comprend pas ce que c'est surname, nous verrons des erreurs
  4. l'enfer s'est déchaîné, nous ne pouvons plus y retourner

Comme vous pouvez le constater, si nous apportons des modifications rétrocompatibles à la base de données et à l'application, nous ne pouvons pas revenir à la version précédente.

Journaux d'exécution de scripts

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

Modifications de la base de données

Script de migration qui renomme last_name в surname

Script source de la voie de migration :

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 qui renomme last_name.

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

Modifications des codes

Nous avons changé le nom du champ lastName sur surname.

Renommer une colonne de manière rétrocompatible

C’est la situation la plus courante que nous pouvons rencontrer. Nous devons apporter des modifications rétrocompatibles. Nous avons déjà prouvé que pour un déploiement sans temps d'arrêt, nous ne devons pas simplement appliquer la migration de bases de données sans étapes supplémentaires. Dans cette section de l'article, nous effectuerons 3 déploiements de l'application ainsi que des migrations de bases de données pour obtenir le résultat souhaité tout en conservant la rétrocompatibilité.

La note. Rappelons que nous avons une base de données de versions v1. Il contient des colonnes first_name и last_name. Nous devons changer last_name sur surname. Nous avons également une version d'application 1.0.0, qui n'est pas encore utilisé surname.

Étape 2 : Ajouter le nom de famille

Version de l'application: 2.0.0
Version de la base de données : v2

commentaire

En ajoutant une nouvelle colonne et en copiant son contenu, nous créons des modifications de base de données rétrocompatibles. Dans le même temps, si nous annulons le JAR ou si un ancien JAR est en cours d'exécution, il ne se brisera pas pendant l'exécution.

Nous déployons une nouvelle version

Étapes:

  1. effectuer une migration de base de données pour créer une nouvelle colonne surname. Maintenant votre version DB v2
  2. copier les données de last_name в surname. Noterque si vous disposez de beaucoup de ces données, vous devriez envisager une migration par lots !
  3. écrire le code là où ils sont utilisés Les deux и nouveauEt le vieux colonne. Maintenant la version de votre application 2.0.0
  4. lire la valeur de la colonne surname, si ce n'est pas le cas null, ou de last_name, si surname non spécifié. Vous pouvez supprimer getLastName() du code, car il affichera null lors de la restauration de votre application à partir de 3.0.0 à 2.0.0.

Si vous utilisez Spring Boot Flyway, ces deux étapes seront effectuées lors du démarrage de la version 2.0.0 applications. Si vous exécutez l'outil de gestion de versions de base de données manuellement, vous devrez faire deux choses différentes pour ce faire (d'abord mettre à jour la version de base de données manuellement, puis déployer la nouvelle application).

Il est important. N'oubliez pas que la colonne nouvellement créée NE PAS быть PAS NUL. Si vous effectuez une restauration, l'ancienne application ne connaît pas la nouvelle colonne et ne l'installera pas pendant Insert. Mais si vous ajoutez cette contrainte et que votre base de données sera v2, cela nécessitera de définir la valeur de la nouvelle colonne. Ce qui entraînera des violations des restrictions.

Il est important. Vous devriez supprimer la méthode getLastName(), car dans la version 3.0.0 Il n'y a pas de notion de colonne dans le code last_name. Cela signifie que null y sera défini. Vous pouvez quitter la méthode et ajouter des contrôles pour null, mais une bien meilleure solution serait de s'assurer que dans la logique getSurname() vous avez sélectionné la bonne valeur non nulle.

Tests A/B

La situation actuelle est que nous avons une version d'application 1.0.0, déployé en production, et la base de données dans v1. Nous devons déployer une deuxième instance de la version application 2.0.0qui mettra à jour la base de données pour v2.

Étapes:

  1. une nouvelle instance de la version application est déployée 2.0.0qui met à jour la base de données pour v2
  2. entre-temps, certaines requêtes étaient traitées par les instances de version 1.0.0
  3. la mise à jour a réussi et vous disposez de plusieurs instances en cours d'exécution de la version de l'application 1.0.0 et d'autres versions 2.0.0. Tout le monde communique avec la base de données dans v2
  4. Version 1.0.0 n'utilise pas la colonne nom de famille dans la base de données, mais la version 2.0.0 les usages. Ils n’interfèrent pas les uns avec les autres et il ne devrait y avoir aucune erreur.
  5. Version 2.0.0 stocke les données dans l'ancienne et la nouvelle colonne, garantissant ainsi une compatibilité ascendante

Il est important. Si vous avez des requêtes qui comptent les éléments en fonction des valeurs de l'ancienne/nouvelle colonne, n'oubliez pas que vous avez maintenant des valeurs en double (il est fort probable qu'elles soient toujours en train de migrer). Par exemple, si vous souhaitez compter le nombre d'utilisateurs dont le nom (quel que soit le nom de la colonne) commence par la lettre A, puis jusqu'à ce que la migration des données soit terminée (oldnew colonne), vous risquez d'avoir des données incohérentes si vous interrogez une nouvelle colonne.

Restauration de l'application

Nous avons maintenant la version de l'application 2.0.0 et base de données dans v2.

Étapes:

  1. restaurer votre application à la version 1.0.0.
  2. Version 1.0.0 n'utilise pas de colonne dans la base de données surname, donc la restauration devrait réussir

Modifications de la base de données

La base de données contient une colonne nommée last_name.

Script source de la voie de migration :

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

Ajouter un script surname.

Attention N'oubliez pas que vous NE POUVEZ AJOUTER aucune contrainte NON NULL à la colonne que vous ajoutez. Si vous annulez le JAR, l'ancienne version n'aura aucune idée de la colonne ajoutée et la définira automatiquement sur NULL. S'il existe une telle limitation, l'ancienne application sera tout simplement interrompue.

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

Modifications des codes

Nous stockons les données comme last_nameet surname. En même temps, nous lisons dans last_name, puisque cette colonne est la plus pertinente. Lors du processus de déploiement, certaines requêtes peuvent avoir été traitées par une instance d'application qui n'a pas encore été mise à jour.

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

Étape 3 : Supprimer last_name du code

Version de l'application: 3.0.0

Version de la base de données :v3

commentaire

Note per. : Apparemment, dans l'article original, l'auteur a copié par erreur le texte de ce bloc de l'étape 2. À cette étape, des modifications doivent être apportées au code de l'application visant à supprimer la fonctionnalité qui utilise la colonne last_name.

En ajoutant une nouvelle colonne et en copiant son contenu, nous avons créé des modifications de base de données rétrocompatibles. De plus, si nous annulons le JAR ou si un ancien JAR est en cours d'exécution, il ne se brisera pas pendant l'exécution.

Restauration de l'application

Nous avons actuellement une version de l'application 3.0.0 et base de données v3... Version 3.0.0 n'enregistre pas les données dans last_name. Cela signifie que dans surname les informations les plus récentes sont stockées.

Étapes:

  1. restaurer votre application à la version 2.0.0.
  2. Version 2.0.0 utilise et last_name и surname.
  3. Version 2.0.0 prendra surname, s'il n'est pas nul, sinon -last_name

Modifications de la base de données

Il n'y a aucun changement structurel dans la base de données. Le script suivant est exécuté pour effectuer la migration finale des anciennes données :

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

Modifications des codes

Note per. : La description de ce bloc a également été copiée par erreur par l'auteur de l'étape 2. Conformément à la logique de l'article, les modifications apportées au code à cette étape devraient viser à en supprimer les éléments qui fonctionnent avec la colonne last_name.

Nous stockons les données comme last_nameet surname. De plus, nous lisons dans la colonne last_name, puisque c'est le plus pertinent. Lors du processus de déploiement, certaines requêtes peuvent être traitées par une instance qui n'a pas encore été mise à niveau.

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

Étape 4 : Supprimer last_name de la base de données

Version de l'application: 4.0.0

Version de la base de données : v4

commentaire

Étant donné que le code de version 3.0.0 je n'ai pas utilisé la colonne last_name, rien de grave ne se produira pendant l'exécution si nous revenons à 3.0.0 après avoir supprimé une colonne de la base de données.

Journaux d'exécution de scripts

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

Modifications de la base de données

Sur v3 nous supprimons simplement la colonne last_name et ajoutez les restrictions manquantes.

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

Modifications des codes

Il n'y a aucun changement dans le code.

conclusion

Nous avons appliqué avec succès un changement de nom de colonne rétrocompatible en effectuant plusieurs déploiements rétrocompatibles. Ci-dessous un récapitulatif des actions réalisées :

  1. déploiement de la version de l'application 1.0.0 с v1 schéma de base de données (nom de colonne = last_name)
  2. déploiement de la version de l'application 2.0.0, qui stocke les données dans last_name и surname. L'application lit à partir de last_name. La base de données est en version v2contenant des colonnes comme last_nameEt surname. surname est une copie de last_name. (REMARQUE : Cette colonne ne doit pas avoir de contrainte non nulle)
  3. déploiement de la version de l'application 3.0.0, qui stocke uniquement les données dans surname et lit à partir du nom de famille. Quant à la base de données, la dernière migration est en cours last_name в surname. Une limite également PAS NUL retiré de last_name. La base de données est maintenant en version v3
  4. déploiement de la version de l'application 4.0.0 - aucune modification n'est apportée au code. Déploiement de base de données v4, ce qui supprime last_name. Ici, vous pouvez ajouter toutes les contraintes manquantes à la base de données.

En suivant cette approche, vous pouvez toujours restaurer une version sans rompre la compatibilité base de données/application.

Code

Tout le code utilisé dans cet article est disponible sur Github. Vous trouverez ci-dessous une description supplémentaire.

Projets

Après avoir cloné le référentiel, vous verrez la structure de dossiers suivante.

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

Vous pouvez exécuter les scripts décrits dans les scripts ci-dessous, qui démontreront les modifications rétrocompatibles et incompatibles apportées à la base de données.

Voir le cas avec des modifications rétrocompatibles, courir:

./scripts/scenario_backward_compatible.sh

Et pour voir cas avec des modifications rétrocompatibles, courir:

./scripts/scenario_backward_incompatible.sh

Exemple de voie de migration Spring Boot

Tous les exemples sont tirés de Spring Boot Sample Flyway.

Vous pouvez jeter un oeil à http://localhost:8080/flyway, il y a une liste de scripts.

Cet exemple inclut également la console H2 (à http://localhost:8080/h2-console) afin que vous puissiez afficher l'état de la base de données (l'URL jdbc par défaut est jdbc:h2:mem:testdb).

en outre

Lisez également d'autres articles sur notre blog:

Source: habr.com

Ajouter un commentaire