В этой статье подробно объясняется, как решать проблемы, связанные с совместимостью баз данных при деплое. Мы расскажем, что может произойти с вашими приложениями на проде, если вы попытаетесь выполнить деплой без предварительной подготовки. Затем мы пройдемся по этапам жизненного цикла приложения, которые необходимы, чтобы иметь нулевое время простоя (прим. пер.: далее — zero downtime). Результатом наших операций будет применение обратно несовместимого изменения базы данных обратно совместимым способом.
Если вы хотите разобраться с примерами кода из статьи, вы их найдете на GitHub.
Введение
Zero downtime deployment
Что за мистический 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 деплою?
Что ж, у них довольно много общего, поскольку поддержка двух копий одной и той же среды требует двойных усилий для их обслуживания. Вот почему некоторые команды, как утверждает Martin Fowler, придерживаются вариации этого подхода:
другой вариант заключается в использовании той же БД, создавая сине-зеленые переключатели для web и domain layers. В таком подходе базы данных часто могут являться проблемой, особенно когда вам нужно изменить её схему для поддержки новой версии программного обеспечения.
И здесь мы подходим к главной проблеме в этой статье. База данных. Давайте еще раз взглянем на эту фразу.
произведите миграцию БД.
Теперь вы должны задать себе вопрос — что, если изменение базы данных обратно несовместимо? Разве моя первая версия приложения не сломается? На самом деле, именно это и случится...
Таким образом, даже несмотря на огромные преимущества zero downtime / blue green deployment, компании склонны следовать следующему более безопасному процессу деплоя своих приложений:
- подготовить пакет с новой версией приложения
- выключить запущенное приложение
- запустить скрипты для миграции базы данных
- развернуть и запустить новую версию приложения
В этой статье мы подробно опишем, как вы можете работать с базой данных и кодом, чтобы воспользоваться преимуществами zero downtime deployment.
Проблемы с базой данных
Если у вас есть stateless приложение, которое не хранит никаких данных в БД, вы можете получить zero downtime deployment сразу. К сожалению, большая часть программного обеспечения должна где-то хранить данные. Вот почему вы должны дважды подумать, прежде чем вносить какие-либо изменения в схему. Прежде чем мы углубимся в детали того, как изменить схему таким образом, чтобы стал возможным деплой без простоя, давайте сначала сосредоточимся на схеме управления версиями.
Схема управления версиями
В этой статье мы будем использовать Flyway в качестве инструмента для управления версиями (прим. пер.: речь идёт про миграции БД). Естественно, мы также напишем приложение Spring Boot, которое имеет встроенную поддержку Flyway и выполнит миграцию схемы во время настройки контекста приложения. При использовании Flyway вы можете хранить скрипты миграции в папке ваших проектов (по умолчанию в classpath:db/migration
). Здесь вы можете увидеть пример таких файлов миграции
L-- db
L-- migration
+-- V1__init.sql
+-- V2__Add_surname.sql
+-- V3__Final_migration.sql
L-- 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, вы получаете 2 больших преимущества:
- вы отделяете изменения базы данных от изменений кода
- миграция базы данных происходит вместе с выкаткой вашего приложения, т.е. ваш процесс деплоя упрощается
Решение проблем с базой данных
В следующем разделе статьи мы сосредоточимся на рассмотрении двух подходов к изменениям базы данных.
- обратная несовместимость
- обратная совместимость
Первый будет рассмотрен как предостережение, что не стоит производить 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
.
Шаги:
- развёрнут новый экземпляр приложения версии
2.0.0.BAD
, которая обновляет базу данных доv2bad
- в базе данных
v2bad
столбецlast_name
больше не существует — его изменили наsurname
- обновление базы данных и приложения прошло успешно, и некоторые экземпляры работают в
1.0.0
, другие — в2.0.0.BAD
. Все связаны с БДv2bad
- все экземпляры версии
1.0.0
начнут выдавать ошибки, потому что они попытаются вставить данные в столбецlast_name
, которого больше нет - все экземпляры версии
2.0.0.BAD
будут работать без проблем
Как вы видите, если мы делаем обратно несовместимые изменения БД и приложения, A/B тестирование невозможно.
Откат приложения
Давайте предположим, что после попытки выполнить A/B deployment (прим. пер.: вероятно, тут автор имел в виду A/B тестирование) мы решили, что нам нужно откатить приложение до версии 1.0.0.
Допустим, мы не хотим делать откат базы данных.
Шаги:
- мы останавливаем экземпляр приложения версии
2.0.0.BAD
- база данных все еще
v2bad
- так как версия
1.0.0
не понимает, что такоеsurname
, мы увидим ошибки - ад вырвался на свободу, мы больше не можем вернуться
Как вы видите, если мы делаем обратно несовместимые изменения БД и приложения, мы не можем откатиться к предыдущей версии.
Логи исполнения скрипта
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, он не сломается во время исполнения.
Выкатываем новую версию
Шаги:
- произведите миграцию БД, чтобы создать новый столбец
surname
. Теперь ваша БД версииv2
- скопируйте данные из
last_name
вsurname
. Обратите внимание, что если у вас много этих данных, вы должны рассмотреть возможность пакетной миграции! - напишите код, где используются ОБА и новый, и старый столбец. Теперь ваше приложение версии
2.0.0
- прочитайте значение из столбца
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
.
Шаги:
- развёрнут новый экземпляр приложения версии
2.0.0
, которая обновляет базу данных доv2
- тем временем некоторые запросы обрабатывались экземплярами версии
1.0.0
- обновление прошло успешно, и у вас есть несколько работающих экземпляров приложения версии
1.0.0
и остальные версии2.0.0.
Все общаются с БД вv2
- версия
1.0.0
не использует в БД столбец surname, а версия2.0.0
использует. Они не мешают друг другу, и ошибок не должно быть. - версия
2.0.0
сохраняет данные как в старом, так и в новом столбце, что обеспечивает обратную совместимость
Важно. Если у вас есть какие-либо запросы, которые подсчитывают элементы на основе значений из старого / нового столбца, вы должны помнить, что теперь у вас есть дублирование значений (скорее всего, они все еще мигрируют). Например, если вы хотите посчитать количество пользователей, чья фамилия (как бы ни назывался столбец) начиналась с буквыA
, то до завершения миграции данных (old
>new
столбец) у вас могут быть неконсистентные данные, если вы выполняете запрос к новому столбцу.
Откат приложения
Сейчас у нас есть приложение версии 2.0.0
и база данных в v2
.
Шаги:
- откатите ваше приложение до версии
1.0.0
. - версия
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
хранится самая актуальная информация.
Шаги:
- откатите ваше приложение до версии
2.0.0
. - версия
2.0.0
использует иlast_name
иsurname
. - версия
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.0.0
сv1
схемы БД (имя столбца =last_name
) - деплой приложения версии
2.0.0,
которое сохраняет данные вlast_name
иsurname
. Приложение читает изlast_name
. БД находится в версииv2
, содержащей столбцы какlast_name
, так иsurname. surname
является копией last_name
. (ПРИМЕЧАНИЕ: этот столбец не должен иметь ограничение not null) - деплой приложения версии
3.0.0
, которое сохраняет данные только вsurname
и читает из surname. Что касается БД, то происходит последняя миграцияlast_name
вsurname
. Также ограничение NOT NULL снимается сlast_name
. БД сейчас в версииv3
- деплой приложения версии
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)
L-- 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
).
Дополнительно
Также читайте другие статьи в нашем блоге:
- Kubernetes: почему так важно настроить управление ресурсами системы?
- Tekton Pipeline — Kubernetes-нативные pipelines
- Cборка динамических модулей для Nginx
- Введение в Hashicorp Consul’s Kubernetes Авторизацию
- К чему привела миграция с ClickHouse без авторизации на ClickHouse с авторизацией
- Blue-Green Deployment приложений на Spring c веб-сервером Nginx