Nigdy wcześniej nie było i znów to się zdarzyło!
W naszym najnowszym projekcie zdecydowaliśmy się wdrożyć Liquibase od samego początku, aby uniknąć problemów w przyszłości. Okazało się, że nie wszyscy młodsze członkowie zespołu potrafią go właściwie używać. Zorganizowałem wewnętrzne warsztaty, które postanowiłem zamienić w artykuł.
Artykuł zawiera przydatne wskazówki oraz opis trzech najczęstszych pułapek, w które można wpaść pracując z narzędziami do migracji relacyjnych baz danych, w szczególności Liquibase. Jest skierowany do programistów Java na poziomie Junior i Middle, ale może być interesujący również dla bardziej doświadczonych programistów w celu uporządkowania i powtórzenia tego, co najprawdopodobniej już wiedzą.

Liquibase i Flyway to główne konkurencyjne technologie do zarządzania wersjami struktur relacyjnych w świecie Java. Pierwsza z nich jest całkowicie bezpłatna i w praktyce jest częściej wybierana do użycia, dlatego Liquibase zostało bohaterem tego publikacji. Niemniej jednak, niektóre z opisanych praktyk mogą być uniwersalne, w zależności od architektury twojego aplikacji.
Migracje struktur relacyjnych są wymuszonym sposobem radzenia sobie z niską elastycznością relacyjnych magazynów danych. W czasach mody na OOP praca z bazami danych polegała na tym, że raz opisaliśmy schemat i nie zmienialiśmy go więcej. Jednak rzeczywistość zawsze jest taka, że wszystko się zmienia, a zmiany w strukturze tabel są dość częste. Oczywiście, sam proces bywa bolesny i nieprzyjemny.
Nie będę zagłębiać się w opis technologii i instrukcje dotyczące dodawania biblioteki do swojego projektu, na ten temat napisano już wystarczająco dużo artykułów:
Ponadto była już świetna artykuł na temat przydatnych wskazówek:
Porady
Chcę podzielić się swoimi wskazówkami i komentarzami, które narodziły się w wyniku trudności, bólu i wysiłku przy rozwiązywaniu problemów z migracją.
1. Przed przystąpieniem do pracy warto zapoznać się z sekcją najlepszych praktyk na Liquibase
opisano są proste, ale bardzo ważne rzeczy, bez których korzystanie z biblioteki może znacząco utrudnić życie. Na przykład, niestrukturalne podejście do zarządzania changsetami prędzej czy później prowadzi do nieporozumień i uszkodzonych migracji. Jeśli zmiany w strukturze bazy danych i logice usług nie są wprowadzane jednocześnie, istnieje duże prawdopodobieństwo, że doprowadzi to do błędów w testach lub uszkodzenia środowiska. Ponadto, zalecenia dotyczące korzystania z Liquibase na oficjalnej stronie zawierają punkt dotyczący opracowywania i sprawdzania skryptów rollback wraz z podstawowymi skryptami migracji. A także w artykule są przykłady kodu dotyczącego migracji i mechanizmu rollback.
2. Jeśli zacząłeś używać narzędzi do migracji – nie dokonuj ręcznych poprawek w strukturze bazy.
Jak mówią: „Jeden raz Persil — zawsze Persil”. Jeśli baza twojej aplikacji zaczęła być zarządzana przez Liquibase — wszelkie ręczne zmiany natychmiast prowadzą do niespójnego stanu, a poziom zaufania do changsetów staje się równy zeru. Potencjalne ryzyko to kilka straconych godzin na przywracanie bazy, w najgorszym przypadku — uszkodzony serwer. Jeśli w twoim zespole jest DBA Architekt „starej daty”, cierpliwie i przemyślanie wyjaśnij mu, jak źle się to skończy, jeśli po prostu edytuje bazę według własnego uznania z użyciem SQL Developer.
3. Jeśli changset został już wprowadzony do repozytorium, unikaj edytowania.
Jeśli inny programista zrobił pull i zastosował changset, który następnie zostanie edytowany — na pewno wspomni o tobie ciepłym słowem, gdy napotka błąd przy uruchamianiu aplikacji. Jeśli edytowanie changsetu w jakikolwiek sposób przejdzie do środowiska deweloperskiego — trzeba będzie uważać na pułapki hotfixów. Istota problemu sprowadza się do walidacji zmian na podstawie sumy kontrolnej — podstawowego mechanizmu Liquibase. Podczas edytowania kodu changsetu zmienia się suma kontrolna. Edytowanie changsetów jest możliwe tylko wtedy, gdy istnieje możliwość bez utraty danych przywrócenia całej bazy od zera. W takim przypadku refaktoryzacja kodu SQL lub XML może wręcz ułatwić życie, czyniąc migracje bardziej czytelnymi. Przykładem może być sytuacja, gdy na starcie aplikacji schemat początkowej bazy danych został uzgodniony w zespole.
4. Miej niezawodne kopie zapasowe baz danych, jeśli to możliwe
Myślę, że wszystko jest tutaj jasne. W przypadku, gdy migracja nie powiedzie się, wszystko można będzie przywrócić. W Liquibase istnieje narzędzie do wycofywania zmian, ale skrypty do wycofania również pisze sam programista, i mogą występować w nich problemy z taką samą prawdopodobnością, jak w skryptach głównego zestawu zmian. Oznacza to, że warto mieć kopie zapasowe w każdym przypadku.
5. Używaj niezawodnych kopii zapasowych baz danych w rozwijaniu, jeśli to możliwe
Jeśli nie jest to sprzeczne z umowami i prywatnością, w bazie nie ma danych osobowych, a nie waży ona jak dwa słońca — przed zastosowaniem migracji na serwerach produkcyjnych można sprawdzić, jak to działa na maszynie dewelopera i zidentyfikować prawie 100% potencjalnych problemów podczas migracji.
6. Komunikuj się z innymi programistami w zespole
W prawidłowo zorganizowanym procesie tworzenia oprogramowania wszyscy w zespole wiedzą, kto nad czym pracuje. W rzeczywistości często tak nie jest, dlatego, jeśli w ramach swojej zadania wprowadzasz zmiany w strukturze bazy danych, warto dodatkowo powiadomić o tym cały zespół. Jeśli ktoś wprowadza zmiany równolegle — powinniście się zorganizować. Warto rozmawiać z kolegami także po zakończeniu pracy, nie tylko na początku. Wiele potencjalnych problemów z zestawami zmian można rozwiązać na etapie przeglądu kodu.
7. Myśl, co robisz!
To oczywista rada, która ma zastosowanie w każdej sytuacji. Jednak wiele problemów dałoby się uniknąć, gdyby programista raz jeszcze przeanalizował, co robi i jakie to może mieć wpływ. Praca z migracjami zawsze wymaga dodatkowej uwagi i staranności.
Pułapki
Zobaczmy teraz typowe pułapki, w które można wpaść, jeśli nie będziesz stosować się do powyższych rad, i co w takim razie robić?
Sytuacja 1. Dwóch programistów próbuje jednocześnie dodać nowe zestawy zmian

