Wdrożenie i bazy danych bez przestojów

Wdrożenie i bazy danych bez przestojów

W tym artykule szczegółowo wyjaśniono, jak rozwiązywać problemy ze zgodnością baz danych podczas wdrażania. Powiemy Ci, co może się stać z Twoimi aplikacjami produkcyjnymi, jeśli spróbujesz wdrożyć je bez wstępnego przygotowania. Następnie przejdziemy przez etapy cyklu życia aplikacji, które wymagają zerowego przestoju (około. pas: dalej - zero przestojów). Efektem naszych działań będzie zastosowanie zmiany bazy danych niekompatybilnej wstecz w sposób kompatybilny wstecz.

Jeśli chcesz zrozumieć przykłady kodu z artykułu, możesz je znaleźć pod adresem GitHub.

Wprowadzenie

Wdrożenie bez przestojów

Co za mistyczne wdrożenie bez przestojów? Można powiedzieć, że jest to moment, w którym Twoja aplikacja jest wdrożona w taki sposób, że możesz z sukcesem wprowadzić nową wersję aplikacji na produkcję, a użytkownik nie zauważy jej niedostępności. Z punktu widzenia użytkownika i firmy jest to najlepszy możliwy scenariusz wdrożenia, ponieważ umożliwia wprowadzanie nowych funkcji i naprawianie błędów bez zakłóceń.

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

  • wdróż wersję nr 1 swojej usługi
  • wykonać migrację bazy danych
  • Wdróż wersję nr 2 swojej usługi równolegle z wersją nr 1
  • gdy tylko zobaczysz, że wersja nr 2 działa tak, jak powinna, usuń wersję nr 1
  • gotowy!

Łatwe, prawda? Niestety nie jest to takie proste i przyjrzymy się temu szczegółowo później. Sprawdźmy teraz inny dość powszechny proces wdrażania - wdrożenie niebiesko-zielone.

Czy kiedykolwiek słyszałeś o niebiesko-zielone wdrożenie? Dzięki Cloud Foundry jest to niezwykle łatwe. Po prostu spójrz Ten artykuł, gdzie opisujemy to bardziej szczegółowo. Krótko podsumowując, przypomnijmy jak przeprowadzić wdrożenie niebiesko-zielone:

  • upewnij się, że działają dwie kopie Twojego kodu produkcyjnego („niebieski” i „zielony”);
  • kieruj cały ruch do niebieskiego środowiska, tj. aby produkcyjne adresy URL wskazywały właśnie na nie;
  • wdrażaj i testuj wszystkie zmiany w aplikacjach w ekologicznym środowisku;
  • przełącz adresy URL z niebieskiego na zielone środowisko

Wdrożenie niebiesko-zielone to podejście, które pozwala łatwo wprowadzać nowe funkcje bez obawy o przerwanie produkcji. Dzieje się tak dlatego, że nawet jeśli coś się stanie, można łatwo wrócić do poprzedniego środowiska, po prostu „pstrykając przełącznik”.

Po przeczytaniu wszystkich powyższych informacji możesz zadać pytanie: Co ma wspólnego zerowy przestój w przypadku wdrożenia Blue Green?

Cóż, mają ze sobą sporo wspólnego, ponieważ utrzymanie dwóch kopii tego samego środowiska wymaga podwójnego wysiłku, aby je utrzymać. Dlatego niektóre zespoły tak twierdzą Martina Fowlera, zastosuj odmianę tego podejścia:

Inną opcją jest użycie tej samej bazy danych i utworzenie niebiesko-zielonych przełączników dla warstw sieciowych i domenowych. W takim podejściu baza danych często może stanowić problem, zwłaszcza gdy trzeba zmienić jej schemat w celu obsługi nowej wersji oprogramowania.

I tu dochodzimy do głównego problemu tego artykułu. Baza danych. Przyjrzyjmy się jeszcze raz temu wyrażeniu.

wykonać migrację bazy danych.

Teraz musisz zadać sobie pytanie - co jeśli zmiana w bazie danych nie będzie kompatybilna wstecz? Czy moja pierwsza wersja aplikacji nie ulegnie awarii? W rzeczywistości tak właśnie się stanie...

Tak więc, nawet pomimo ogromnych korzyści płynących z zerowego przestoju / wdrożenia niebiesko-zielonego, firmy zwykle stosują następujący bezpieczniejszy proces wdrażania swoich aplikacji:

  • przygotuj paczkę z nową wersją aplikacji
  • zamknąć działającą aplikację
  • uruchom skrypty w celu migracji bazy danych
  • wdrożyć i uruchomić nową wersję aplikacji

W tym artykule szczegółowo opisano, jak pracować z bazą danych i kodem, aby skorzystać z wdrożenia bez przestojów.

Problemy z bazą danych

Jeśli masz aplikację bezstanową, która nie przechowuje żadnych danych w bazie danych, możesz od razu wdrożyć wdrożenie bez przestojów. Niestety większość oprogramowania musi gdzieś przechowywać dane. Dlatego powinieneś pomyśleć dwa razy zanim dokonasz jakichkolwiek zmian w obwodzie. Zanim przejdziemy do szczegółów zmiany schematu, aby możliwe było wdrożenie bez przestojów, skupmy się najpierw na schemacie wersjonowania.

Schemat wersjonowania

W tym artykule użyjemy Droga przelotowa jako narzędzie kontroli wersji (około. Tłumaczenie: mówimy o migracji baz danych). Naturalnie napiszemy także aplikację Spring Boot, która ma wbudowaną obsługę Flyway i wykona migrację schematu podczas konfigurowania kontekstu aplikacji. Korzystając z Flyway, możesz przechowywać skrypty migracji w folderze projektów (domyślnie w classpath:db/migration). Tutaj możesz zobaczyć przykład takich plików migracyjnych

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

W tym przykładzie widzimy 4 skrypty migracji, które, jeśli nie zostały wcześniej wykonane, zostaną wykonane jeden po drugim podczas uruchamiania aplikacji. Spójrzmy na jeden 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 jest doskonale oczywiste: możesz użyć języka SQL do zdefiniowania sposobu modyfikacji bazy danych. Aby uzyskać więcej informacji na temat Spring Boot i Flyway, sprawdź Dokumentacja dotycząca wiosennych rozruchów.

Używając narzędzia kontroli źródła w Spring Boot, zyskujesz 2 duże korzyści:

  • oddzielasz zmiany w bazie danych od zmian w kodzie
  • Migracja bazy danych następuje wraz z wdrożeniem Twojej aplikacji, tj. proces wdrażania jest uproszczony

Rozwiązywanie problemów z bazami danych

W następnej części artykułu skupimy się na dwóch podejściach do zmian w bazach danych.

  • niekompatybilność wsteczna
  • Kompatybilność wsteczna

Pierwsze zostanie potraktowane jako ostrzeżenie, że nie należy przeprowadzać wdrożenia bez przestojów bez wstępnego przygotowania... Drugie oferuje rozwiązanie, w jaki sposób można przeprowadzić wdrożenie bez przestojów, zachowując jednocześnie kompatybilność wsteczną.

Nasz projekt, nad którym będziemy pracować, będzie prostą aplikacją Spring Boot Flyway, która ma Person с first_name и last_name w bazie danych (około. tłumaczenie: Person jest stołem i first_name и last_name - to są w nim pola). Chcemy zmienić nazwę last_name в surname.

Założenia

Zanim przejdziemy do szczegółów, musimy przyjąć kilka założeń dotyczących naszych aplikacji. Głównym rezultatem, który chcemy osiągnąć, będzie dość prosty proces.

Notatka. Biznes PRO-TIP. Uproszczenie procesów może zaoszczędzić sporo pieniędzy na wsparciu (im więcej osób pracuje w Twojej firmie, tym więcej pieniędzy możesz zaoszczędzić)!

Nie ma potrzeby przywracania bazy danych

Upraszcza to proces wdrażania (przywrócenie niektórych baz danych jest prawie niemożliwe, np. wycofanie usunięcia). Wolimy wycofywać tylko aplikacje. Dzięki temu nawet jeśli masz różne bazy danych (na przykład SQL i NoSQL), potok wdrożenia będzie wyglądał tak samo.

ZAWSZE musi istnieć możliwość wycofania aplikacji o jedną wersję (nigdy więcej)

Wycofywanie powinno odbywać się tylko wtedy, gdy jest to konieczne. Jeśli w aktualnej wersji występuje błąd, którego nie można łatwo naprawić, powinniśmy móc powrócić do najnowszej działającej wersji. Zakładamy, że ta najnowsza działająca wersja jest poprzednią. Utrzymanie zgodności kodu i bazy danych w przypadku więcej niż jednego wdrożenia byłoby niezwykle trudne i kosztowne.

Notatka. Dla większej czytelności w tym artykule zmienimy główną wersję aplikacji.

Krok 1: Stan początkowy

Wersja aplikacji: 1.0.0
Wersja bazy danych: v1

Komentarz

Będzie to początkowy stan aplikacji.

Zmiany w bazie danych

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

Zmiany w kodzie

Aplikacja przechowuje dane osobowe w 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
                + "]";
    }
}

Zmiana nazwy kolumny niekompatybilna wstecz

Spójrzmy na przykład zmiany nazwy kolumny:

Uwaga. Poniższy przykład celowo zepsuje wszystko. Pokazujemy to, aby zademonstrować problem kompatybilności baz danych.

Wersja aplikacji: 2.0.0.BAD

Wersja bazy danych: v2bad

Komentarz

Obecne zmiany NIE pozwalają na jednoczesne uruchomienie dwóch instancji (starej i nowej). Zatem wdrożenie zerowych przestojów będzie trudne do osiągnięcia (przy uwzględnieniu założeń jest to w rzeczywistości niemożliwe).

Testy A/B

Obecna sytuacja jest taka, że ​​mamy wersję aplikacji 1.0.0, wdrożone na produkcji i w bazie danych v1. Musimy wdrożyć drugą instancję aplikacji, wersję 2.0.0.BADi zaktualizuj bazę danych do v2bad.

Kroki:

  1. wdrażana jest nowa instancja wersji aplikacji 2.0.0.BADktóry aktualizuje bazę danych v2bad
  2. w bazie danych v2bad kolumna last_name już nie istnieje - został zmieniony na surname
  3. Aktualizacja bazy danych i aplikacji przebiegła pomyślnie i niektóre instancje są uruchomione 1.0.0, inni - w 2.0.0.BAD. Wszystko jest połączone z bazą danych v2bad
  4. wszystkie wystąpienia danej wersji 1.0.0 zaczną wyrzucać błędy, ponieważ będą próbować wstawić dane do kolumny last_namektórego już nie ma
  5. wszystkie wystąpienia danej wersji 2.0.0.BAD będzie działać bez problemów

Jak widać, jeśli dokonamy niekompatybilnych wstecz zmian w bazie danych i aplikacji, testy A/B nie będą możliwe.

Cofnięcie aplikacji

Załóżmy, że po próbie wdrożenia A/B (około. per.: autor prawdopodobnie miał tutaj na myśli testy A/B) zdecydowaliśmy, że musimy przywrócić aplikację do wersji 1.0.0. Załóżmy, że nie chcemy przywracać bazy danych.

Kroki:

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

Jak widać, jeśli dokonamy niezgodnych wstecz zmian w bazie danych i aplikacji, nie będziemy mogli wrócić do poprzedniej wersji.

Dzienniki wykonywania skryptów

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

Zmiany w bazie danych

Skrypt migracji, który zmienia nazwę last_name в surname

Źródłowy 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 zmieniający nazwę last_name.

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

Zmiany w kodzie

Zmieniliśmy nazwę pola lastName na surname.

Zmiana nazwy kolumny w sposób zgodny wstecz

Jest to najczęstsza sytuacja, z jaką możemy się spotkać. Musimy dokonać zmian niekompatybilnych wstecz. Udowodniliśmy już, że w celu wdrożenia bez przestojów nie powinniśmy po prostu zastosować migracji bazy danych bez dodatkowych kroków. W tej części artykułu wykonamy 3 wdrożenia aplikacji wraz z migracjami baz danych, aby osiągnąć zamierzony efekt przy zachowaniu kompatybilności wstecznej.

Notatka. Przypomnijmy, że mamy bazę danych wersji v1. Zawiera kolumny first_name и last_name. Musimy się zmienić last_name na surname. Mamy również wersję aplikacji 1.0.0, który nie jest jeszcze używany surname.

Krok 2: Dodaj nazwisko

Wersja aplikacji: 2.0.0
Wersja bazy danych: v2

Komentarz

Dodając nową kolumnę i kopiując jej zawartość, tworzymy zmiany w bazie danych kompatybilne wstecz. Jednocześnie, jeśli wycofamy plik JAR lub uruchomimy stary plik JAR, nie ulegnie on uszkodzeniu podczas wykonywania.

Wdrażamy nową wersję

Kroki:

  1. wykonaj migrację bazy danych, aby utworzyć nową kolumnę surname. Teraz twoja wersja DB v2
  2. skopiuj dane z last_name в surname. Zauważyćże jeśli masz dużo tych danych, powinieneś rozważyć migrację wsadową!
  3. napisz kod, w którym są używane ZARÓWNO и nowyi stary kolumna. Teraz Twoja wersja aplikacji 2.0.0
  4. odczytaj wartość z kolumny surname, Jeżeli nie jest nulllub z last_namejeśli surname nieokreślony. Możesz usunąć getLastName() z kodu, ponieważ zostanie wyświetlony null podczas wycofywania aplikacji z 3.0.0 do 2.0.0.

Jeśli używasz Spring Boot Flyway, te dwa kroki zostaną wykonane podczas uruchamiania wersji 2.0.0 Aplikacje. Jeśli ręcznie uruchomisz narzędzie do kontroli wersji bazy danych, będziesz musiał w tym celu wykonać dwie różne czynności (najpierw ręcznie zaktualizować wersję db, a następnie wdrożyć nową aplikację).

Jest to bardzo ważne. Pamiętaj, że nowo utworzona kolumna NIE POWINIENEŚ być NIE JEST ZEREM. Jeśli wykonasz wycofanie, stara aplikacja nie będzie wiedzieć o nowej kolumnie i nie będzie jej instalować Insert. 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ń.

Jest to bardzo ważne. Powinieneś usunąć tę metodę getLastName(), ponieważ w wersji 3.0.0 W kodzie nie ma koncepcji kolumny last_name. Oznacza to, że zostanie tam ustawione null. Możesz opuścić metodę i dodać kontrole dla null, ale znacznie lepszym rozwiązaniem byłoby upewnienie się, że jest to w logice getSurname() wybrałeś poprawną wartość niezerową.

Testy A/B

Obecna sytuacja jest taka, że ​​mamy wersję aplikacji 1.0.0, wdrożony w środowisku produkcyjnym, a baza danych w v1. Musimy wdrożyć drugą instancję wersji aplikacji 2.0.0do którego zaktualizowana zostanie baza danych v2.

Kroki:

  1. wdrażana jest nowa instancja wersji aplikacji 2.0.0który aktualizuje bazę danych v2
  2. w międzyczasie niektóre żądania zostały przetworzone przez instancje wersji 1.0.0
  3. aktualizacja powiodła się i masz wiele uruchomionych wystąpień wersji aplikacji 1.0.0 i inne wersje 2.0.0. Każdy komunikuje się z bazą danych w v2
  4. wersja 1.0.0 nie wykorzystuje w bazie danych kolumny nazwiska, lecz wersję 2.0.0 wykorzystuje. Nie kolidują ze sobą i nie powinno być żadnych błędów.
  5. wersja 2.0.0 przechowuje dane zarówno w starej, jak i nowej kolumnie, zapewniając kompatybilność wsteczną

Jest to bardzo ważne. Jeśli masz zapytania, które liczą elementy na podstawie wartości ze starej/nowej kolumny, pamiętaj, że masz teraz zduplikowane wartości (najprawdopodobniej nadal migrują). Na przykład, jeśli chcesz policzyć liczbę użytkowników, których nazwisko (niezależnie od nazwy kolumny) zaczynało się na literę A, a następnie do zakończenia migracji danych (oldnew kolumna) możesz mieć niespójne dane, jeśli wykonasz zapytanie do nowej kolumny.

Cofnięcie aplikacji

Teraz mamy wersję aplikacji 2.0.0 i baza danych w v2.

Kroki:

  1. przywróć aplikację do wersji 1.0.0.
  2. wersja 1.0.0 nie używa kolumny w bazie danych surname, więc wycofanie powinno zakończyć się sukcesem

Zmiany w DB

Baza danych zawiera kolumnę o nazwie last_name.

Skrypt źródłowy 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');

Dodaj skrypt surname.

Uwaga. Pamiętaj, że NIE MOŻESZ DODAĆ żadnych ograniczeń NOT NULL do dodawanej kolumny. Jeśli wycofasz plik JAR, stara wersja nie będzie miała pojęcia o dodanej kolumnie i automatycznie ustawi ją na NULL. Jeśli będzie takie ograniczenie, stara aplikacja po prostu się zepsuje.

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

Zmiany w kodzie

Przechowujemy dane jako last_namea w surname. Jednocześnie czytamy z last_name, ponieważ ta kolumna jest najbardziej istotna. Podczas procesu wdrażania niektóre żądania mogły zostać przetworzone przez instancję aplikacji, 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 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 nazwiska z kodu

Wersja aplikacji: 3.0.0

Wersja bazy danych:v3

Komentarz

Notatka per.: Najwyraźniej w oryginalnym artykule autor omyłkowo skopiował tekst tego bloku z kroku 2. Na tym etapie należy wprowadzić zmiany w kodzie aplikacji mające na celu usunięcie funkcjonalności wykorzystującej kolumnę last_name.

Dodając nową kolumnę i kopiując jej zawartość, stworzyliśmy kompatybilne wstecz zmiany w bazie danych. Ponadto, jeśli wycofamy plik JAR lub uruchomimy stary plik JAR, nie ulegnie on uszkodzeniu podczas wykonywania.

Cofnięcie aplikacji

Aktualnie posiadamy wersję aplikacji 3.0.0 i baza danych v3... Wersja 3.0.0 nie zapisuje danych last_name. Oznacza to, że w surname przechowywane są najbardziej aktualne informacje.

Kroki:

  1. przywróć aplikację do wersji 2.0.0.
  2. wersja 2.0.0 używa i last_name и surname.
  3. wersja 2.0.0 zajmie surname, jeśli nie jest równe zero, w przeciwnym razie -last_name

Zmiany w bazie danych

W bazie danych nie ma żadnych zmian strukturalnych. Aby przeprowadzić ostateczną migrację starych danych, wykonywany jest następujący skrypt:

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

Zmiany w kodzie

Notatka per.: Opis tego bloku został również omyłkowo skopiowany przez autora z kroku 2. Zgodnie z logiką artykułu zmiany w kodzie na tym etapie powinny mieć na celu usunięcie z niego elementów współpracujących z kolumną last_name.

Przechowujemy dane jako last_namea w surname. Dodatkowo czytamy z kolumny last_name, ponieważ jest to najbardziej istotne. Podczas procesu wdrażania niektóre żądania mogą zostać przetworzone 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: Usuwanie nazwiska z bazy danych

Wersja aplikacji: 4.0.0

Wersja bazy danych: v4

Komentarz

Ze względu na to, że kod wersji 3.0.0 nie użyłem kolumny last_name, nic złego się nie stanie podczas wykonywania, jeśli wrócimy do 3.0.0 po usunięciu kolumny z bazy danych.

Dzienniki wykonywania skryptów

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

Zmiany w DB

Stosunkowo v3 po prostu usuwamy kolumnę last_name i dodaj brakujące ograniczenia.

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

Zmiany w kodzie

Nie ma żadnych zmian w kodzie.

Wniosek

Pomyślnie zastosowaliśmy zmianę nazwy kolumny niezgodną wstecz, wykonując kilka wdrożeń zgodnych wstecz. Poniżej znajduje się podsumowanie wykonanych działań:

  1. wdrożenie wersji aplikacji 1.0.0 с v1 schemat bazy danych (nazwa kolumny = last_name)
  2. wdrożenie wersji aplikacji 2.0.0, w którym przechowywane są dane last_name и surname. Aplikacja czyta z last_name. Baza danych jest w wersji v2zawierające kolumny takie jak last_nameI surname. surname jest kopią last_name. (UWAGA: ta kolumna nie może mieć ograniczenia innego niż null)
  3. wdrożenie wersji aplikacji 3.0.0, który przechowuje dane tylko w surname i czyta od nazwiska. Jeśli chodzi o bazę danych, odbywa się ostatnia migracja last_name в surname. Również ograniczenie NIE JEST ZEREM usunięte z last_name. Baza danych jest teraz w wersji v3
  4. wdrożenie wersji aplikacji 4.0.0 - nie wprowadza się żadnych zmian w kodzie. Wdrożenie bazy danych v4, który usuwa last_name. Tutaj możesz dodać brakujące ograniczenia do bazy danych.

Stosując to podejście, zawsze możesz wycofać jedną wersję bez zakłócania zgodności bazy danych/aplikacji.

kod

Cały kod użyty w tym artykule jest dostępny pod adresem Github. Poniżej znajduje się dodatkowy opis.

Projekty

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

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

Skrypty

Możesz uruchomić skrypty opisane w poniższych skryptach, które zademonstrują wstecznie kompatybilne i niekompatybilne zmiany w bazie danych.

Zobaczyć w przypadku zmian kompatybilnych wstecz, uruchomić:

./scripts/scenario_backward_compatible.sh

I zobaczyć przypadek ze zmianami niekompatybilnymi wstecz, uruchomić:

./scripts/scenario_backward_incompatible.sh

Przykładowy Flyway buta wiosennego

Wszystkie przykłady pochodzą z Spring Boot Sample Flyway.

Możesz rzucić okiem http://localhost:8080/flyway, znajduje się lista skryptów.

Ten przykład obejmuje także konsolę H2 (at http://localhost:8080/h2-console), dzięki czemu można wyświetlić stan bazy danych (domyślny adres URL jdbc to jdbc:h2:mem:testdb).

dodatkowo

Przeczytaj także inne artykuły na naszym blogu:

Źródło: www.habr.com

Dodaj komentarz