Zbatimi dhe bazat e të dhënave zero për kohën e ndërprerjes

Zbatimi dhe bazat e të dhënave zero për kohën e ndërprerjes

Ky artikull shpjegon në detaje se si të zgjidhen çështjet e përputhshmërisë së bazës së të dhënave në vendosje. Ne do t'ju tregojmë se çfarë mund të ndodhë me aplikacionet tuaja të prodhimit nëse përpiqeni të vendosni pa përgatitje paraprake. Më pas do të kalojmë nëpër fazat e ciklit jetësor të aplikacionit që kërkohet të ketë zero kohë joproduktive (përafërsisht. korsi: më tej - zero joproduktive). Rezultati i operacioneve tona do të jetë aplikimi i ndryshimit të bazës së të dhënave të papajtueshme me prapavijën në një mënyrë të përputhshme me prapavijën.

Nëse dëshironi të kuptoni shembujt e kodit nga artikulli, mund t'i gjeni në GitHub.

Paraqitje

Zbarkim zero joproduktive

ÇfarĂ« mistike vendosje zero joproduktive? Ju mund tĂ« thoni se kjo Ă«shtĂ« kur aplikacioni juaj vendoset nĂ« atĂ« mĂ«nyrĂ« qĂ« ju mund tĂ« prezantoni me sukses njĂ« version tĂ« ri tĂ« aplikacionit nĂ« prodhim, ndĂ«rsa pĂ«rdoruesi nuk e vĂ«ren mosdisponueshmĂ«rinĂ« e tij. Nga perspektiva e pĂ«rdoruesit dhe e kompanisĂ«, ky Ă«shtĂ« skenari mĂ« i mirĂ« i mundshĂ«m i vendosjes sepse lejon futjen e veçorive tĂ« reja dhe rregullimin e gabimeve pa ndĂ«rprerje.

Si të arrihet kjo? Ka disa mënyra, këtu është një prej tyre:

  • vendos versionin nr. 1 tĂ« shĂ«rbimit tuaj
  • kryeni njĂ« migrim tĂ« bazĂ«s sĂ« tĂ« dhĂ«nave
  • Vendosni versionin #2 tĂ« shĂ«rbimit tuaj paralelisht me versionin #1
  • Sapo tĂ« shihni se versioni nr. 2 funksionon siç duhet, hiqni versionin nr. 1
  • gati!

Lehtë, apo jo? Fatkeqësisht, nuk është aq e thjeshtë dhe do ta shikojmë në detaje më vonë. Tani le të kontrollojmë një proces tjetër mjaft të zakonshëm të vendosjes - vendosjen e gjelbër blu.

A keni dëgjuar ndonjëherë për vendosja e gjelbër blu? Cloud Foundry e bën këtë jashtëzakonisht të lehtë. Vetëm shikoni Ky artikull, ku e përshkruajmë këtë në mënyrë më të detajuar. Për ta përmbledhur shkurtimisht, le t'ju kujtojmë se si të bëni vendosjen e gjelbër blu:

  • sigurohuni qĂ« tĂ« funksionojnĂ« dy kopje tĂ« kodit tuaj tĂ« prodhimit (“blu” dhe “jeshile”);
  • drejtoje tĂ« gjithĂ« trafikun nĂ« mjedisin blu, d.m.th. nĂ« mĂ«nyrĂ« qĂ« URL-tĂ« e prodhimit tĂ« tregojnĂ« atje;
  • vendos dhe teston tĂ« gjitha ndryshimet e aplikacionit nĂ« njĂ« mjedis tĂ« gjelbĂ«r;
  • ndĂ«rroni url-tĂ« nga mjedisi blu nĂ« jeshil

Vendosja e gjelbër blu është një qasje që ju lejon të prezantoni lehtësisht veçori të reja pa u shqetësuar për prishjen e prodhimit. Kjo për faktin se edhe nëse ndodh diçka, ju mund të ktheheni lehtësisht në mjedisin e mëparshëm thjesht "duke goditur një çelës".

Pasi tĂ« keni lexuar tĂ« gjitha sa mĂ« sipĂ«r, mund tĂ« bĂ«ni pyetjen: ÇfarĂ« lidhje ka zero joproduktive me vendosjen e gjelbĂ«r blu?

