Zero Downtime Deployment і базы дадзеных

Zero Downtime Deployment і базы дадзеных

У гэтым артыкуле падрабязна тлумачыцца, як вырашаць праблемы, злучаныя з сумяшчальнасцю баз дадзеных пры дэплоі. Мы раскажам, што можа адбыцца з вашымі праграмамі на продзе, калі вы паспрабуеце выканаць дэплой без папярэдняй падрыхтоўкі. Затым мы пройдземся па этапах жыццёвага цыкла прыкладання, якія неабходныя, каб мець нулявое час прастою (заўв. зав.: далей - 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 дэплою?

Што ж, у іх даволі шмат агульнага, паколькі падтрымка двух копій аднаго і таго ж асяроддзя патрабуе падвойных намаганняў для іх абслугоўвання. Вось чаму некаторыя каманды, як сцвярджае Марцін Фаулер, прытрымліваюцца варыяцыі гэтага падыходу:

іншы варыянт складаецца ў выкарыстанні той жа БД, ствараючы сіне-зялёныя перамыкачы для 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, вы атрымліваеце 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.

крокі:

  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

Дадаць каментар