Wania i Petya chcą stworzyć zestaw zmian wersji 4, nie wiedząc o sobie nawzajem. Wprowadzili zmiany w strukturze bazy danych i wysłali pull request, z różnymi plikami zestawów zmian. Następnie sugeruje się następujący mechanizm działania:
Jak rozwiązać
- W jakiś sposób koledzy muszą się umówić, w jakiej kolejności powinny iść ich zestawy zmian, niech będzie, że zestaw Peti powinien być zastosowany jako pierwszy.
- Ktoś musi umieścić drugi zestaw danych u siebie i oznaczyć changset Wasy wersją 5. Można to zrobić poprzez Cherry Pick lub staranny merge.
- Po wprowadzeniu zmian należy sprawdzić poprawność dokonanych działań.
Faktycznie, mechanizmy Liquibase pozwalają mieć w repozytorium dwa changsety wersji 4, więc można zostawić wszystko jak jest. Oznacza to, że będziecie mieli po prostu dwa zmiany wersji 4 o różnych nazwach. Przy takim podejściu w kolejnych wersjach bazy danych staje się bardzo trudno się orientować.
Ponadto, Liquibase, jak dom hobbitów, skrywa wiele tajemnic. Jedną z nich jest klucz validCheckSum, który pojawił się w wersji 1.7 i pozwala określić poprawną wartość sumy kontrolnej dla danego changsetu, niezależnie od tego, co jest zapisane w bazie danych. Dokumentacja mówi następująco:
Dodaj sumę kontrolną, która jest uznawana za poprawną dla tego changSetu, niezależnie od tego, co jest przechowywane w bazie danych. Używane głównie, gdy musisz zmienić changSet i nie chcesz, aby na bazach danych, na których już został uruchomiony, pojawiały się błędy (niezalecana procedura)
Tak, taka procedura nie jest zalecana. Ale czasami silny jasny mag posługuje się także ciemnymi technikami.
Sytuacja 2. Migracja, która zależy od danych.

Załóżmy, że nie masz możliwości użycia kopii zapasowych baz z żywych serwerów. Piotr stworzył changset, sprawdził go lokalnie i z pełną pewnością swojej racji zrobił pull request do dev. Lider projektu dla pewności zapytał, czy Piotr to sprawdził, a następnie zintegrował. Ale wdrożenie na serwerze dev spadło.
Faktycznie, to jest możliwe i nikt nie jest na to ubezpieczony. Dzieje się tak, gdy zmiany struktury tabel są w jakiś sposób powiązane z konkretnymi danymi w BD. Oczywiste jest, że jeśli baza Piotra jest wypełniona tylko danymi testowymi, może nie obejmować wszystkich problematycznych przypadków. Na przykład, przy usuwaniu tabeli okazuje się, że są wpisy w innych tabelach powiązane z zapisami w usuwanej przez Foreign Key. Lub przy zmianie typu kolumny okazuje się, że nie 100% danych może zostać przekształconych na nowy typ.
Jak rozwiązać
- Napisanie specjalnych skryptów, które będą stosowane jednorazowo w połączeniu z migracją i przekształcać dane w odpowiedni sposób. To ogólny sposób rozwiązania problemu przenoszenia danych do nowych struktur już po zastosowaniu migracji, ale coś podobnego może być również zastosowane przed, w szczególnych przypadkach. Taki sposób, oczywiście, nie zawsze jest dostępny, ponieważ edytowanie danych na żywych serwerach może być niebezpieczne i wręcz katastrofalne.
- Inny trudny sposób polega na edytowaniu istniejącego changsetu. Problem polega na tym, że wszystkie bazy danych, w których był on już zastosowany w obecnej formie, będą musiały być przywrócone. Jest całkiem możliwe, że cały zespół backendowy będzie musiał lokalnie uruchomić bazy danych od zera.
- Najbardziej uniwersalnym sposobem jest przeniesienie problemu z danymi do środowiska dewelopera, odtwarzając tę samą sytuację i dodanie nowego changsetu przed zepsutym, który pozwoli na obejście problemu.

Ogólnie im bardziej baza danych jest podobna do bazy z serwera produkcyjnego, tym mniejsze szanse, że problemy z migracjami będą się rozwijać daleko. I oczywiście, zanim wyślesz changset do repozytorium, warto kilka razy się zastanowić, czy nie złamie on czegoś.
Sytuacja 3. Liquibase zaczyna być stosowane już po wdrożeniu do produkcji.
Załóżmy, że lider zespołu poprosił Piotra o wprowadzenie Liquibase do projektu, jednak projekt jest już w produkcji i istnieje już struktura bazy.
W związku z tym problem polega na tym, że na jakichkolwiek nowych serwerach lub maszynach deweloperów dane z tych tabel muszą być odtwarzane od zera, a już istniejące środowisko musi pozostać w spójnym stanie, gotowym do przyjmowania nowych changsetów.
Jak rozwiązać
Istnieje tutaj kilka sposobów:
- Pierwszy i najbardziej oczywisty — mieć oddzielny skrypt, który musi być zastosowany ręcznie przy inicjalizacji nowego środowiska.
- Drugi — mniej oczywisty, mieć migrację Liquibase, która znajduje się w innym kontekście Liquibase i ją stosować. Więcej o kontekście Liquibase można przeczytać tutaj: . Ogólnie rzecz biorąc, to interesujący mechanizm, który może być skutecznie stosowany, na przykład do testowania.
- Trzeci sposób składa się z kilku kroków. Najpierw należy stworzyć migrację dla już istniejących tabel. Następnie musi ona zostać zastosowana w jakimś środowisku, a w ten sposób zostanie uzyskany jej hash. Kolejnym krokiem jest zainicjowanie pustych tabel Liquibase na naszym niepustym serwerze, a do tabeli z historią zastosowania change setów można ręcznie dodać wpis o "jakoby zastosowanym" change secie z już istniejącymi w bazie zmianami. W ten sposób na już istniejącym serwerze historia rozpocznie się od wersji 2, a wszystkie nowe środowiska będą się zachowywać identycznie.

Sytuacja 4. Migracje stają się ogromne i nie zdążają się wykonać.
Na początku rozwoju usługi, zazwyczaj, Liquibase jest używany jako zewnętrzna zależność, a wszystkie migracje są przetwarzane przy starcie aplikacji. Jednak z czasem możesz natknąć się na następujące przypadki:
- Migracje stają się ogromne i trwają długo.
- Pojawia się potrzeba migracji w rozproszonych środowiskach, na przykład, na kilku instancjach baz danych jednocześnie.
W takim przypadku zbyt długie zastosowanie migracji spowoduje timeout przy starcie aplikacji. Ponadto zastosowanie migracji dla każdej instancji aplikacji z osobna może prowadzić do tego, że różne serwery będą w niesynchronizowanym stanie.
Jak rozwiązać
W takich przypadkach Twój projekt jest już duży, być może nawet dojrzały, a Liquibase zaczyna pełnić rolę oddzielnego, zewnętrznego narzędzia. Chodzi o to, że Liquibase jako biblioteka jest pakowana do pliku jar i może działać zarówno jako zależność wewnątrz projektu, jak i autonomicznie.
W trybie autonomicznym można powierzyć zastosowanie migracji Twojemu środowisku CI/CD lub zaufanym rękom Twoich administratorów systemów/specjalistów ds. wdrożeń. Wymagana będzie komenda Liquibase. . W takim trybie pojawia się możliwość uruchomienia aplikacji dopiero po tym, jak wszystkie niezbędne migracje zostały przeprowadzone.
Wnioski
W rzeczywistości pułapek przy migracjach baz danych może być znacznie więcej, a wiele z nich wymaga kreatywnego podejścia. Ważne jest, aby zrozumieć, że jeśli narzędzie jest używane prawidłowo, to większość tych pułapek można uniknąć. Osobiście spotkałem się z każdą z wymienionych problemów w różnych sytuacjach, a niektóre z nich były wynikiem moich błędów. Z reguły zdarza się to z braku uwagi, ale czasami – z powodu przestępczej niekompetencji w korzystaniu z narzędzia.
Źródło: habr.com


