Zero Downtime Deployment та бази даних

Zero Downtime Deployment та бази даних

У цій статті докладно пояснюється, як вирішувати проблеми, пов'язані із сумісністю баз даних при депло. Ми розповімо, що може статися з вашими програмами на проді, якщо ви спробуєте виконати депло без попередньої підготовки. Потім ми пройдемося по етапах життєвого циклу програми, які необхідні, щоб мати нульовий час простою (прим. пров.: далі - zero downtime). Результатом наших операцій буде застосування несумісної зміни бази даних назад сумісним способом.

Якщо ви хочете розібратися з прикладами коду зі статті, ви знайдете їх на GitHub.

Запровадження

Zero downtime deployment

Що за містичний нульове розгортання простоїв? Можна сказати, це коли ваша програма розгорнута так, що ви можете успішно вводити нову версію програми на продакшн, в той час як користувач не помічає його недоступність. З погляду користувача та компанії, це найкращий із можливих сценаріїв деплою, оскільки таким чином можна вводити нові функції та усувати помилки без перебоїв у роботі.

Як цього досягти? Є кілька способів, ось один із них:

  • розгорніть версію №1 вашого сервісу
  • здійсніть міграцію БД
  • розгорніть версію № 2 вашого сервісу паралельно із версією № 1
  • як тільки ви побачите, що версія №2 працює як треба, прибирайте версію №1
  • готово!

Легко, чи не так? На жаль, це не так просто, і ми пізніше це розглянемо. А зараз перевіримо ще один досить поширений процес деплою — blue green deployment.

Ви коли-небудь чули про blue green deployment? З Cloud Foundry це надзвичайно легко зробити. Просто гляньте на цю статтю, де ми описуємо це докладніше. Коротко резюмуючи, нагадаємо, як робити blue green deployment:

  • забезпечити роботу двох копій вашого production коду (“blue” та “green”);
  • направити весь трафік у blue середу, тобто. щоб URL-адреси продакшена вказували туди;
  • розгортати та тестувати всі зміни програми у green середовищі;
  • переключити URL-адресу з blue на green середу

Blue green deployment — це підхід, який дозволяє легко вводити нові функції, не переживаючи, що продакшн зламається. Це пов'язано з тим фактом, що навіть якщо щось трапиться, ви можете легко відкотитися на попереднє середовище просто «клацнувши перемикачем».

Прочитавши все перераховане вище, ви можете поставити запитання: Яке відношення zero downtime має до Blue green деплою?

Що ж, у них досить багато спільного, оскільки підтримка двох копій одного й того ж середовища потребує подвійних зусиль для їхнього обслуговування. Ось чому деякі команди, як стверджує Мартін Фаулер, дотримуються варіації цього підходу:

Інший варіант полягає у використанні тієї ж БД, створюючи синьо-зелені перемикачі для web та domain layers. У такому підході бази даних часто можуть бути проблемою, особливо коли вам потрібно змінити її схему для підтримки нової версії програмного забезпечення.

І тут ми підходимо до головної проблеми у цій статті. База даних. Погляньмо ще раз на цю фразу.

здійсніть міграцію БД.

Тепер ви повинні поставити собі питання — що, якщо зміна бази даних несумісна? Хіба моя перша версія програми не зламається? Насправді, саме це і станеться.

Таким чином, навіть незважаючи на величезні переваги zero downtime / blue green deployment, компанії схильні дотримуватися наступного безпечнішого процесу деплою своїх додатків:

  • підготувати пакет з новою версією програми
  • вимкнути запущену програму
  • запустити скрипти для міграції бази даних
  • розгорнути та запустити нову версію програми

У цій статті ми докладно опишемо, як ви можете працювати з базою даних та кодом, щоб скористатися перевагами zero downtime deployment.

Проблеми з базою даних

Якщо у вас є stateless додаток, який не зберігає жодних даних у БД, ви можете отримати zero downtime deployment відразу. На жаль, більша частина програмного забезпечення повинна зберігати дані. Ось чому ви повинні двічі подумати, перш ніж вносити зміни в схему. Перш ніж ми заглибимося в деталі того, як змінити схему таким чином, щоб став можливим деплою без простою, спочатку зосередимося на схемі управління версіями.

Схема керування версіями

У цій статті ми будемо використовувати Пролітний шлях як інструмент для керування версіями (прим. пров.: мова йде про міграцію БД). Звичайно, ми також напишемо програму Spring Boot, яка має вбудовану підтримку Flyway і виконає міграцію схеми під час налаштування контексту програми. При використанні Flyway ви можете зберігати скрипти міграції в папці ваших проектів (за замовчуванням classpath:db/migration). Тут можна побачити приклад таких файлів міграції

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

