Сул зогсолтгүй байршуулалт ба мэдээллийн сан

Сул зогсолтгүй байршуулалт ба мэдээллийн сан

Энэ нийтлэлд өгөгдлийн сангийн нийцтэй байдлын асуудлыг хэрхэн шийдвэрлэх талаар дэлгэрэнгүй тайлбарласан болно. Хэрэв та урьдчилсан бэлтгэлгүйгээр ашиглахыг оролдвол таны үйлдвэрлэлийн програмуудад юу тохиолдож болохыг бид танд хэлэх болно. Дараа нь бид тэг зогсолттой байх шаардлагатай програмын амьдралын мөчлөгийн үе шатуудыг давах болно (ойролцоогоор. эгнээ: цаашлаад - тэг зогсолт). Бидний үйл ажиллагааны үр дүн нь өгөгдлийн сангийн хоцрогдсон нийцтэй бус өөрчлөлтийг буцаан нийцтэй байдлаар ашиглах явдал юм.

Хэрэв та нийтлэл дэх кодын жишээг ойлгохыг хүсвэл эндээс олж болно GitHub.

Танилцуулга

Сул зогсолтыг тэглэх

Ямар ид шидийн юм бэ тэг сул зогсолт байршуулалт? Энэ нь таны аппликешныг ашиглалтад оруулахын тулд програмын шинэ хувилбарыг үйлдвэрлэлд амжилттай нэвтрүүлж, хэрэглэгч ашиглах боломжгүй байгааг анзаардаггүй гэж хэлж болно. Хэрэглэгч болон компанийн үүднээс авч үзвэл энэ нь шинэ боломжуудыг нэвтрүүлэх, алдааг тасалдуулахгүйгээр засах боломжийг олгодог тул хамгийн сайн байршуулах хувилбар юм.

Үүнд хэрхэн хүрэх вэ? Хэд хэдэн арга байдаг бөгөөд тэдгээрийн нэг нь энд байна:

  • үйлчилгээнийхээ 1-р хувилбарыг байрлуул
  • өгөгдлийн сангийн шилжилт хөдөлгөөн хийх
  • Үйлчилгээнийхээ 2-р хувилбарыг №1 хувилбартай зэрэгцүүлэн байрлуул
  • 2-р хувилбар хэвийн ажиллаж байгааг хармагцаа 1-р хувилбарыг устгана уу
  • бэлэн!

Амархан, тийм үү? Харамсалтай нь энэ нь тийм ч энгийн зүйл биш бөгөөд бид үүнийг дараа нь нарийвчлан авч үзэх болно. Одоо өөр нэг нийтлэг байршуулах процессыг шалгацгаая - blue green deployment.

Та сонсож байсан уу цэнхэр ногоон байршуулалт? Cloud Foundry нь үүнийг маш хялбар болгодог. Зүгээр л хар энэ нийтлэл, бид үүнийг илүү дэлгэрэнгүй тайлбарлах болно. Товчхондоо цэнхэр ногоон байршуулалтыг хэрхэн хийхийг танд сануулъя:

  • үйлдвэрлэлийн кодын хоёр хувь ("цэнхэр" ба "ногоон") ажиллаж байгаа эсэхийг шалгах;
  • бүх урсгалыг цэнхэр орчинд чиглүүлэх, өөрөөр хэлбэл. ингэснээр үйлдвэрлэлийн URL-ууд тэнд зааж өгнө;
  • програмын бүх өөрчлөлтийг ногоон орчинд байрлуулах, турших;
  • url-ийг цэнхэрээс ногоон орчинд шилжүүлэх

Цэнхэр ногоон байршуулалт нь үйлдвэрлэлийн эвдрэлээс санаа зовохгүйгээр шинэ боломжуудыг хялбархан нэвтрүүлэх боломжийг олгодог арга юм. Энэ нь ямар нэг зүйл тохиолдсон ч та зүгээр л "шилжүүлэгчийг дарахад" өмнөх орчин руугаа амархан эргэлддэгтэй холбоотой юм.

Дээрх бүгдийг уншсаны дараа та дараах асуултыг тавьж болно: Цэнхэр ногоон байршуулалттай тэг зогсолт ямар хамаатай вэ?

Нэг орчны хоёр хуулбарыг хадгалахын тулд тэдгээрийг хадгалахын тулд хоёр дахин их хүчин чармайлт шаардагддаг тул тэд маш их нийтлэг зүйлтэй байдаг. Ийм учраас зарим багууд мэдэгддэг Мартин Фаулер, энэ аргын нэг хувилбарыг дагана уу:

Өөр нэг сонголт бол ижил мэдээллийн санг ашиглах, вэб болон домэйн давхаргад цэнхэр-ногоон шилжүүлэгч үүсгэх явдал юм. Энэ аргын хувьд мэдээллийн сан нь ихэвчлэн асуудал үүсгэдэг, ялангуяа програм хангамжийн шинэ хувилбарыг дэмжихийн тулд түүний схемийг өөрчлөх шаардлагатай үед.

