Migration tests i data tests: bezpieczna zmiana stanu
- data
- kategoria
- Testing
- także w
- Databases · Reliability Engineering
- czytanie
- 2 min / 385 słów
Kod zwykle cofa się łatwiej niż dane.
Można przełączyć obraz kontenera.
Można wyłączyć feature flag.
Można wrócić do poprzedniego commita.
Ale jeżeli nowa wersja zapisała dane w formacie, którego stara wersja nie rozumie, rollback przestaje być prosty.
To jest miejsce dla migration tests i data tests.
Co to za rodzaj testu
Migration test sprawdza migrację schematu albo danych.
Może dotyczyć:
dodania kolumny
zmiany typu
nowego indeksu
backfillu
podziału tabeli
zmiany constraintu
Data compatibility test sprawdza, czy różne wersje kodu rozumieją dane, które mogą spotkać w systemie.
Najważniejsze pytania brzmią:
czy nowy kod czyta stare dane?
czy stary kod czyta nowe dane?
czy migracja może zostać uruchomiona drugi raz?
czy rollback zostawia dane w stanie zrozumiałym?
Expand and contract
Bezpieczna zmiana danych często idzie w kilku krokach.
dodaj nowe pole
pisz stare i nowe pole
przenieś czytanie na nowe pole
wyczyść stare pole
usuń stare pole
To podejście nazywa się często expand and contract.
Testy migracji powinny pilnować każdego kroku.
Jeżeli od razu usuniemy stare pole, stara wersja aplikacji może przestać działać zanim nowa wersja będzie wszędzie.
Jeżeli zaczniemy zapisywać tylko nowy format, worker w starszej wersji może przestać czytać kolejkę.
Co się dzieje, kiedy ich nie ma
Bez testów migracji pipeline może przepuścić zmianę, której nie da się cofnąć.
Przykłady:
migracja działa tylko na pustej bazie
backfill trwa godzinę zamiast minuty
NOT NULL wchodzi przed uzupełnieniem danych
rollback usuwa dane, których nie da się odtworzyć
nowa wersja zapisuje event nieczytelny dla starego konsumenta
To nie są problemy samego SQL.
To problemy operowania systemem w czasie zmiany.
Test na prawdziwym stanie
Migration test ma największą wartość, kiedy startuje z realistycznego stanu.
Nie musi kopiować produkcji.
Powinien jednak zawierać przypadki, które produkcja naprawdę ma:
brakujące wartości
stare rekordy
duże tabele
duplikaty
nieaktywne konta
rzadkie statusy
Migracja przetestowana tylko na idealnych fixture potrafi przejść lokalnie i paść na pierwszym starym rekordzie.
Dane jako kontrakt
Dane są kontraktem między wersjami kodu.
Nie tylko między usługami.
Także między:
starą i nową wersją aplikacji
aplikacją i workerem
backendem i raportami
eventem i konsumentem
backupem i restore
Jeżeli zmieniamy format danych, zmieniamy kontrakt.
Test powinien powiedzieć, która wersja kodu może czytać który format.
Bez tego rollback jest nadzieją, nie planem.
Narzędzie ze stacka
Do migration tests wybrałbym Testcontainers z prawdziwym PostgreSQL.
Test powinien uruchomić migracje tym samym mechanizmem, którego używa aplikacja, a potem sprawdzić dane przez normalny kod albo SQL.
Minimalny zestaw:
PostgreSQL w kontenerze
migracje projektu
fixture ze starym stanem
uruchomienie migracji
asercje na nowym stanie
Na backendzie Node można to spiąć z Vitest, bo runner nie jest tu najważniejszy.
Najważniejsze jest to, że test używa prawdziwej bazy i prawdziwej migracji.
Kiedy test jest za późno
Migration test odpalony dopiero po deploymencie jest za późno.
Może potwierdzić, że środowisko żyje, ale nie powinien być pierwszym miejscem, w którym sprawdzamy migrację.
Najpierw potrzebny jest test w pipeline.
Potem można mieć smoke test po wdrożeniu, który sprawdzi, że aplikacja widzi nowy schemat.
To są dwa różne pytania.
Pierwsze brzmi: czy migracja jest poprawna.
Drugie: czy wdrożone środowisko jest po migracji używalne.
Następna warstwa dotyczy sytuacji, w której system jest poprawny funkcjonalnie, ale nie wytrzymuje ruchu.