Epo, ato kanë mjaft të përbashkëta, pasi mbajtja e dy kopjeve të të njëjtit mjedis kërkon përpjekje të dyfishtë për t'i ruajtur ato. Kjo është arsyeja pse disa ekipe pretendojnë Martin Fowler, ndiqni një variacion të kësaj qasjeje:

Një tjetër mundësi është të përdorni të njëjtën bazë të dhënash, duke krijuar çelësa blu-jeshile për shtresat e ueb-it dhe të domenit. Në këtë qasje, baza e të dhënave shpesh mund të jetë problem, veçanërisht kur duhet të ndryshoni skemën e saj për të mbështetur një version të ri të softuerit.

Dhe këtu kemi ardhur te problemi kryesor në këtë artikull. bazës së të dhënave. Le t'i hedhim një vështrim tjetër kësaj fraze.

kryeni një migrim të bazës së të dhënave.

Tani duhet t'i bëni vetes pyetjen - çka nëse ndryshimi i bazës së të dhënave nuk është i përputhshëm me të prapme? A nuk do të prishet versioni im i parë i aplikacionit? Në fakt, kjo është pikërisht ajo që do të ndodhë...

Pra, edhe përkundër përfitimeve të mëdha të kohës së ndërprerjes zero / vendosjes së gjelbër blu, kompanitë priren të ndjekin procesin e mëposhtëm më të sigurt për vendosjen e aplikacioneve të tyre:

  • pĂ«rgatitni njĂ« paketĂ« me njĂ« version tĂ« ri tĂ« aplikacionit
  • mbyllni njĂ« aplikacion qĂ« funksionon
  • ekzekutoni skriptet pĂ«r tĂ« migruar bazĂ«n e tĂ« dhĂ«nave
  • vendos dhe lĂ«shon njĂ« version tĂ« ri tĂ« aplikacionit

Në këtë artikull, ne do të detajojmë se si mund të punoni me bazën e të dhënave dhe kodin tuaj për të përfituar nga vendosja zero joproduktive.

Problemet e bazës së të dhënave

Nëse keni një aplikacion pa shtetësi që nuk ruan asnjë të dhënë në bazën e të dhënave, mund të merrni zero vendosje joproduktive menjëherë. Fatkeqësisht, shumica e programeve kompjuterike duhet të ruajnë të dhënat diku. Kjo është arsyeja pse duhet të mendoni dy herë përpara se të bëni ndonjë ndryshim në qark. Përpara se të futemi në detajet se si të ndryshojmë skemën në mënyrë që vendosja pa ndërprerje të jetë e mundur, le të përqendrohemi së pari në skemën e versionimit.

Skema e versionimit

Në këtë artikull do të përdorim Rrugë fluturimi si një mjet kontrolli versioni (përafërsisht. Përkthimi: ne po flasim për migrimet e bazës së të dhënave). Natyrisht, ne do të shkruajmë gjithashtu një aplikacion Spring Boot që ka mbështetje të integruar Flyway dhe do të kryejë migrimin e skemës gjatë konfigurimit të kontekstit të aplikacionit. Kur përdorni Flyway, mund të ruani skriptet e migrimit në dosjen e projekteve tuaja (si parazgjedhje në classpath:db/migration). Këtu mund të shihni një shembull të skedarëve të tillë migrimi

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

Në këtë shembull shohim 4 skripta migrimi që, nëse nuk ekzekutohen më parë, do të ekzekutohen njëri pas tjetrit kur të fillojë aplikacioni. Le të shohim një nga skedarët (V1__init.sql) si nje shembull.

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

Gjithçka është krejtësisht e vetëshpjegueshme: ju mund të përdorni SQL për të përcaktuar se si duhet modifikuar databaza juaj. Për më shumë informacion rreth Spring Boot dhe Flyway, shikoni Dokumentet e nisjes së pranverës.

Duke përdorur një mjet kontrolli burimi me Spring Boot, ju merrni 2 përfitime të mëdha:

  • ju ndani ndryshimet e bazĂ«s sĂ« tĂ« dhĂ«nave nga ndryshimet e kodit
  • Migrimi i bazĂ«s sĂ« tĂ« dhĂ«nave ndodh sĂ« bashku me paraqitjen e aplikacionit tuaj, d.m.th. procesi juaj i vendosjes Ă«shtĂ« thjeshtuar

Zgjidhja e problemeve të bazës së të dhënave

Në pjesën tjetër të artikullit, ne do të përqendrohemi në shikimin e dy qasjeve për ndryshimet e bazës së të dhënave.

  • papajtueshmĂ«ria e prapambetur
  • pĂ«rputhshmĂ«ria e prapambetur

E para do të konsiderohet si një paralajmërim se nuk duhet të kryeni vendosjen e kohës së ndërprerjes zero pa përgatitje paraprake... E dyta ofron një zgjidhje se si mund të kryeni një vendosje pa ndërprerje dhe në të njëjtën kohë të ruani përputhshmërinë e prapambetur.

Projekti ynĂ« pĂ«r tĂ« cilin do tĂ« punojmĂ« do tĂ« jetĂ« njĂ« aplikacion i thjeshtĂ« Spring Boot Flyway qĂ« ka Person с first_name Đž last_name nĂ« bazĂ«n e tĂ« dhĂ«nave (pĂ«rafĂ«rsisht. pĂ«rkthimi: Person Ă«shtĂ« njĂ« tabelĂ« dhe first_name Đž last_name - kĂ«to janĂ« fushat nĂ« tĂ«). Ne duam tĂ« riemĂ«rtojmĂ« last_name ĐČ surname.

Supozimet

Përpara se të futemi në detaje, ka disa supozime që duhet të bëjmë në lidhje me aplikacionet tona. Rezultati kryesor që duam të arrijmë do të jetë një proces mjaft i thjeshtë.

ShĂ«nimi. Biznes PRO-KËSHILLA. Thjeshtimi i proceseve mund t'ju kursejĂ« shumĂ« para nĂ« mbĂ«shtetje (sa mĂ« shumĂ« njerĂ«z tĂ« keni qĂ« punojnĂ« pĂ«r kompaninĂ« tuaj, aq mĂ« shumĂ« para mund tĂ« kurseni)!

Nuk ka nevojë për të rikthyer bazën e të dhënave

Kjo thjeshton procesin e vendosjes (disa rikthime të bazës së të dhënave janë pothuajse të pamundura, siç është rikthimi i fshirjes). Ne preferojmë të rikthejmë vetëm aplikacionet. Në këtë mënyrë, edhe nëse keni baza të të dhënave të ndryshme (për shembull, SQL dhe NoSQL), tubacioni juaj i vendosjes do të duket i njëjtë.

Duhet tĂ« jetĂ« GJITHMONË e mundur qĂ« aplikacioni tĂ« kthehet njĂ« version prapa (jo mĂ« shumĂ«)

Rikthimi duhet të bëhet vetëm kur është e nevojshme. Nëse ka një gabim në versionin aktual që nuk rregullohet lehtë, duhet të jemi në gjendje të kthehemi në versionin më të fundit të punës. Supozojmë se ky version i fundit i punës është ai i mëparshmi. Ruajtja e përputhshmërisë së kodit dhe bazës së të dhënave për më shumë se një paraqitje do të ishte jashtëzakonisht e vështirë dhe e shtrenjtë.

Shënimi. Për lexueshmëri më të madhe, në këtë artikull do të ndryshojmë versionin kryesor të aplikacionit.

Hapi 1: Gjendja fillestare

Versioni i aplikacionit: 1.0.0
Versioni i DB: v1

Koment

Kjo do të jetë gjendja fillestare e aplikacionit.

Ndryshimet e bazës së të dhënave

DB përmban 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');

Ndryshimet e kodit

Aplikacioni ruan të dhënat e personit në 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
                + "]";
    }
}

Riemërtimi i kolonës i papajtueshëm prapa

Le të shohim një shembull se si të ndryshojmë emrin e një kolone:

Vëmendje. Shembulli i mëposhtëm do t'i prishë gjërat qëllimisht. Ne e tregojmë këtë për të demonstruar problemin e përputhshmërisë së bazës së të dhënave.

Versioni i aplikacionit: 2.0.0.BAD

Versioni i DB: v2bad

Koment

Ndryshimet aktuale NUK na lejojnë të ekzekutojmë dy instanca (të vjetra dhe të reja) në të njëjtën kohë. Kështu, vendosja e kohës së ndërprerjes zero do të jetë e vështirë të arrihet (nëse merren parasysh supozimet, në fakt është e pamundur).

Testimi A/B

Situata aktuale është se ne kemi një version aplikacioni 1.0.0, të vendosura në prodhim dhe në bazën e të dhënave v1. Duhet të vendosim një shembull të dytë të aplikacionit, version 2.0.0.BAD, dhe përditësoni bazën e të dhënave në v2bad.

hapa:

  1. vendoset një shembull i ri i aplikacionit të versionit 2.0.0.BADi cili përditëson bazën e të dhënave në v2bad
  2. në bazën e të dhënave v2bad kolonë last_name nuk ekziston më - u ndryshua në surname
  3. Përditësimi i bazës së të dhënave dhe aplikacionit ishte i suksesshëm dhe disa raste janë duke u ekzekutuar 1.0.0, të tjerët - në 2.0.0.BAD. Gjithçka është e lidhur me bazën e të dhënave v2bad
  4. të gjitha rastet e versionit 1.0.0 do të fillojnë të hedhin gabime sepse do të përpiqen të fusin të dhëna në kolonë last_nameqë nuk ekziston më
  5. të gjitha rastet e versionit 2.0.0.BAD do të funksionojë pa probleme

Siç mund ta shihni, nëse bëjmë ndryshime të papajtueshme prapa në bazën e të dhënave dhe aplikacionit, testimi A/B është i pamundur.

Rikthimi i aplikacionit

Le të supozojmë se pasi të përpiqemi të bëjmë vendosjen e A/B (përafërsisht. per.: autori ndoshta ka menduar këtu testimin A/B) vendosëm që duhet ta kthejmë aplikacionin në version 1.0.0. Le të themi se nuk duam të rikthejmë bazën e të dhënave.

hapa:

  1. ne ndalojmë shembullin e aplikacionit të versionit 2.0.0.BAD
  2. baza e të dhënave është ende v2bad
  3. që nga versioni 1.0.0 nuk e kupton se çfarë është surname, do të shohim gabime
  4. ferri ka dalë, nuk mund të kthehemi më

Siç mund ta shihni, nëse bëjmë ndryshime të papajtueshme në bazën e të dhënave dhe aplikacionit, nuk mund të kthehemi në versionin e mëparshëm.

Regjistrat e ekzekutimit të skriptit

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

Ndryshimet e bazës së të dhënave

Skripti i migrimit qĂ« riemĂ«ron last_name ĐČ surname

Burimi i skenarit 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');

Skript që riemëron last_name.

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

Ndryshimet e kodit

Ne kemi ndryshuar emrin e fushës lastName mbi surname.

Riemërtimi i një kolone në një mënyrë të përputhshme me prapavijën

Kjo është situata më e zakonshme që mund të hasim. Ne duhet të bëjmë ndryshime të papajtueshme prapa. Ne kemi vërtetuar tashmë se për vendosjen me kohë jo të ndërprerjes, nuk duhet të aplikojmë thjesht migrimin e bazës së të dhënave pa hapa shtesë. Në këtë pjesë të artikullit, ne do të kryejmë 3 vendosje të aplikacionit së bashku me migrimet e bazës së të dhënave për të arritur rezultatin e dëshiruar duke ruajtur përputhshmërinë e prapambetur.

Shënimi. Kujtojmë që ne kemi një bazë të dhënash versionesh v1. Ai përmban kolona first_name О last_name. Ne duhet të ndryshojmë last_name mbi surname. Kemi edhe versionin e aplikacionit 1.0.0, e cila ende nuk është përdorur surname.

Hapi 2: Shtoni mbiemrin

Versioni i aplikacionit: 2.0.0
Versioni i DB: v2

Koment

Duke shtuar një kolonë të re dhe duke kopjuar përmbajtjen e saj, ne krijojmë ndryshime të pajtueshme në bazën e të dhënave. Në të njëjtën kohë, nëse e rikthejmë JAR-in ose kemi një JAR të vjetër që funksionon, ai nuk do të prishet gjatë ekzekutimit.

Ne po nxjerrim një version të ri

hapa:

  1. kryeni një migrim të bazës së të dhënave për të krijuar një kolonë të re surname. Tani versioni juaj DB v2
  2. kopjoni tĂ« dhĂ«nat nga last_name ĐČ surname. ОбратОтД ĐČĐœĐžĐŒĐ°ĐœĐžĐ”qĂ« nĂ«se keni shumĂ« nga kĂ«to tĂ« dhĂ«na, duhet tĂ« merrni parasysh migrimin e grupeve!
  3. shkruani kodin ku janĂ« pĂ«rdorur TË DYJA Đž i riDhe старыĐč kolonĂ«. Tani versioni i aplikacionit tuaj 2.0.0
  4. lexoni vlerën nga kolona surname, nëse nuk është null, ose nga last_name, nëse surname e paspecifikuar. Ju mund të fshini getLastName() nga kodi, pasi ai do të dalë null kur ktheni aplikacionin tuaj nga 3.0.0 tek 2.0.0.

Nëse jeni duke përdorur Spring Boot Flyway, këto dy hapa do të kryhen gjatë fillimit të versionit 2.0.0 aplikacionet. Nëse e përdorni manualisht mjetin e versionimit të bazës së të dhënave, do t'ju duhet të bëni dy gjëra të ndryshme për ta bërë këtë (së pari përditësoni versionin db manualisht dhe më pas vendosni aplikacionin e ri).

E rëndësishme Mos harroni se kolona e krijuar rishtazi NUK DUHET të jetë JO NULL. Nëse bëni një rikthim, aplikacioni i vjetër nuk di për kolonën e re dhe nuk do ta instalojë atë gjatë Insert. Por nëse shtoni këtë kufizim dhe db juaj do të jetë v2, kjo do të kërkojë vendosjen e vlerës së kolonës së re. Që do të çojë në shkelje të kufizimeve.

E rëndësishme Ju duhet të hiqni metodën getLastName(), sepse në version 3.0.0 Nuk ka asnjë koncept të një kolone në kod last_name. Kjo do të thotë se null do të vendoset atje. Mund të lini metodën dhe të shtoni kontrolle për null, por një zgjidhje shumë më e mirë do të ishte të sigurohesh që në logjikë getSurname() keni zgjedhur vlerën e saktë jo zero.

Testimi A/B

Situata aktuale është se ne kemi një version aplikacioni 1.0.0, i vendosur në prodhim dhe baza e të dhënave në v1. Duhet të vendosim një shembull të dytë të aplikacionit të versionit 2.0.0e cila do të përditësojë bazën e të dhënave në v2.

hapa:

  1. vendoset një shembull i ri i aplikacionit të versionit 2.0.0i cili përditëson bazën e të dhënave në v2
  2. ndërkohë disa kërkesa u përpunuan nga instancat e versionit 1.0.0
  3. përditësimi ishte i suksesshëm dhe ju keni disa raste të ekzekutimit të aplikacionit të versionit 1.0.0 dhe versione të tjera 2.0.0. Të gjithë komunikojnë me bazën e të dhënave në v2
  4. version 1.0.0 nuk përdor kolonën e mbiemrit në bazën e të dhënave, por versionin 2.0.0 përdor. Ata nuk ndërhyjnë me njëri-tjetrin dhe nuk duhet të ketë gabime.
  5. version 2.0.0 ruan të dhënat si në kolonën e vjetër ashtu edhe në atë të re, duke siguruar përputhshmëri të prapambetur

E rĂ«ndĂ«sishme NĂ«se keni ndonjĂ« pyetje qĂ« numĂ«ron artikujt bazuar nĂ« vlerat nga kolona e vjetĂ«r/e re, duhet tĂ« mbani mend se tani keni vlera tĂ« kopjuara (ka shumĂ« tĂ« ngjarĂ« qĂ« ato janĂ« ende duke migruar). PĂ«r shembull, nĂ«se dĂ«shironi tĂ« numĂ«roni numrin e pĂ«rdoruesve, mbiemri i tĂ« cilĂ«ve (sido qĂ« tĂ« quhet kolona) fillon me shkronjĂ«n A, pastaj derisa tĂ« pĂ«rfundojĂ« migrimi i tĂ« dhĂ«nave (old → new kolonĂ«) mund tĂ« keni tĂ« dhĂ«na jokonsistente nĂ«se kĂ«rkoni njĂ« kolonĂ« tĂ« re.

Rikthimi i aplikacionit

Tani kemi versionin e aplikacionit 2.0.0 dhe baza e të dhënave në v2.

hapa:

  1. kthejeni aplikacionin tuaj në version 1.0.0.
  2. version 1.0.0 nuk përdor një kolonë në bazën e të dhënave surname, kështu që rikthimi duhet të jetë i suksesshëm

DB ndryshon

Baza e të dhënave përmban një kolonë me emrin last_name.

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

Shto skript surname.

VĂ«mendje. Mos harroni se ju NUK MUND TË SHTONI asnjĂ« kufizim NOT NULL nĂ« kolonĂ«n qĂ« po shtoni. NĂ«se e ktheni prapa JAR-in, versioni i vjetĂ«r nuk do tĂ« ketĂ« asnjĂ« ide pĂ«r kolonĂ«n e shtuar dhe do ta vendosĂ« automatikisht nĂ« NULL. NĂ«se ekziston njĂ« kufizim i tillĂ«, aplikacioni i vjetĂ«r thjesht do tĂ« prishet.

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

Ndryshimet e kodit

Ne ruajmë të dhënat si last_name, dhe në surname. Në të njëjtën kohë lexojmë nga last_name, pasi kjo kolonë është më e rëndësishme. Gjatë procesit të vendosjes, disa kërkesa mund të jenë përpunuar nga një shembull aplikacioni që nuk është përditësuar ende.

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

Hapi 3: Heqja e mbiemrit nga kodi

Versioni i aplikacionit: 3.0.0

Versioni i DB:v3

Koment

shënim per.: Me sa duket, në artikullin origjinal autori gabimisht kopjoi tekstin e këtij blloku nga hapi 2. Në këtë hap, duhet të bëhen ndryshime në kodin e aplikacionit që synojnë heqjen e funksionalitetit që përdor kolonën last_name.

Duke shtuar një kolonë të re dhe duke kopjuar përmbajtjen e saj, ne krijuam ndryshime të pajtueshme në bazën e të dhënave. Gjithashtu, nëse e kthejmë JAR-in ose kemi një JAR të vjetër që funksionon, ai nuk do të prishet gjatë ekzekutimit.

Rikthimi i aplikacionit

Aktualisht kemi versionin e aplikacionit 3.0.0 dhe bazës së të dhënave v3. Version 3.0.0 nuk ruan të dhënat në last_name. Kjo do të thotë se në surname ruhet informacioni më i përditësuar.

hapa:

  1. kthejeni aplikacionin tuaj në version 2.0.0.
  2. version 2.0.0 përdor dhe last_name О surname.
  3. version 2.0.0 do të marrë surname, nëse nuk është zero, përndryshe -last_name

Ndryshimet e bazës së të dhënave

Nuk ka ndryshime strukturore në bazën e të dhënave. Skripti i mëposhtëm ekzekutohet për të kryer migrimin përfundimtar të të dhënave të vjetra:

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

Ndryshimet e kodit

shënim per.: Përshkrimi i këtij blloku gjithashtu u kopjua gabimisht nga autori nga hapi 2. Në përputhje me logjikën e artikullit, ndryshimet në kod në këtë hap duhet të synojnë heqjen prej tij të elementeve që punojnë me kolonën last_name.

Ne ruajmë të dhënat si last_name, dhe në surname. Për më tepër, lexojmë nga kolona last_name, pasi është më e rëndësishmja. Gjatë procesit të vendosjes, disa kërkesa mund të përpunohen nga një shembull që nuk është përmirësuar ende.

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

Hapi 4: Heqja e mbiemrit nga baza e të dhënave

Versioni i aplikacionit: 4.0.0

Versioni i DB: v4

Koment

Për faktin se kodi i versionit 3.0.0 nuk e përdori kolonën last_name, asgjë e keqe nuk do të ndodhë gjatë ekzekutimit nëse kthehemi përsëri në 3.0.0 pas heqjes së një kolone nga baza e të dhënave.

Regjistrat e ekzekutimit të skriptit

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 ndryshon

për v3 thjesht heqim kolonën last_name dhe shtoni kufizime që mungojnë.

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

Ndryshimet e kodit

Nuk ka ndryshime në kod.

Prodhim

Zbatuam me sukses një ndryshim të emrit të kolonës të papajtueshëm me prapavijë duke kryer disa vendosje të pajtueshme me prapavijën. Më poshtë është një përmbledhje e veprimeve të kryera:

  1. vendosja e versionit të aplikacionit 1.0.0 с v1 skema e bazës së të dhënave (emri i kolonës = last_name)
  2. vendosja e versionit tĂ« aplikacionit 2.0.0, i cili ruan tĂ« dhĂ«nat nĂ« last_name Đž surname. Aplikacioni lexon nga last_name. Baza e tĂ« dhĂ«nave Ă«shtĂ« nĂ« version v2qĂ« pĂ«rmban kolona si last_nameDhe surname. surname Ă«shtĂ« njĂ« kopje e last_name. (SHËNIM: Kjo kolonĂ« nuk duhet tĂ« ketĂ« njĂ« kufizim jo null)
  3. vendosja e versionit tĂ« aplikacionit 3.0.0, i cili ruan vetĂ«m tĂ« dhĂ«nat nĂ« surname dhe lexon nga mbiemri. Sa i pĂ«rket bazĂ«s sĂ« tĂ« dhĂ«nave, migrimi i fundit po ndodh last_name ĐČ surname. Gjithashtu njĂ« kufizim JO NULL hequr nga last_name. Baza e tĂ« dhĂ«nave Ă«shtĂ« tani nĂ« version v3
  4. vendosja e versionit të aplikacionit 4.0.0 - nuk janë bërë ndryshime në kod. Vendosja e bazës së të dhënave v4, e cila heq last_name. Këtu mund të shtoni çdo kufizim që mungon në bazën e të dhënave.

Duke ndjekur këtë qasje, gjithmonë mund të rikthehet një version pa prishur pajtueshmërinë e bazës së të dhënave/aplikacionit.

Kod

I gjithë kodi i përdorur në këtë artikull është i disponueshëm në Github. Më poshtë është një përshkrim shtesë.

projektet

Pas klonimit të depove, do të shihni strukturën e mëposhtme të dosjeve.

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

Skenare

Ju mund të ekzekutoni skriptet e përshkruara në skriptet më poshtë, të cilat do të demonstrojnë ndryshime të përputhshme dhe të papajtueshme në bazën e të dhënave.

Të shikosh rasti me ndryshime të përputhshme prapa, vraponi:

./scripts/scenario_backward_compatible.sh

Dhe për të parë rast me ndryshime të papajtueshme prapa, vraponi:

./scripts/scenario_backward_incompatible.sh

Shembull i çizmeve pranverore Flyway

Të gjithë shembujt janë marrë nga Spring Boot Sample Flyway.

Ju mund t'i hidhni një sy http://localhost:8080/flyway, ekziston një listë skriptesh.

Ky shembull përfshin gjithashtu konsolën H2 (në http://localhost:8080/h2-console) kështu që ju mund të shikoni statusin e bazës së të dhënave (URL e parazgjedhur jdbc është jdbc:h2:mem:testdb).

Përveç kësaj

Lexoni gjithashtu artikuj të tjerë në blogun tonë:

Burimi: www.habr.com

Bleni njĂ« host tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, serverĂ« VPS VDS đŸ”„ Bleni hosting tĂ« besueshĂ«m tĂ« faqeve tĂ« internetit me mbrojtje DDoS, servera VPS VDS | ProHoster