Эндээс бид энэ нийтлэлийн гол асуудалд хүрлээ. Өгөгдлийн сан. Энэ хэллэгийг дахин харцгаая.

өгөгдлийн сангийн шилжилт хөдөлгөөн хийх.

Одоо та өөрөөсөө асуулт асуух хэрэгтэй - хэрэв мэдээллийн баазын өөрчлөлт буцаж тохирохгүй бол яах вэ? Миний програмын анхны хувилбар эвдрэхгүй гэж үү? Ер нь яг ийм л зүйл болох нь дээ...

Тиймээс, тэг зогсолт / цэнхэр ногоон байршуулалт нь асар их ашиг тустай ч гэсэн компаниуд өөрсдийн програмуудыг байрлуулахдаа дараах аюулгүй үйл явцыг дагаж мөрдөх хандлагатай байдаг:

  • програмын шинэ хувилбар бүхий багц бэлтгэх
  • ажиллаж байгаа програмыг хаах
  • өгөгдлийн санг шилжүүлэхийн тулд скриптүүдийг ажиллуул
  • програмын шинэ хувилбарыг байрлуулж, ажиллуулна уу

Энэ нийтлэлд бид өгөгдлийн сан болон кодтой хэрхэн ажиллах талаар дэлгэрэнгүй ярих болно.

Өгөгдлийн сангийн асуудал

Хэрэв танд өгөгдлийн санд ямар ч өгөгдөл хадгалдаггүй, харьяалалгүй програм байгаа бол та тэр даруйдаа тэг зогсолтыг ашиглах боломжтой. Харамсалтай нь ихэнх программ хангамжууд хаа нэгтээ өгөгдөл хадгалах шаардлагатай болдог. Ийм учраас та хэлхээнд өөрчлөлт оруулахаасаа өмнө хоёр удаа бодох хэрэгтэй. Сул зогсолтгүй байршуулах боломжтой байхын тулд схемийг хэрхэн өөрчлөх талаар дэлгэрэнгүй ярихаасаа өмнө эхлээд хувилбарын схемд анхаарлаа хандуулцгаая.

Хувилбарын схем

Энэ нийтлэлд бид ашиглах болно Нислэгийн зам хувилбарын хяналтын хэрэгсэл болгон (ойролцоогоор. Орчуулга: бид мэдээллийн баазын шилжилтийн тухай ярьж байна). Мэдээжийн хэрэг, бид суулгасан Flyway дэмжлэгтэй Spring Boot програмыг бичих бөгөөд програмын контекстийг тохируулах явцад схемийн шилжилтийг хийх болно. 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 том давуу талыг олж авна:

  • та өгөгдлийн сангийн өөрчлөлтийг кодын өөрчлөлтөөс тусгаарлана
  • Өгөгдлийн сангийн шилжилт нь таны програмыг нэвтрүүлэхтэй зэрэгцэн явагддаг, i.e. таны байршуулах үйл явц хялбаршуулсан

Өгөгдлийн сангийн асуудлыг шийдвэрлэх

Өгүүллийн дараагийн хэсэгт бид мэдээллийн баазыг өөрчлөх хоёр аргыг авч үзэх болно.

  • хоцрогдсон үл нийцэх байдал
  • хоцрогдсон нийцтэй байдал

Эхнийх нь урьдчилсан бэлтгэл хийлгүйгээр сул зогсолтгүй байршуулалт хийх ёсгүй гэсэн анхааруулга гэж үзэх болно... Хоёр дахь нь та хэрхэн сул зогсолтгүйгээр байршуулалт хийж, нэгэн зэрэг хоцрогдсон нийцтэй байдлыг хадгалах талаар шийдлийг санал болгож байна.

Бидний ажиллах төсөл маань энгийн Spring Boot Flyway программ байх болно Person с first_name и last_name мэдээллийн санд (ойролцоогоор. орчуулга: Person хүснэгт ба first_name и last_name - эдгээр нь түүний доторх талбарууд юм). Бид нэрээ өөрчлөхийг хүсч байна last_name в surname.

Таамаглал

Нарийвчилсан мэдээлэлд орохын өмнө бидний хэрэглээний талаар хэд хэдэн таамаглал дэвшүүлэх шаардлагатай байна. Бидний хүрэхийг хүсч буй гол үр дүн нь нэлээд энгийн үйл явц байх болно.

Тэмдэглэл. Бизнесийн PRO-TIP. Үйл явцыг хялбарчлах нь танд дэмжлэг үзүүлэхэд маш их мөнгө хэмнэж чадна (таны компанид олон хүн ажиллах тусам илүү их мөнгө хэмнэх боломжтой)!

Өгөгдлийн санг буцаах шаардлагагүй

Энэ нь байршуулах үйл явцыг хялбаршуулдаг (зарим мэдээллийн санг буцаах нь бараг боломжгүй, тухайлбал устгахыг буцаах). Бид зөвхөн програмуудыг буцаахыг илүүд үздэг. Ингэснээр та өөр өгөгдлийн сантай байсан ч (жишээ нь, SQL болон NoSQL) таны байршуулалтын шугам ижил харагдах болно.

Аппликешныг нэг хувилбараар буцаах ҮРГЭЛЖ боломжтой байх ёстой (цаашаа ч үгүй)

Буцах ажиллагааг зөвхөн шаардлагатай үед л хийх ёстой. Хэрэв одоогийн хувилбарт амархан засагдахгүй алдаа байгаа бол бид хамгийн сүүлийн үеийн хувилбар руу буцах боломжтой байх ёстой. Энэ хамгийн сүүлийн үеийн хувилбар нь өмнөх хувилбар гэж бид таамаглаж байна. Код болон мэдээллийн баазын нийцтэй байдлыг нэгээс олон хувилбарт хадгалах нь маш хэцүү бөгөөд үнэтэй байх болно.

Тэмдэглэл. Уншихад илүү хялбар болгохын тулд энэ нийтлэлд бид програмын үндсэн хувилбарыг өөрчлөх болно.

Алхам 1: Анхны төлөв

Апп хувилбар: 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
                + "]";
    }
}

Баганын нэрийг буцаах нь таарахгүй байна

Баганын нэрийг хэрхэн өөрчлөх жишээг харцгаая:

Анхаар. Дараах жишээ нь аливаа зүйлийг санаатайгаар эвдэх болно. Өгөгдлийн сангийн нийцтэй байдлын асуудлыг харуулахын тулд бид үүнийг харуулж байна.

Апп хувилбар: 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 байршуулалт хийхийг оролдсоны дараа (ойролцоогоор. per.: Зохиогч энд 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: Овог нэмнэ үү

Апп хувилбар: 2.0.0
DB хувилбар: v2

сэтгэгдэл

Шинэ багана нэмж, агуулгыг нь хуулж авснаар бид буцаад нийцтэй мэдээллийн сангийн өөрчлөлтийг бий болгодог. Үүний зэрэгцээ, хэрэв бид JAR-г буцаах эсвэл хуучин JAR ажиллаж байгаа бол гүйцэтгэлийн явцад эвдэрч чадахгүй.

Бид шинэ хувилбарыг гаргаж байна

Алхам:

  1. шинэ багана үүсгэхийн тулд мэдээллийн баазын шилжилтийг хийнэ үү surname. Одоо таны DB хувилбар v2
  2. -аас өгөгдлийг хуулах last_name в surname. АнхаарХэрэв танд ийм их мэдээлэл байгаа бол багц шилжүүлэх талаар бодох хэрэгтэй!
  3. тэдгээрийг ашиглаж байгаа кодыг бичнэ үү Хоёулаа и новыйболон хуучин байна багана. Одоо таны програмын хувилбар 2.0.0
  4. баганаас утгыг уншина уу surname, хэрэв тийм биш бол null, эсвэл l-ээс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.

Анхаар. Та нэмж буй баганад NO 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, ба in 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: Кодоос овог нэрийг хасна

Апп хувилбар: 3.0.0

DB хувилбар:v3

сэтгэгдэл

Анхаарна уу per.: Анхны нийтлэлд зохиогч энэ блокийн текстийг 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;

Кодын өөрчлөлт

Анхаарна уу per.: Энэ блокийн тайлбарыг зохиогч 2-р алхамаас андуурч хуулсан байна. Өгүүллийн логикийн дагуу энэ алхам дахь кодын өөрчлөлт нь баганатай ажилладаг элементүүдийг үүнээс хасахад чиглэгдэх ёстой. last_name.

Бид өгөгдлийг дараах байдлаар хадгалдаг last_name, ба in 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: Өгөгдлийн сангаас овог нэрийг устгах

Апп хувилбар: 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 l-ийн хуулбар юмast_name. (ЖИЧ: Энэ баганад null бус хязгаарлалт байх ёсгүй)
  3. програмын хувилбарыг байршуулах 3.0.0, энэ нь зөвхөн өгөгдлийг хадгалдаг surname мөн овог нэрнээс нь уншдаг. Мэдээллийн сангийн хувьд сүүлийн шилжилт хөдөлгөөн явагдаж байна last_name в surname. Мөн хязгаарлалт ҮГҮЙ -аас хасагдсан 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.

Та үзэж болно http://localhost:8080/flyway, скриптүүдийн жагсаалт байна.

Энэ жишээнд мөн H2 консол ( at http://localhost:8080/h2-console) ингэснээр та өгөгдлийн сангийн статусыг харах боломжтой (үндсэн jdbc URL нь jdbc:h2:mem:testdb).

Үүнээс гадна

Мөн манай блог дээрх бусад нийтлэлүүдийг уншина уу:

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх