Zero Downtime Deployment i bazy danych

Zero Downtime Deployment i bazy danych

W tym artykule szczegółowo wyjaśniamy, jak rozwiązywać problemy związane z kompatybilnością baz danych podczas wdrażania. Opowiemy, co może się stać z Twoimi aplikacjami na produkcji, jeśli spróbujesz wykonać wdrożenie bez wcześniejszego przygotowania. Następnie przejdziemy przez etapy cyklu życia aplikacji, które są niezbędne, aby osiągnąć zero downtime (przyp. red.: dalej — zero downtime). Wynikiem naszych operacji będzie zastosowanie cofających zmian bazy danych w sposób wspierający kompatybilność.

Jeśli chcesz zapoznać się z przykładami kodu z artykułu, znajdziesz je na GitHub.

Wprowadzenie

Zero downtime deployment

Czym jest tajemniczy zero downtime deployment? Можно сказать, это когда ваше приложение развернуто так, что вы можете успешно вводить новую версию приложения на продакшн, в то время как пользователь не замечает его недоступности. С точки зрения пользователя и компании, это наилучший из возможных сценариев деплоя, поскольку таким образом можно вводить новые функции и устранять ошибки без перебоев в работе.

Jak to osiągnąć? Istnieje kilka sposobów, oto jeden z nich:

  • wdroż swoją wersję nr 1 usługi
  • wykonaj migrację bazy danych
  • wdroż wersję nr 2 swojej usługi równolegle z wersją nr 1
  • gdy tylko zobaczysz, że wersja nr 2 działa tak, jak należy, usuń wersję nr 1
  • gotowe!

Łatwe, prawda? Niestety, to nie jest takie proste i później omówimy to bardziej szczegółowo. Teraz sprawdźmy jeszcze jeden dość powszechny proces wdrażania — blue green deployment.

Czy kiedykolwiek słyszałeś o blue green deployment? С Cloud Foundry это чрезвычайно легко сделать. Просто гляньте на ten artykuł, w którym opisujemy to bardziej szczegółowo. Krótko podsumowując, przypomnijmy, jak przeprowadzać blue green deployment:

  • zapewnij działanie dwóch kopii swojego kodu produkcyjnego („blue” i „green”);
  • kieruj cały ruch do środowiska blue, tzn. aby adresy URL produkcji wskazywały tam;
  • wdrażaj i testuj wszystkie zmiany aplikacji w środowisku green;
  • przełącz adresy URL z blue na green

Blue green deployment to podejście, które umożliwia łatwe wprowadzanie nowych funkcji, nie martwiąc się o to, że produkcja się zepsuje. Wynika to z faktu, że nawet jeśli coś się wydarzy, możesz łatwo powrócić do poprzedniego środowiska, po prostu „przełączając przycisk”.

Po przeczytaniu powyższego możesz zadać pytanie: Jakie znaczenie zero downtime ma dla Blue green deploymentu?

Cóż, mają wiele wspólnego, ponieważ utrzymanie dwóch kopii tego samego środowiska wymaga podwójnego wysiłku. Dlatego niektóre zespoły, jak twierdzi Martin Fowler, przyjmują wariant tego podejścia:

Innym sposobem jest użycie tej samej bazy danych, tworząc niebiesko-zielone przełączniki dla warstw web i domain. W takim podejściu bazy danych często mogą być problemem, szczególnie gdy trzeba zmienić jej schemat, aby wspierać nową wersję oprogramowania.

I tutaj dochodzimy do głównego problemu w tym artykule. Baza danych.. Przyjrzyjmy się jeszcze raz temu zdaniu.

przeprowadź migrację bazy danych.

Teraz musisz zadać sobie pytanie — co jeśli zmiana bazy danych jest wstecznie niezgodna? Czy moja pierwsza wersja aplikacji nie przestanie działać? W rzeczywistości, właśnie to się stanie…

W ten sposób, mimo ogromnych zalet zero downtime / blue green deployment, firmy tendencjonują do postępowania według bardziej bezpiecznego procesu wdrożenia swoich aplikacji:

  • przygotować pakiet z nową wersją aplikacji
  • wyłączyć uruchomioną aplikację
  • uruchomić skrypty migracji bazy danych
  • wdrożyć i uruchomić nową wersję aplikacji

W tym artykule szczegółowo omówimy, jak możesz pracować z bazą danych i kodem, aby skorzystać z zerowego czasu przestoju przy wdrożeniu.

Problemy z bazą danych

Jeśli masz aplikację stateless, która nie przechowuje żadnych danych w bazie danych, możesz uzyskać zero downtime deployment od razu. Niestety, większość oprogramowania musi gdzieś przechowywać dane. Dlatego należy dwa razy pomyśleć, zanim wprowadzi się jakiekolwiek zmiany w schemacie. Zanim zagłębimy się w szczegóły, jak zmienić schemat w taki sposób, aby możliwe było wdrożenie bez przestoju, skoncentrujmy się najpierw na schemacie zarządzania wersjami.

Schemat zarządzania wersjami

W tym artykule będziemy używać Flyway jako narzędzia do zarządzania wersjami (przyp. red.: chodzi o migracje bazy danych). Oczywiście, napiszemy również aplikację Spring Boot, która ma wbudowane wsparcie dla Flyway i wykona migrację schematu podczas konfigurowania kontekstu aplikacji. Używając Flyway, możesz przechowywać skrypty migracyjne w folderze swoich projektów (domyślnie w classpath:db/migration). Tutaj możesz zobaczyć przykład takich plików migracji

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

W tym przykładzie widzimy 4 scenariusze migracji, które, jeśli nie zostały wcześniej zrealizowane, będą wykonywane jeden po drugim podczas uruchamiania aplikacji. Przyjrzyjmy się jednemu z plików (V1__init.sql) jako przykład.

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

Wszystko samo mówi za siebie: możesz używać SQL, aby określić, jak Twoja baza danych powinna być zmieniona. Aby uzyskać więcej informacji na temat Spring Boot i Flyway, zapoznaj się z Dokumentacją Spring Boot.

Używając narzędzia do zarządzania wersjami z Spring Boot, zyskujesz 2 ogromne korzyści:

  • oddzielasz zmiany w bazie danych od zmian w kodzie
  • migracja bazy danych ma miejsce razem z wdrożeniem Twojej aplikacji, co upraszcza Twój proces wdrożeniowy

Rozwiązywanie problemów z bazą danych

W następnej części artykułu skupimy się na rozważeniu dwóch podejść do zmian w bazie danych.

  • niezgodność wsteczna
  • zgodność wsteczna

Pierwsze zostanie omówione jako ostrzeżenie, że nie należy przeprowadzać wdrożenia bez przestoju bez wcześniejszego przygotowania… Drugie proponuje rozwiązanie, jak przeprowadzić wdrożenie bez przestojów i jednocześnie wprowadzać zgodność wstecz.

Nasz projekt, nad którym będziemy pracować, będzie prostą aplikacją Spring Boot Flyway, która zawiera Person z first_name i last_name w bazie danych (przyp. tłum.: Person jest tabelą, a first_name i last_name to pola w niej). Chcemy zmienić nazwę last_name do surname.

Założenia

Zanim zagłębimy się w szczegóły, musimy określić kilka założeń dotyczących naszych aplikacji. Głównym wynikiem, który chcemy osiągnąć, będzie dość prosty proces.

Uwaga. Wskazówka biznesowa: Uproszczenie procesów może zaoszczędzić Ci dużo pieniędzy na utrzymaniu (im więcej osób pracuje w Twojej firmie, tym więcej pieniędzy możesz zaoszczędzić)!

Nie należy cofać bazy danych

Ułatwia to proces wdrożenia (niektóre cofnięcia bazy danych są praktycznie niemożliwe, na przykład cofnięcie usunięcia). Wolimy cofać tylko aplikacje. Dzięki temu, nawet jeśli masz różne bazy danych (na przykład SQL i NoSQL), Twój proces wdrożenia będzie wyglądał tak samo.