У цьому прикладі ми бачимо 4 сценарії міграції, які якщо вони не були виконані раніше, будуть виконуватися один за одним при запуску програми. Давайте розглянемо один із файлів (V1__init.sql) в якості прикладу.

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

Все чудово говорить саме за себе: ви можете використовувати SQL, щоб визначити, як ваша база даних має бути змінена. Для отримання додаткової інформації про Spring Boot та Flyway ознайомтесь з Spring Boot Docs.

Використовуючи інструмент управління версіями зі Spring Boot, ви отримуєте дві великі переваги:

  • ви відокремлюєте зміни бази даних від змін коду
  • міграція бази даних відбувається разом із викочуванням вашої програми, тобто. ваш процес деплою спрощується

Вирішення проблем з базою даних

У наступному розділі статті ми зосередимося на розгляді двох підходів до зміни бази даних.

  • зворотна несумісність
  • Зворотня сумістність

Перший буде розглянутий як застереження, що не варто робити zero downtime deployment без попередньої підготовки… Другий пропонує рішення, як можна виконати деплой без простоїв та одночасно підтримувати зворотну сумісність.

Наш проект, над яким ми будемо працювати, буде простою програмою Spring Boot Flyway, в якій є Person с first_name и last_name у базі даних (прим. пров.: Person є таблицею, а first_name и last_name - це поля в ній). Ми хочемо перейменувати last_name в surname.

Припущення

Перш ніж ми заглибимося в деталі, необхідно позначити пару припущень щодо наших додатків. Головним результатом, якого ми хочемо досягти, буде досить простий процес.

Замітка. Business PRO-TIP. Спрощення процесів може заощадити вам багато грошей на підтримці (що більше людей працює у вашій компанії, тим більше грошей ви можете заощадити)!

Не треба робити відкат бази даних

Це спрощує процес деплою (деякі відкати бази даних практично неможливі, наприклад, відкат видалення). Ми воліємо відкочувати лише програми. Таким чином, навіть якщо у вас різні бази даних (наприклад, SQL і NoSQL), ваш deployment pipeline виглядатиме однаково.

Потрібно, щоб ЗАВЖДИ була можливість відкотити програму на одну версію назад (не більше)

Відкат варто проводити лише за потребою. Якщо поточна версія має помилку, яку нелегко усунути, ми повинні мати можливість повернути останню робочу версію. Ми вважаємо, що ця остання робоча версія є попередньою. Підтримка сумісності коду та бази даних більш ніж для однієї викочування було б надзвичайно важкою та дорогою.

замітка. Для більшої читабельності, в рамках цієї статті ми змінюватимемо мажорну версію програми.

Крок 1: Початковий стан

Версія програми: 1.0.0
Версія БД: v1

Коментар

Це буде вихідний стан програми.

Зміни БД

БД містить 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');

Зміни коду

Програма зберігає дані Person в 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
                + "]";
    }
}

Назад несумісне перейменування стовпця

Давайте розглянемо приклад, як змінити ім'я стовпця:

Увага. Наступний приклад навмисно призведе до поломки. Ми показуємо це з метою продемонструвати проблему сумісності бази даних.

Версія програми: 2.0.0.BAD

Версія ДБ: v2bad

Коментар

Поточні зміни НЕ дозволяють нам запускати два екземпляри (старий та новий) одночасно. Таким чином, zero downtime deployment буде важко досяжним (якщо взяти до уваги припущення, це фактично неможливо).

A/B-тестування

Поточна ситуація така, що у нас є програма версії 1.0.0, розгорнуте в проді, і БД v1. Ми повинні розгорнути другий екземпляр програми, версії 2.0.0.BAD, та оновити базу даних до v2bad.

кроки:

  1. розгорнуть новий екземпляр програми версії 2.0.0.BAD, яка оновлює базу даних до v2bad
  2. в базі даних v2bad стовпець last_name більше не існує - його змінили на surname
  3. оновлення бази даних та програми пройшло успішно, і деякі екземпляри працюють у 1.0.0, інші - в 2.0.0.BAD. Усі пов'язані з БД v2bad
  4. всі екземпляри версії 1.0.0 почнуть видавати помилки, тому що вони спробують вставити дані в стовпець last_name, якого більше немає
  5. всі екземпляри версії 2.0.0.BAD працюватимуть без проблем

Як ви бачите, якщо ми робимо назад несумісні зміни БД та програми, A/B тестування неможливе.

Відкат програми

Припустімо, що після спроби виконати A/B deployment (прим. пер.: мабуть, тут автор мав на увазі A/B тестування) ми вирішили, що нам потрібно відкотити додаток до версії 1.0.0. Допустимо, ми не хочемо робити відкат бази даних.

