Զրոյական պարապուրդի տեղակայում և տվյալների բազաներ

Զրոյական պարապուրդի տեղակայում և տվյալների բազաներ

Այս հոդվածը մանրամասնորեն բացատրում է, թե ինչպես լուծել տվյալների բազայի համատեղելիության խնդիրները տեղակայման ժամանակ: Մենք ձեզ կասենք, թե ինչ կարող է պատահել ձեր արտադրական հավելվածների հետ, եթե փորձեք տեղակայել առանց նախնական նախապատրաստման: Այնուհետև մենք կանցնենք հավելվածի կյանքի ցիկլի փուլերը, որոնք անհրաժեշտ են զրոյական պարապուրդի համար (մոտ. գոտի. հետագա - զրոյական պարապուրդ). Մեր գործողությունների արդյունքը կլինի հետընթաց-անհամատեղելի տվյալների բազայի փոփոխությունը հետհամատեղելի եղանակով կիրառելը:

Եթե ​​ցանկանում եք հասկանալ կոդերի օրինակները հոդվածից, կարող եք գտնել դրանք այստեղ GitHub.

Ներածություն

Զրոյական պարապուրդի տեղակայում

Ինչպիսի միստիկ զրոյական պարապուրդի տեղակայում? Կարելի է ասել, որ սա այն դեպքում, երբ ձեր հավելվածը տեղադրվում է այնպես, որ դուք կարող եք հաջողությամբ ներկայացնել հավելվածի նոր տարբերակը արտադրության մեջ, մինչդեռ օգտագործողը չի նկատում դրա անհասանելիությունը: Օգտատիրոջ և ընկերության տեսանկյունից սա տեղակայման հնարավոր լավագույն սցենարն է, քանի որ այն թույլ է տալիս ներդնել նոր հնարավորություններ և շտկել սխալներն առանց խափանումների:

Ինչպե՞ս հասնել դրան: Կան մի քանի եղանակներ, ահա դրանցից մեկը.

  • տեղակայեք ձեր ծառայության թիվ 1 տարբերակը
  • կատարել տվյալների բազայի միգրացիա
  • Տեղադրեք ձեր ծառայության թիվ 2 տարբերակը թիվ 1 տարբերակին զուգահեռ
  • հենց որ տեսնեք, որ թիվ 2 տարբերակը աշխատում է այնպես, ինչպես պետք է, հեռացրեք թիվ 1 տարբերակը
  • պատրաստ!

Հեշտ է, այնպես չէ՞: Ցավոք, դա այնքան էլ պարզ չէ, և մենք ավելի ուշ կանդրադառնանք դրան: Այժմ եկեք ստուգենք մեկ այլ բավականին տարածված տեղակայման գործընթաց՝ կապույտ կանաչ տեղակայումը:

Երբևէ լսե՞լ եք դրա մասին կապույտ կանաչ տեղակայում? Cloud Foundry-ն դա չափազանց հեշտ է դարձնում: Պարզապես նայեք Այս հոդվածը, որտեղ մենք ավելի մանրամասն նկարագրում ենք սա: Համառոտ ամփոփելու համար հիշեցնենք, թե ինչպես անել կապույտ կանաչ տեղակայումը.

  • համոզվեք, որ ձեր արտադրության կոդի երկու օրինակ («կապույտ» և «կանաչ») աշխատում են.
  • ուղղեք ամբողջ երթևեկությունը դեպի կապույտ միջավայր, այսինքն. այնպես, որ արտադրության URL-ները մատնանշեն այնտեղ;
  • տեղակայել և փորձարկել հավելվածի բոլոր փոփոխությունները կանաչ միջավայրում.
  • միացնել urls-ը կապույտից կանաչ միջավայրի

Կապույտ կանաչ տեղակայումը մոտեցում է, որը թույլ է տալիս հեշտությամբ ներդնել նոր հնարավորություններ՝ չանհանգստանալով արտադրության խզման մասին: Դա պայմանավորված է այն հանգամանքով, որ նույնիսկ եթե ինչ-որ բան պատահի, դուք կարող եք հեշտությամբ հետ գլորվել նախկին միջավայր՝ պարզապես «անջատիչը սեղմելով»:

Վերը նշված բոլորը կարդալուց հետո դուք կարող եք հարց տալ. Ի՞նչ կապ ունի զրոյական պարապուրդը Կապույտ կանաչ տեղակայման հետ:

Դե, նրանք բավականին շատ ընդհանրություններ ունեն, քանի որ նույն միջավայրի երկու օրինակի պահպանումը կրկնակի ջանք է պահանջում դրանք պահպանելու համար: Ահա թե ինչու են որոշ թիմեր պնդում Մարտին Ֆաուլեր, հետևեք այս մոտեցման տարբերակին.

Մեկ այլ տարբերակ է օգտագործել նույն տվյալների բազան, ստեղծելով կապույտ-կանաչ անջատիչներ վեբի և տիրույթի շերտերի համար: Այս մոտեցման դեպքում տվյալների բազան հաճախ կարող է խնդիր լինել, հատկապես, երբ դուք պետք է փոխեք դրա սխեման՝ ծրագրաշարի նոր տարբերակն աջակցելու համար:

Եվ ահա մենք գալիս ենք այս հոդվածի հիմնական խնդրին: Նյութերի բազա. Եկեք ևս մեկ նայենք այս արտահայտությանը.

կատարել տվյալների բազայի միգրացիա:

Այժմ դուք պետք է ինքներդ ձեզ հարց տաք. իսկ եթե տվյալների շտեմարանի փոփոխությունը հետընթաց համատեղելի չէ: Հավելվածի իմ առաջին տարբերակը չի՞ ընդհատվի: Իրականում հենց սա է լինելու...

Այսպիսով, նույնիսկ չնայած զրոյական պարապուրդի/կապույտ կանաչ տեղակայման հսկայական առավելություններին, ընկերությունները հակված են հետևել իրենց հավելվածների տեղակայման հետևյալ ավելի անվտանգ գործընթացին.

  • պատրաստել փաթեթ հավելվածի նոր տարբերակով
  • անջատել գործող հավելվածը
  • գործարկել սկրիպտներ՝ տվյալների բազան տեղափոխելու համար
  • տեղակայել և գործարկել հավելվածի նոր տարբերակը

Այս հոդվածում մենք մանրամասն կներկայացնենք, թե ինչպես կարող եք աշխատել ձեր տվյալների բազայի և կոդի հետ՝ օգտվելու զրոյական պարապուրդի տեղակայումից:

Տվյալների բազայի խնդիրներ

Եթե ​​դուք ունեք քաղաքացիություն չունեցող ծրագիր, որը տվյալների բազայում չի պահում որևէ տվյալ, կարող եք անմիջապես ստանալ զրոյական պարապուրդի տեղակայում: Ցավոք, ծրագրերի մեծ մասը պետք է ինչ-որ տեղ պահի տվյալներ: Ահա թե ինչու դուք պետք է երկու անգամ մտածեք շղթայում որևէ փոփոխություն կատարելուց առաջ: Նախքան մանրամասներին ծանոթանալը, թե ինչպես փոխել սխեման այնպես, որ հնարավոր լինի առանց ընդհատումների տեղակայումը, եկեք նախ կենտրոնանանք տարբերակման սխեմայի վրա:

Տարբերակման սխեմա

Այս հոդվածում մենք կօգտագործենք թռիչքուղի որպես տարբերակի վերահսկման գործիք (մոտ. Թարգմանություն՝ խոսքը տվյալների բազայի միգրացիայի մասին է) Բնականաբար, մենք նաև կգրենք 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 մեծ առավելություն.

  • դուք առանձնացնում եք տվյալների բազայի փոփոխությունները կոդի փոփոխություններից
  • Տվյալների բազայի միգրացիան տեղի է ունենում ձեր հավելվածի ներդրման հետ մեկտեղ, այսինքն. ձեր տեղակայման գործընթացը պարզեցված է

Տվյալների բազայի խնդիրների վերացում

Հոդվածի հաջորդ բաժնում մենք կկենտրոնանանք տվյալների բազայի փոփոխության երկու մոտեցումների վրա:

  • հետամնաց անհամատեղելիություն
  • հետընթաց համատեղելիություն

Առաջինը կդիտարկվի որպես նախազգուշացում, որ առանց նախնական նախապատրաստման չպետք է կատարեք զրոյական պարապուրդի տեղակայում... Երկրորդն առաջարկում է լուծում, թե ինչպես կարող եք կատարել տեղակայում առանց պարապուրդի և միևնույն ժամանակ պահպանել հետընթաց համատեղելիությունը:

Մեր նախագիծը, որի վրա մենք աշխատելու ենք, կլինի պարզ Spring Boot Flyway հավելված, որն ունի Person с first_name и last_name տվյալների բազայում (մոտ. թարգմանություն: Person սեղան է և զirst_name и last_name - սրանք են դրա դաշտերը). Մենք ուզում ենք վերանվանել last_name в surname.

Ենթադրություններ

Նախքան մանրամասների մեջ մտնելը, կան մի քանի ենթադրություններ, որոնք մենք պետք է անենք մեր դիմումների վերաբերյալ: Հիմնական արդյունքը, որին մենք ցանկանում ենք հասնել, կլինի բավականին պարզ գործընթաց։

Գրառումը. Բիզնես PRO-TIP. Գործընթացների պարզեցումը կարող է ձեզ շատ գումար խնայել աջակցության վրա (որքան շատ մարդ ունենաք ձեր ընկերությունում աշխատող, այնքան ավելի շատ գումար կարող եք խնայել):

Տվյալների բազան հետ վերադարձնելու կարիք չկա

Սա հեշտացնում է տեղակայման գործընթացը (շտեմարանի որոշ հետադարձումներ գրեթե անհնարին են, օրինակ՝ ջնջման հետ վերադարձը): Մենք նախընտրում ենք հետ վերադարձնել միայն հավելվածները։ Այս կերպ, նույնիսկ եթե դուք ունեք տարբեր տվյալների բազաներ (օրինակ, SQL և NoSQL), ձեր տեղակայման խողովակաշարը նույն տեսքը կունենա:

Պետք է ՄԻՇՏ հնարավոր լինի ետ վերադարձնել հավելվածը մեկ տարբերակով (ոչ ավելին)

Հետադարձը պետք է կատարվի միայն անհրաժեշտության դեպքում: Եթե ​​ընթացիկ տարբերակում կա սխալ, որը հեշտությամբ չի շտկվում, մենք պետք է կարողանանք վերադառնալ վերջին աշխատանքային տարբերակին: Մենք ենթադրում ենք, որ այս վերջին աշխատանքային տարբերակը նախորդն է։ Կոդի և տվյալների բազայի համատեղելիության պահպանումը մեկից ավելի տեղադրման համար չափազանց դժվար և թանկ կլինի:

Գրառումը. Ավելի մեծ ընթերցանության համար այս հոդվածում մենք կփոխենք հավելվածի հիմնական տարբերակը:

Քայլ 1. Սկզբնական վիճակ

Appրագրի տարբերակը ՝ 1.0.0
DB տարբերակ. v1

Մեկնաբանություն

Սա կլինի հայտի նախնական վիճակը:

Տվյալների բազայի փոփոխություններ

DB-ն պարունակում է 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');

Կոդի փոփոխությունները

Հավելվածը պահում է Անձի տվյալները 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
                + "]";
    }
}

Սյունակի հետ անհամատեղելի վերանվանում

Եկեք նայենք սյունակի անունը փոխելու օրինակին.

Ուշադրություն. Հետևյալ օրինակը միտումնավոր կխախտի իրերը: Մենք սա ցույց ենք տալիս տվյալների բազայի համատեղելիության խնդիրը ցուցադրելու համար:

Appրագրի տարբերակը ՝ 2.0.0.BAD

DB տարբերակ. v2bad

Մեկնաբանություն