Musi zawsze być możliwość cofnięcia aplikacji o jedną wersję wstecz (nie więcej)

Rollback should only be performed when necessary. If there is an error in the current version that is difficult to fix, we must be able to revert to the last working version. We assume that this last working version is the previous one. Maintaining compatibility for code and the database across more than one rollout would be extremely difficult and costly.

NotatkaFor better readability, in this article we will change the major version of the application.

Step 1: Initial state

Application version: 1.0.0
Database version: v1

Komentarz

This will be the initial state of the application.

Database changes

The database contains 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');

Code changes

The application saves Person data in 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
                + "]";
    }
}

Backwards incompatible column renaming

Let's look at an example of how to change a column name:

Attention. The following example will intentionally lead to a breakage. We show this to demonstrate the database compatibility issue.

Application version: 2.0.0.BAD

Database version: v2bad

Komentarz

The current changes do NOT allow us to run two instances (old and new) simultaneously. Thus, zero downtime deployment would be difficult to achieve (given the assumptions, it is practically impossible).

A/B testing

The current situation is that we have an application version 1.0.0, deployed in production, and the database v1. We need to deploy a second instance of the application, version 2.0.0.BAD, and update the database to v2bad.

Kroki:

  1. a new instance of application version 2.0.0.BAD, which updates the database to v2bad
  2. w bazie danych v2bad the column last_name no longer exists — it has been changed to surname
  3. the database and application update was successful, and some instances are running in 1.0.0, others in 2.0.0.BAD. All are connected to the database v2bad
  4. all instances of version 1.0.0 will start throwing errors because they will try to insert data into the column last_name, which no longer exists
  5. all instances of version 2.0.0.BAD will work without issues

As you can see, if we make backwards incompatible changes to the database and application, A/B testing is impossible.

Application rollback

Let's assume that after attempting to perform A/B deployment (note: the author probably meant A/B testing) we decided that we need to rollback the application to version 1.0.0. Let's say we do not want to rollback the database.

Kroki:

  1. zatrzymujemy instancję aplikacji wersji 2.0.0.BAD
  2. baza danych nadal v2bad
  3. ponieważ wersja 1.0.0 nie rozumie, co to jest surname, zobaczymy błędy
  4. piekło uwolniło się, nie możemy już wrócić

Jak widać, jeśli wprowadzamy niekompatybilne zmiany w bazie danych i aplikacji, nie możemy cofnąć się do poprzedniej wersji.

Logi wykonania skryptu

Scenariusz niekompatybilności wstecznej:

01) Uruchom 1.0.0
02) Poczekaj na uruchomienie aplikacji (1.0.0)
03) Wygeneruj osobę, wysyłając POST localhost:9991/person do wersji 1.0.0
04) Uruchom 2.0.0.BAD
05) Poczekaj na uruchomienie aplikacji (2.0.0.BAD)
06) Wygeneruj osobę, wysyłając POST localhost:9991/person do wersji 1.0.0 <-- to powinno się nie udać
07) Wygeneruj osobę, wysyłając POST localhost:9992/person do wersji 2.0.0.BAD <-- to powinno przejść

Uruchamianie aplikacji w wersji 1.0.0
Wygeneruj osobę w wersji 1.0.0
Wysyłanie posta do 127.0.0.1:9991/person. Oto odpowiedź:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Uruchamianie aplikacji w wersji 2.0.0.BAD
Wygeneruj osobę w wersji 1.0.0
Wysyłanie posta do 127.0.0.1:9991/person. Oto odpowiedź:

curl: (22) Żądany URL zwrócił błąd: 500 Internal Server Error

Wygeneruj osobę w wersji 2.0.0.BAD
Wysyłanie posta do 127.0.0.1:9995/person. Oto odpowiedź:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

Database changes

Skrypt migracji, który zmienia nazwy last_name do surname

Skrypt Flyway źródłowy:

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

Skrypt, który zmienia nazwy last_name.

-- Ta zmiana jest niekompatybilna wstecz - nie możesz testować A/B
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Code changes

Zmieniliśmy nazwę pola lastName na surname.

Zmienianie nazwy kolumny w sposób kompatybilny z wstecz

To najbardziej typowa sytuacja, z którą możemy się spotkać. Musimy wprowadzać zmiany niekompatybilne wstecz. Już udowodniliśmy, że aby wdrożyć bez przestojów, nie powinniśmy po prostu stosować migracji bazy danych bez dodatkowych działań. W tej części artykułu przeprowadzimy 3 wdrożenia aplikacji wraz z migracjami bazy danych, aby osiągnąć pożądany wynik i jednocześnie zachować zgodność z wstecz.

Uwaga. Przypomnijmy, że mamy bazę danych wersji v1. Zawiera kolumny first_name i last_name. Musimy zmienić last_name na surname. Mamy również aplikację wersji 1.0.0, która jeszcze nie używa surname.

Krok 2: Dodajemy surname

Application version: 2.0.0
Database version: v2

Komentarz

Dodając nową kolumnę i kopiując jej zawartość, tworzymy zmiany bazy danych, które są zgodne wstecz. W tym samym czasie, jeśli cofniemy JAR lub mamy działający stary JAR, nie będzie on psuł się podczas wykonywania.

Wydajemy nową wersję

Kroki:

  1. wykonaj migrację bazy danych, aby stworzyć nową kolumnę surname. Teraz twoja baza danych wersji v2
  2. skopiuj dane z last_name do surname. Zwróć uwagę, co jeśli masz dużo tych danych, powinieneś rozważyć migrację wsadową!
  3. napisz kod, w którym używane są OBE i nowy, i stary kolumna. Teraz Twoja aplikacja w wersji 2.0.0
  4. odczytuje wartość z kolumny surname, jeśli nie jest null, lub z last_name, jeśli surname nie jest ustawione. Możesz usunąć getLastName() z kodu, ponieważ będzie ona zwracać null przy cofnięciu Twojej aplikacji do 3.0.0 do 2.0.0.

Jeśli używasz Spring Boot Flyway, te dwa kroki będą wykonane podczas uruchamiania wersji 2.0.0 aplikacji. Jeśli uruchamiasz narzędzie do zarządzania wersjami bazy danych ręcznie, będziesz musiał wykonać dwa różne działania (najpierw ręcznie zaktualizować wersję bazy danych, a następnie wdrożyć nową aplikację).

To istotne. Pamiętaj, że nowo utworzona kolumna NIE MOŻE być NOT NULL. Jeśli dokonujesz cofnięcia, starsza aplikacja nie wie o nowej kolumnie i nie ustawi jej podczas Wstaw. Ale jeśli dodasz to ograniczenie, a Twoja baza danych będzie v2, będzie to wymagało ustawienia wartości nowej kolumny. Co doprowadzi do naruszenia ograniczeń.

To istotne. Powinieneś usunąć metodę getLastName(), ponieważ w wersji 3.0.0 w kodzie nie ma pojęcia kolumny last_name. Oznacza to, że tam zostaną ustawione wartości null. Możesz pozostawić metodę i dodać sprawdzenia na null, ale znacznie lepszym rozwiązaniem będzie upewnienie się, że w logice getSurname() wybrałeś odpowiednią wartość nie-null.

A/B testing

The current situation is that we have an application version 1.0.0, wdrożona na produkcji, a baza danych w v1. Musimy wdrożyć drugi egzemplarz aplikacji w wersji 2.0.0, który zaktualizuje bazę danych do v2.

Kroki:

  1. a new instance of application version 2.0.0, which updates the database to v2
  2. w międzyczasie niektóre zapytania były przetwarzane przez egzemplarze wersji 1.0.0
  3. aktualizacja przebiegła pomyślnie i masz kilka działających egzemplarzy aplikacji wersji 1.0.0 oraz inne wersje 2.0.0. Wszystkie komunikują się z bazą danych w v2
  4. wersja 1.0.0 nie używa w bazie danych kolumny surname, a wersja 2.0.0 używa. Nie przeszkadzają sobie nawzajem i nie powinno być błędów.
  5. wersja 2.0.0 przechowuje dane zarówno w starej, jak i nowej kolumnie, co zapewnia kompatybilność wsteczną

To istotne. Jeśli masz jakiekolwiek zapytania, które zliczają elementy na podstawie wartości ze starej / nowej kolumny, musisz pamiętać, że teraz masz duplikację wartości (najprawdopodobniej nadal migrują). Na przykład, jeśli chcesz policzyć liczbę użytkowników, których nazwisko (cokolwiek to jest za nazwa kolumny) zaczyna się na literę A, to przed zakończeniem migracji danych (oldnowy kolumna) możesz mieć niespójne dane, jeśli wykonasz zapytanie do nowej kolumny.

Application rollback

Obecnie mamy aplikację w wersji 2.0.0 i bazę danych w v2.

Kroki:

  1. cofnij swoją aplikację do wersji 1.0.0.
  2. wersja 1.0.0 nie używa w bazie danych kolumny surname, dlatego wycofanie powinno być udane

Zmiany w DB

Baza danych zawiera kolumnę o nazwie last_name.

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

Skrypt dodawania surname.

Attention. Pamiętaj, że NIE MOŻESZ DODAWAĆ żadnych ograniczeń NOT NULL do dodawanej kolumny. Jeśli cofasz JAR, stara wersja nie ma pojęcia o dodanej kolumnie i automatycznie ustawi ją na NULL. W przypadku istnienia takiego ograniczenia, stara aplikacja po prostu się zepsuje.

-- UWAGA: To pole nie może mieć ograniczenia NOT NULL, ponieważ jeśli wycofasz, stara wersja nie będzie znała tej kolumny
-- i zawsze ustawi ją na NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- ZAKŁADAMY, ŻE JEST TO SZYBKA MIGRACJA - W PRZYPAKU INACZEJ MUSIELIBYŚMY MIGROWAĆ PARTIAMI
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Code changes

Zachowujemy dane zarówno w last_name, jak i w surname. Przy czym odczytujemy z last_name, ponieważ ta kolumna jest najbardziej aktualna. W trakcie wdrażania niektóre zapytania mogły być przetwarzane przez instancję aplikacji, która jeszcze nie została zaktualizowana.

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

Krok 3: Usunięcie last_name z kodu

Application version: 3.0.0

Database version:v3

Komentarz

Przyp. red.: Wygląda na to, że w oryginalnym artykule autor błędnie skopiował tekst z tego bloku z kroku 2. Na tym kroku należy wprowadzić zmiany w kodzie aplikacji, mające na celu usunięcie funkcjonalności, która wykorzystuje kolumnę last_name.

Dodając nową kolumnę i kopiując jej zawartość, stworzyliśmy zmiany w bazie danych zgodne wstecz. Także, jeśli wycofamy JAR lub będziemy mieli działający stary JAR, nie zepsuje się on podczas działania.

Application rollback

Obecnie mamy aplikację wersji 3.0.0 i bazę danych v3. Wersja 3.0.0 nie przechowuje danych w last_name. Oznacza to, że w surname przechowywane są najbardziej aktualne informacje.

Kroki:

  1. cofnij swoją aplikację do wersji 2.0.0.
  2. wersja 2.0.0 używa i last_name i surname.
  3. wersja 2.0.0 weźmie surname, jeśli nie jest nullem, a w przeciwnym razie —last_name

Database changes

Nie ma strukturalnych zmian w DB. Wykonywany jest następujący skrypt, który przeprowadza ostateczną migrację starych danych:

-- ZAKŁADAMY, ŻE JEST TO SZYBKA MIGRACJA - W PRZYPADKU INACZEJ MUSIELIBYŚMY MIGROWAĆ PARTIAMI
-- TAKŻE NIE SPRAWDZAMY, CZY NIE NADWRACAMY ISTNIEJĄCYCH WPISÓW. MUSIELIBYŚMY PORÓWNAĆ
-- WERSJE WPISÓW, ABY UPEWNIĆ SIĘ, ŻE JEŚLI JEST JUŻ WPIS Z WYŻSZYM NUMEREM WERSJI
-- NIE NADWRACAMY GO.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- USUWANIE OGRANICZENIA NOT NULL; W PRZECIWNYM RAZIE BĘDZIESZ PRÓBOWAŁ WSTAWIĆ WARTOŚĆ NULL LAST_NAME
-- Z OGRANICZENIEM NOT NULL.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Code changes

Przyp. red.: Opis tego bloku również został błędnie skopiowany przez autora z kroku 2. Zgodnie z logiką narracyjną artykułu, zmiany w kodzie na tym etapie powinny być skierowane na usunięcie z niego elementów, które operują na kolumnie last_name.

Przechowujemy dane zarówno w last_name, jak i w surname. Ponadto odczytujemy z kolumny last_name, ponieważ jest to najbardziej aktualne. Podczas wdrażania niektóre zapytania mogą być obsługiwane przez instancję, która nie została jeszcze zaktualizowana.

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

Krok 4: Usunięcie last_name z bazy danych

Application version: 4.0.0

Database version: v4

Komentarz

Z powodu, że kod wersji 3.0.0 nie używał kolumny last_name, podczas wykonywania nie wydarzy się nic złego, jeśli wrócimy do 3.0.0 po usunięciu kolumny z bazy danych.

Logi wykonania skryptu

Zrobimy to w następujący sposób:

01) Uruchom 1.0.0
02) Poczekaj na uruchomienie aplikacji (1.0.0)
03) Wygeneruj osobę, wywołując POST localhost:9991/person dla wersji 1.0.0
04) Uruchom 2.0.0
05) Poczekaj na uruchomienie aplikacji (2.0.0)
06) Wygeneruj osobę, wywołując POST localhost:9991/person dla wersji 1.0.0
07) Wygeneruj osobę, wywołując POST localhost:9992/person dla wersji 2.0.0
08) Zatrzymaj aplikację (1.0.0)
09) Uruchom 3.0.0
10) Poczekaj na uruchomienie aplikacji (3.0.0)
11) Wygeneruj osobę, wywołując POST localhost:9992/person dla wersji 2.0.0
12) Wygeneruj osobę, wywołując POST localhost:9993/person dla wersji 3.0.0
13) Zatrzymaj aplikację (3.0.0)
14) Uruchom 4.0.0
15) Poczekaj na uruchomienie aplikacji (4.0.0)
16) Wygeneruj osobę, wywołując POST localhost:9993/person dla wersji 3.0.0
17) Wygeneruj osobę, wywołując POST localhost:9994/person dla wersji 4.0.0

Uruchamianie aplikacji w wersji 1.0.0
Generowanie osoby w wersji 1.0.0
Wysyłanie POST do 127.0.0.1:9991/person. Oto odpowiedź:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Uruchamianie aplikacji w wersji 2.0.0

Generowanie osoby w wersji 1.0.0
Wysyłanie POST do 127.0.0.1:9991/person. Oto odpowiedź:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generowanie osoby w wersji 2.0.0
Wysyłanie POST do 127.0.0.1:9992/person. Oto odpowiedź:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Zatrzymywanie aplikacji 1.0.0

Uruchamianie aplikacji w wersji 3.0.0

Generowanie osoby w wersji 2.0.0
Wysyłanie POST do 127.0.0.1:9992/person. Oto odpowiedź:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generowanie osoby w wersji 3.0.0
Wysyłanie POST do 127.0.0.1:9993/person. Oto odpowiedź:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Zatrzymywanie aplikacji 2.0.0

Uruchamianie aplikacji w wersji 4.0.0