кроки:

  1. ми зупиняємо екземпляр програми версії 2.0.0.BAD
  2. база даних все ще v2bad
  3. тому що версія 1.0.0 не розуміє, що таке surname, ми побачимо помилки
  4. пекло вирвалося на волю, ми більше не можемо повернутися

Як ви бачите, якщо ми робимо назад несумісні зміни БД та програми, ми не можемо відкотитися до попередньої версії.

Логи виконання скрипту

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

Зміни БД

Скрипт міграції, який перейменовує last_name в surname

Початковий Flyway скрипт:

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

Скрипт, який перейменовує last_name.

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

Зміни коду

Ми змінили ім'я поля lastName на surname.

Перейменування стовпця зворотно-сумісним способом

Це найчастіша ситуація, з якою ми можемо зіткнутися. Нам потрібно виконати несумісні зміни. Ми вже довели, що для деплою без простоїв ми просто не повинні застосовувати міграцію бази даних без додаткових дій. У цьому розділі статті ми зробимо 3 деплої програми разом з міграціями бази даних, щоб досягти бажаного результату і зберегти зворотну сумісність.

Замітка. Нагадаємо, що у нас є БД версії v1. Вона містить стовпці first_name и last_name. Ми маємо змінити last_name на surname. У нас також є програма версії 1.0.0, яке поки не використовує surname.

Крок 2: Додаємо surname

Версія програми: 2.0.0
Версія БД: v2

Коментар

Додаючи новий стовпець та копіюючи його вміст, ми створюємо зворотно сумісні зміни БД. У той же час, якщо ми відкотимо JAR або у нас буде старий JAR, що працює, він не зламається під час виконання.

Викочуємо нову версію

кроки:

  1. здійсніть міграцію БД, щоб створити новий стовпець surname. Тепер ваша БД версії v2
  2. скопіюйте дані з last_name в surname. Зверніть увагу, Що якщо у вас багато цих даних, ви повинні розглянути можливість пакетної міграції!
  3. напишіть код, де використовуються ОБИДВА и новий, І старий стовпець. Тепер ваш додаток версії 2.0.0
  4. прочитайте значення зі стовпця surname, якщо воно не null, або з last_name, якщо surname не задано. Ви можете видалити getLastName() з коду, оскільки він видаватиме null при відкаті вашої програми з 3.0.0 до 2.0.0.

Якщо ви використовуєте Spring Boot Flyway, ці два кроки будуть виконані під час старту версії 2.0.0 програми. Якщо ви запускаєте інструмент управління версіями бази даних вручну, вам доведеться зробити для цього дві різні дії (спочатку оновіть версію db вручну, а потім розгорніть нову програму).

Важливо. Пам'ятайте, що новостворений стовпець НЕ ПОВИНЕН бути NOT NULL. Якщо ви робите відкат, старий додаток не знає про новий стовпчик і не встановить його під час Insert. Але якщо ви додасте це обмеження, і ваша БД буде v2, це вимагатиме встановлення значення нового стовпця. Що спричинить порушення обмежень.

Важливо. Вам слід видалити метод getLastName(), оскільки у версії 3.0.0 у коді відсутнє поняття стовпця last_name. Це означає, що там буде встановлено null. Ви можете залишити метод і додати перевірки на nullАле набагато кращим рішенням буде переконатися, що в логіці getSurname() ви вибрали правильне ненульове значення.

A/B-тестування

Поточна ситуація така, що у нас є програма версії 1.0.0, розгорнуте на проді, і БД в v1. Ми повинні розгорнути другий екземпляр програми версії 2.0.0, який оновить базу даних до v2.

кроки:

  1. розгорнуть новий екземпляр програми версії 2.0.0, яка оновлює базу даних до v2
  2. Тим часом, деякі запити оброблялися екземплярами версії. 1.0.0
  3. оновлення пройшло успішно, і у вас є кілька працюючих екземплярів програми версії 1.0.0 та інші версії 2.0.0. Усі спілкуються з БД у v2
  4. версія 1.0.0 не використовує в БД стовпець surname, а версія 2.0.0 використовує. Вони не заважають один одному, і помилок не повинно бути.
  5. версія 2.0.0 зберігає дані як у старому, так і в новому стовпці, що забезпечує зворотну сумісність

Важливо. Якщо у вас є будь-які запити, які підраховують елементи на основі значень зі старого / нового стовпця, ви повинні пам'ятати, що тепер у вас є дублювання значень (скоріше за все, вони все ще мігрують). Наприклад, якщо ви хочете порахувати кількість користувачів, чиє прізвище (хоч би як називався стовпець) починалося з літери A, то до завершення міграції даних (oldnew стовпець) у вас можуть бути неконсистентні дані, якщо ви виконуєте запит до нового стовпця.

Відкат програми

Зараз у нас є програма версії 2.0.0 та база даних у v2.

кроки:

  1. відкотіть ваш додаток до версії 1.0.0.
  2. версія 1.0.0 не використовує у БД стовпець surnameтому відкат повинен бути успішним

Зміни DB

БД містить стовпець з ім'ям last_name.

Вихідний скрипт Flyway:

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

Скрипт додавання surname.

Увага. Пам'ятайте, що НЕ МОЖНА додавати будь-які обмеження NOT NULL в стовпець, що додається. Якщо ви відкочуєте JAR, стара версія не має уявлення про доданий стовпчик, і автоматично задасть йому значення NULL. У разі такого обмеження старий додаток просто зламається.

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

Зміни коду

Ми зберігаємо дані як у last_name, Так і в surname. При цьому читаємо з 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;
    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
                + "]";
    }
}

Крок 3: Видалення last_name з коду

Версія програми: 3.0.0

Версія ДБ:v3

Коментар

Прим. пер.: Ймовірно, у вихідній статті автором помилково було скопійовано текст даного блоку з кроку 2. На цьому кроці мають бути зроблені зміни в коді програми, спрямовані на видалення функціоналу, який використовує стовпець last_name.

Додаючи новий стовпець та копіюючи його вміст, ми створили обернено сумісні зміни БД. Також, якщо ми відкотимо JAR або ми будемо працювати старий JAR, він не зламається під час виконання.

Відкат програми

В даний час у нас є програма версії 3.0.0 та база даних v3. Версія 3.0.0 не зберігає дані в last_name. Це означає, що в surname зберігається найактуальніша інформація.

кроки:

  1. відкотіть ваш додаток до версії 2.0.0.
  2. версія 2.0.0 використовує та last_name и surname.
  3. версія 2.0.0 візьме surnameякщо він не нульовий, а інакше —last_name

Зміни БД

У БД немає структурних змін. Виконується наступний скрипт, який виконує остаточну міграцію старих даних:

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

Зміни коду

Прим. пер.: Опис цього блоку також було помилково скопійовано автором з кроку 2. Відповідно до логіки розповіді статті, зміни в коді на цьому кроці повинні бути спрямовані на видалення з нього елементів, які здійснюють роботу зі стовпцем last_name.

Ми зберігаємо дані як у last_name, Так і в surname. Крім того, ми читаємо зі стовпця 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 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
                + "]";
    }
}

Крок 4: Видалення last_name з БД

Версія програми: 4.0.0

Версія БД: v4

Коментар

Через те, що код версії 3.0.0 не використовував стовпець last_name, під час виконання нічого поганого не станеться, якщо ми відкотимося до 3.0.0 після видалення шпальти з бази даних.

Логи виконання скрипту

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

Зміни ДБ

Щодо v3 ми просто видаляємо стовпець last_name та додаємо відсутні обмеження.

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

Зміни коду

Зміни у коді відсутні.

Висновок

Ми успішно застосували несумісну зміну імені стовпця, виконавши кілька назад сумісних деплоїв. Нижче зведення виконаних дій:

  1. деплой програми версії 1.0.0 с v1 схеми БД (ім'я стовпця = last_name)
  2. деплой програми версії 2.0.0, яке зберігає дані в last_name и surname. Додаток читає з last_name. БД перебуває у версії v2, що містить стовпці як last_name, Так і surname. surname є копією last_name. (ПРИМІТКА: цей стовпець не повинен мати обмеження not null)
  3. деплой програми версії 3.0.0, яке зберігає дані тільки в surname та читає з surname. Щодо БД, то відбувається остання міграція last_name в surname. Також обмеження NOT NULL знімається з last_name. БД зараз у версії v3
  4. деплой програми версії 4.0.0 - У коді не проводиться жодних змін. Деплой бази даних v4яка видаляє last_name. Тут ви можете додати будь-які обмеження в БД.

Наслідуючи цей підхід, ви завжди можете відкотитися на одну версію назад, не ламаючи сумісність бази даних / програми.

Код

Весь код, який використовується в цій статті, доступний на Github. Нижче додатковий опис.

Проекти

Після клонування репозиторію ви побачите наступну структуру папок.

├── 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/scenario_backward_compatible.sh

А щоб побачити випадок із зворотно несумісними змінами, запустіть:

./scripts/scenario_backward_incompatible.sh

Spring Boot Sample Flyway

Усі приклади взяті з Spring Boot Sample Flyway.

Ви можете подивитися на http://localhost:8080/flywayтам список скриптів.

Цей приклад також включає консоль H2 (за адресою http://localhost:8080/h2-console), щоб ви могли переглядати стан бази даних (URL jdbc за замовчуванням) jdbc:h2:mem:testdb).

Додатково

Також читайте інші статті у нашому блозі:

Джерело: habr.com

Додати коментар або відгук