Ներկայիս փոփոխությունները մեզ թույլ չեն տալիս միաժամանակ գործարկել երկու օրինակ (հին և նոր): Այսպիսով, զրոյական պարապուրդի տեղակայումը դժվար կլինի հասնել (եթե ենթադրությունները հաշվի առնվեն, իրականում դա անհնար է):

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 տեղակայում անելուց հետո (մոտ. Հեղինակը հավանաբար նկատի ուներ 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. Ավելացնել ազգանուն

Appրագրի տարբերակը ՝ 2.0.0
DB տարբերակ. v2

Մեկնաբանություն

Նոր սյունակ ավելացնելով և դրա բովանդակությունը պատճենելով՝ մենք ստեղծում ենք տվյալների բազայի հետ համատեղելի փոփոխություններ: Միևնույն ժամանակ, եթե մենք հետ գցենք JAR-ը կամ աշխատենք հին JAR-ը, այն չի փչանա կատարման ընթացքում:

Մենք թողարկում ենք նոր տարբերակ

Քայլեր.

  1. կատարել տվյալների բազայի միգրացիա՝ նոր սյունակ ստեղծելու համար surname. Այժմ ձեր DB տարբերակը v2
  2. պատճենել տվյալները last_name в surname. Խնդրում ենք նկատի ունենալ,որ եթե այս տվյալներից շատ ունեք, պետք է հաշվի առնել խմբաքանակի միգրացիան:
  3. գրեք կոդը, որտեղ դրանք օգտագործվում են ԵՐԿՈՒՍ и նորԻսկ հին սյունակ։ Այժմ ձեր հավելվածի տարբերակը 2.0.0
  4. կարդալ արժեքը սյունակից surname, եթե դա չէ null, կամ լast_name, եթե surname նշված չէ. Դուք կարող եք ջնջել getLastName() կոդից, քանի որ այն դուրս կգա null երբ հետ գլորում եք ձեր դիմումը 3.0.0 դեպի 2.0.0.

Եթե ​​դուք օգտագործում եք Spring Boot Flyway-ը, այս երկու քայլերը կկատարվեն տարբերակի գործարկման ժամանակ 2.0.0 հավելվածներ։ Եթե ​​դուք գործարկում եք տվյալների բազայի տարբերակավորման գործիքը ձեռքով, ապա դա անելու համար դուք պետք է անեք երկու տարբեր բան (նախ թարմացրեք db տարբերակը ձեռքով, ապա գործարկեք նոր հավելվածը):

Կարևոր. Հիշեք, որ նորաստեղծ սյունակը ՉՊԵՏՔ Է լինել ՈՉ ՄԻ ՔՆՆԱՐԿԵԼ. Եթե ​​դուք վերադարձնեք, ապա հին հավելվածը չգիտի նոր սյունակի մասին և չի տեղադրի այն Insert. Բայց եթե ավելացնեք այս սահմանափակումը, ապա ձեր db կլինի 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 տվյալների բազայում չի օգտագործում ազգանվան սյունակը, այլ տարբերակը 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.

Ուշադրություն. Հիշեք, որ Դուք ՉԵՔ ԿԱՐՈՂ ԱՎԵԼԱՑՆԵԼ Ձեր կողմից ավելացված սյունակում ոչ մի 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. past_name-ի հեռացում կոդից

Appրագրի տարբերակը ՝ 3.0.0

DB տարբերակ.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. Հեռացնել ազգանունը տվյալների բազայից

Appրագրի տարբերակը ՝ 4.0.0

DB տարբերակ. 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"}

DB փոփոխություններ

Համեմատաբար 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 լ–ի պատճենն էast_name. (ՆՇՈՒՄ. Այս սյունակը չպետք է ունենա ոչ զրոյական սահմանափակում)
  3. հավելվածի տարբերակի տեղակայում 3.0.0, որը պահպանում է միայն տվյալները surname և կարդում է ազգանունից: Ինչ վերաբերում է տվյալների բազային, ապա վերջին միգրացիան է տեղի ունենում last_name в surname. Նաև սահմանափակում ՈՉ ՄԻ ՔՆՆԱՐԿԵԼ հեռացվել է last_name. Տվյալների բազան այժմ տարբերակում է v3
  4. հավելվածի տարբերակի տեղակայում 4.0.0 - կոդի մեջ փոփոխություններ չեն կատարվել: Տվյալների բազայի տեղակայում v4, որը հեռացնում է last_name. Այստեղ դուք կարող եք ավելացնել ցանկացած բացակայող սահմանափակումներ տվյալների բազայում:

Հետևելով այս մոտեցմանը, դուք միշտ կարող եք հետ գցել մեկ տարբերակ՝ առանց խախտելու տվյալների բազայի/հավելվածի համատեղելիությունը:

Code

Այս հոդվածում օգտագործված բոլոր ծածկագրերը հասանելի են այստեղ 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 կոնսոլը (at http://localhost:8080/h2-console), որպեսզի կարողանաք դիտել տվյալների բազայի կարգավիճակը (լռելյայն jdbc URL-ն է jdbc:h2:mem:testdb).

Բացի

Կարդացեք նաև մեր բլոգի այլ հոդվածներ.

Source: www.habr.com

Добавить комментарий