Generowanie osoby w wersji 3.0.0
Wysyłanie POST do 127.0.0.1:9993/person. Oto odpowiedź:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generowanie osoby w wersji 4.0.0
Wysyłanie POST do 127.0.0.1:9994/person. Oto odpowiedź:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

Zmiany w bazie danych

Odnośnie v3 po prostu usuwamy kolumnę last_name i dodajemy brakujące ograniczenia.

-- USUŃ KOLUMNĘ
ALTER TABLE PERSON DROP last_name;

-- DODAJ OGRANICZENIA
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Code changes

Zmiany w kodzie są nieobecne.

Wnioski

Sukcesywnie zastosowaliśmy niekompatybilną zmianę nazwy kolumny z powrotem, przeprowadzając kilka zgodnych wdrożeń. Poniżej podsumowanie wykonanych działań:

  1. wdrożenie aplikacji wersji 1.0.0 z v1 schemat bazy danych (nazwa kolumny = last_name)
  2. wdrożenie aplikacji wersji 2.0.0, które przechowuje dane w last_name i surname. Aplikacja odczytuje z last_name. Baza danych jest w wersji v2, zawierającej kolumny takie jak last_name, jak i nazwisko. nazwisko jest kopią last_name. (UWAGA: ta kolumna nie powinna mieć ograniczenia not null)
  3. wdrożenie aplikacji wersji 3.0.0, która przechowuje dane tylko w surname i odczytuje z nazwiska. Jeśli chodzi o bazę danych, to odbywa się ostatnia migracja last_name do surname. Ponadto ograniczenie NOT NULL zostało usunięte z last_name. Baza danych jest teraz w wersji v3
  4. wdrożenie aplikacji wersji 4.0.0 — w kodzie nie wprowadzane są żadne zmiany. Wdrożenie bazy danych v4, która usuwa last_name. Tutaj możesz dodać wszelkie brakujące ograniczenia w bazie danych.

Podążając tym podejściem, zawsze możesz cofnąć się o jedną wersję wstecz, nie łamiąc kompatybilności bazy danych / aplikacji.

Kod

Cały kod użyty w tym artykule jest dostępny na Podróż, którą przeszedłem, okazała się fascynującą wyprawą w przeszłość i cieszę się, że w końcu znalazłem rozwiązanie. Poza tym:. Poniżej znajduje się dodatkowy opis.

Projekty

Po sklonowaniu repozytorium zobaczysz następującą strukturę folderów.

├── boot-flyway-v1              - wersja 1.0.0 aplikacji z v1 schematu
├── boot-flyway-v2              - wersja 2.0.0 aplikacji z v2 schematu (kompatybilna wstecz - aplikacja może być cofnięta)
├── boot-flyway-v2-bad          - wersja 2.0.0.BAD aplikacji z v2bad schematu (niekompatybilna wstecz - aplikacja nie może być cofnięta)
├── boot-flyway-v3              - wersja 3.0.0 aplikacji z v3 schematu (aplikacja może być cofnięta)
└── boot-flyway-v4              - wersja 4.0.0 aplikacji z v4 schematu (aplikacja może być cofnięta)

Skrypty

Możesz uruchomić skrypty opisane poniżej, które pokazują zmiany w bazie danych kompatybilne i niekompatybilne wstecz.

Aby zobaczyć przypadek ze zmianami kompatybilnymi wstecz, uruchom:

.\/scripts\/scenario_backward_compatible.sh

Aby zobaczyć przypadek ze zmianami niekompatybilnymi wstecz, uruchom:

.\/scripts\/scenario_backward_incompatible.sh

Przykład Spring Boot Flyway

Wszystkie przykłady pochodzą z Przykład Spring Boot Flyway.

Możesz spojrzeć na http://localhost:8080/flyway, tam znajduje się lista skryptów.

Ten przykład zawiera również konsolę H2 (pod adresem http://localhost:8080/h2-console), abyś mógł przeglądać stan bazy danych (domyślny adres URL jdbc — jdbc:h2:mem:testdb).

Dodatkowo

Przeczytaj również inne artykuły na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster