Expand–Migrate–Contract: jak zmieniać schemat bez zatrzymywania aplikacji
Expand–Migrate–Contract: jak zmieniać schemat bez zatrzymywania aplikacji
Projektowanie nowej tabeli jest stosunkowo łatwe. Znacznie trudniejsze jest zmienianie tabeli, z której korzystają już aplikacje, raporty, procesy ETL oraz użytkownicy.
W środowisku laboratoryjnym możemy wykonać ALTER TABLE, poprawić kod i uruchomić wszystko ponownie. W systemie produkcyjnym przez pewien czas mogą jednak działać równolegle dwie wersje aplikacji. Jeden raport może nadal oczekiwać starej kolumny, a proces integracyjny może wykonywać się tylko raz dziennie i ujawnić problem dopiero wiele godzin po wdrożeniu.
Wzorzec Expand–Migrate–Contract pozwala rozłożyć zmianę na kontrolowane etapy:
- Expand – dodajemy nową strukturę, ale nie usuwamy starej.
- Migrate – przenosimy dane i ruch aplikacyjny.
- Validate – sprawdzamy kompletność i rzeczywiste wykorzystanie starego modelu.
- Contract – dopiero wtedy usuwamy starą strukturę.
Najważniejsza zasada brzmi:
Zmiana schematu jest procesem wdrożeniowym, a nie pojedynczą instrukcją DDL.
Przykładowy problem
Załóżmy, że pierwsza wersja systemu przechowuje adres klienta w jednej kolumnie:
| |
Po pewnym czasie biznes chce:
- przechowywać kilka adresów,
- rozróżniać adres rozliczeniowy i wysyłkowy,
- filtrować klientów po mieście,
- integrować dane z firmą kurierską.
Naturalnym modelem docelowym jest osobna tabela adresów. Ryzykownym rozwiązaniem byłoby jednak natychmiastowe usunięcie AddressText.
Etap 1: Expand
Najpierw dodajemy nową tabelę:
| |
Stara kolumna nadal istnieje. Oznacza to, że aplikacja V1 może działać bez zmian, a aplikacja V2 może być przygotowywana do korzystania z nowego modelu.
Zmiana rozszerzająca jest zwykle łatwiejsza do wdrożenia niż zmiana usuwająca. Dodanie tabeli nie łamie istniejącego kontraktu. Usunięcie kolumny już tak.
Etap 2: Migrate
Dane należy przenieść do nowego modelu. Na małej tabeli wystarczy pojedynczy INSERT ... SELECT. Na dużej tabeli lepiej stosować partie.
| |
Migracja partiami ogranicza:
- czas pojedynczej transakcji,
- wzrost logu transakcyjnego,
- długość blokad,
- ryzyko długiego rollbacku,
- wpływ na codzienną pracę systemu.
Proces powinien być idempotentny albo przynajmniej wznawialny. Ponowne uruchomienie nie może tworzyć duplikatów.
Okres zgodności
Przez pewien czas stara i nowa wersja aplikacji mogą działać równolegle. Wtedy trzeba zdecydować, jak utrzymać spójność.
Możliwe rozwiązania to:
- tymczasowy dual write,
- jedna procedura zapisująca oba modele,
- widok zgodności,
- zatrzymanie zapisu V1 po przełączeniu wszystkich instancji aplikacji.
Dual write bywa użyteczny, ale zwiększa złożoność. Jeżeli zapis do jednej struktury powiedzie się, a do drugiej nie, powstaje niespójność. Dlatego oba zapisy powinny znaleźć się w jednej transakcji lokalnej.
Walidacja przed usunięciem starego modelu
Nie należy usuwać kolumny tylko dlatego, że wdrożono nową aplikację. Potrzebne są dowody.
Najprostsza kontrola kompletności:
| |
Zapytanie powinno zwrócić zero wierszy.
Trzeba również sprawdzić zależności modułów:
| |
To nadal nie daje pełnej gwarancji. Dynamiczny SQL, kod aplikacji i zewnętrzne raporty mogą nie pojawić się w metadanych. W praktyce warto wykorzystać również Query Store, Extended Events, monitoring błędów aplikacji oraz analizę repozytoriów kodu.
Etap 3: Contract
Dopiero po zakończeniu migracji i wycofaniu aplikacji V1 możemy usunąć starą strukturę:
| |
Przed tą operacją należy usunąć lub zmienić:
- procedury odwołujące się do kolumny,
- widoki zgodności,
- triggery,
- indeksy,
- raporty,
- procesy ETL,
- mapowania ORM.
Etap Contract powinien być osobnym wdrożeniem. Rozdzielenie go od etapu Expand daje czas na obserwację systemu i bezpieczne wycofanie kodu aplikacji.
Rollback czy roll forward?
Wycofanie kodu nie zawsze oznacza możliwość wycofania migracji bazy. Jeżeli nowa aplikacja zapisała dane, których stary model nie potrafi reprezentować, powrót może oznaczać utratę informacji.
Dlatego warto wcześniej określić:
- do którego momentu możliwy jest rollback,
- kiedy przechodzimy na strategię roll forward,
- jak odtworzyć dane w przypadku niepełnej migracji,
- jakie metryki decydują o zatrzymaniu wdrożenia.
Kiedy używać wzorca?
Expand–Migrate–Contract sprawdza się szczególnie wtedy, gdy:
- wdrożenie nie może wymagać długiego przestoju,
- aplikacja jest uruchomiona na wielu instancjach,
- schemat jest współdzielony przez raporty i integracje,
- migracja danych trwa długo,
- potrzebujemy możliwości kontrolowanego przełączenia.
Koszty
Wzorzec nie jest darmowy. Przez pewien czas utrzymujemy dwa modele, dodatkowy kod zgodności i więcej kroków wdrożenia. Potrzebujemy też monitoringu oraz procedury sprzątania.
To jednak koszt kontrolowanej zmiany. Alternatywą jest wdrożenie typu „wszystko naraz”, w którym każdy ukryty konsument starej struktury może zatrzymać system.
Podsumowanie
Dojrzała ewolucja schematu nie zaczyna się od pytania:
Czy instrukcja
ALTER TABLEsię wykona?
Zaczyna się od pytań:
- Czy system będzie działał podczas zmiany?
- Czy obie wersje aplikacji pozostaną kompatybilne?
- Czy migrację można wznowić?
- Jak potwierdzimy kompletność danych?
- Kiedy usunięcie starego modelu będzie bezpieczne?
Expand–Migrate–Contract zamienia ryzykowną zmianę w serię małych, obserwowalnych i odwracalnych kroków. I właśnie dlatego jest jednym z podstawowych wzorców ewolucji relacyjnych baz danych.