Baza danych i migracje
Goat wykorzystuje PostgreSQL uruchamiany w Dockerze, dzięki czemu nie musisz instalować ani konfigurować serwera bazy danych bezpośrednio na hoście.
Lokalne środowisko przechowuje dane PostgreSQL w katalogu:
.cache/postgresDzięki temu dane nie znikają po zatrzymaniu lub ponownym uruchomieniu kontenera.
Takie podejście pozwala zachować wygodę lokalnej pracy, a jednocześnie utrzymać powtarzalne środowisko pomiędzy różnymi komputerami i systemami operacyjnymi.
Uruchomienie bazy danych
Aby uruchomić samą bazę danych, wykonaj:
go run ./scripts run:script --path=herd/db/run.goatPo uruchomieniu PostgreSQL jest dostępny na porcie:
5433Parametry połączenia są pobierane z lokalnego pliku .env.
Goat korzysta z wartości z prefiksem:
GOAT_DB_MAIN_*Są tam definiowane między innymi dane potrzebne do połączenia z bazą, takie jak nazwa bazy, użytkownik, hasło czy host.
Nie umieszczaj prawdziwych sekretów produkcyjnych w repozytorium.
Trwałość danych lokalnych
Kontener PostgreSQL może być zatrzymywany i uruchamiany ponownie bez utraty danych, ponieważ właściwe pliki bazy znajdują się poza jego tymczasowym systemem plików.
W środowisku lokalnym dane są przechowywane w:
.cache/postgresOznacza to, że:
- ponowne uruchomienie kontenera nie usuwa bazy,
- ponowne uruchomienie środowiska developerskiego zachowuje dane,
- migracje mogą być wykonywane na istniejącym lokalnym stanie,
- możesz pracować nad projektem bez każdorazowego odtwarzania bazy od zera.
Jeżeli potrzebujesz czystego środowiska, użyj dedykowanego skryptu czyszczącego zamiast ręcznie usuwać pliki PostgreSQL.
Migracje
Migracje uruchamiasz poleceniem:
goat run:script --path=herd/db/migrate.goatSkrypt przygotowuje środowisko wymagane do wykonania migracji.
Typowy proces obejmuje:
uruchomienie PostgreSQL
↓
wygenerowanie aplikacji
↓
zbudowanie CLI
↓
uruchomienie db:migrateDzięki temu migracje nie zależą od ręcznego wykonywania kolejnych kroków przez programistę.
Początkowy schemat bazy
Podstawowy schemat aplikacji jest generowany na podstawie modelu zdefiniowanego w:
herd/_model.goatWygenerowany SQL znajduje się w:
app/static/raw/data/sql/migrations/0001_schema.sqlPlik zawiera początkową strukturę bazy wynikającą z modelu aplikacji.
Może obejmować między innymi:
- tabele,
- kolumny,
- typy danych,
- klucze,
- relacje,
- indeksy,
- ograniczenia wynikające z modelu.
To pozwala utrzymać spójność pomiędzy modelem domeny aplikacji a podstawowym schematem PostgreSQL.
Model a migracje
Zmiana:
herd/_model.goatmoże wpłynąć na strukturę wygenerowanego schematu.
Przykładowo:
dodanie pola do encji
↓
goat re
↓
zmiana wygenerowanego modelu
↓
zmiana SQLNie oznacza to jednak, że każda zmiana modelu jest automatycznie bezpieczną migracją istniejącej bazy danych.
To szczególnie istotne, gdy baza zawiera już dane lub jest wykorzystywana przez innych użytkowników.
Przykłady zmian wymagających dodatkowej uwagi:
- usunięcie kolumny,
- zmiana typu danych,
- dodanie pola
NOT NULL, - zmiana relacji,
- usunięcie tabeli,
- zmiana kluczy lub ograniczeń,
- przebudowa danych już zapisanych w bazie.
Generator może opisać docelową strukturę, ale sposób bezpiecznego przejścia z aktualnego stanu danych do nowego schematu może wymagać ręcznie przygotowanej migracji.
Automatyczne migracje podczas deployu
Skrypt herd/deploy.goat uruchamia db:migrate przed startem nowej wersji<br>aplikacji, gdy PostgreSQL jest już gotowy. Dlatego zwykła zmiana modelu trafia<br>na produkcję razem z wdrożeniem, bez osobnego ręcznego polecenia na serwerze.
Typowy przebieg dla zmiany bez migracji danych:
# 1. zmień herd/_model.goat
goat re
# 2. utwórz i przejrzyj nową migrację
goat db:migration:generate --out-dir=goatapp/static/raw/data/sql/migrations
# 3. wdroż aplikację; deploy uruchomi db:migrate automatycznie
goat run:script --path=herd/deploy.goat0001_schema.sql jest tworzony raz. db:migrate wykonuje wyłącznie nowe,<br>numerowane nazwy plików. Generator porównuje tymczasowe bazy uruchomione przez<br>db:migrate i db:fresh, po czym tworzy migrację do przeglądu.
Automatyczny mechanizm jest przeznaczony dla zmian zachowujących dane, na<br>przykład dodania tabeli lub opcjonalnej kolumny. Nie rozpoznaje zmiany nazwy<br>kolumny ani nie zgaduje sposobu przekształcenia danych.
Przed migracjami produkcyjnymi istniejącej instalacji goat deploy zatrzymuje<br>aplikację i tworzy zaszyfrowany backup strony. Archiwum zawiera zrzut<br>PostgreSQL, trwałe pliki, bieżącą konfigurację uruchomieniową i obraz aplikacji.<br>Backup jest odtwarzany w izolowanej bazie przy utrzymanej blokadzie wdrożenia;<br>błąd backupu lub jego weryfikacji zatrzymuje wdrożenie przed uruchomieniem<br>którejkolwiek migracji.<br>Kopie są przechowywane lokalnie w .backups/goatcms.com/ oraz zdalnie w<br>~/deploy/goatcms.com/backups/. Hasło jest pobierane przez wspólny mechanizm<br>encrypt; w CI należy ustawić GOAT_ENCRYPT_PASSWORD.
Ręczna weryfikacja i przywrócenie kopii:
goat remote:backup:verify --scope=goatcms.com --from=.backups/goatcms.com/<backup>.goatbackup
goat remote:restore --scope=goatcms.com --from=.backups/goatcms.com/<backup>.goatbackup --replaceGdy zmiana wymaga uzupełnienia danych, konwersji typu, usunięcia danych albo<br>dodania ograniczenia dla istniejących rekordów, przygotuj własny plik SQL w<br>child/app/migrations/sql/, na przykład<br>0002_backfill_customer_status.sql. Deploy uruchamia potem także<br>child:db:migrate; plik zostanie wykonany raz i zapisany w niezależnej<br>historii migracji aplikacji.
Generowanie migracji ze snapshotu modelu
Generator lokalnie porównuje historię migracji z pełnym snapshotem modelu:
goat db:migration:generate --out-dir=goatapp/static/raw/data/sql/migrations
# przejrzyj goatapp/static/raw/data/sql/migrations/0002_model_sync.sql
goat run:script --path=herd/deploy.goatPolecenie nie łączy się z produkcją. Po regeneracji buduje aplikację i uruchamia<br>tymczasowy PostgreSQL 17 z dwiema pustymi bazami: w jednej wykonuje<br>db:migrate, a w drugiej db:fresh. Przypięty pg-schema-diff tworzy plan<br>migracje → snapshot. Brak różnic nie tworzy pliku.
db:migration:generate tworzy SQL do przeglądu, natomiast db:migrate i<br>child:db:migrate wykonują migracje na wskazanej bazie. Dopiero<br>goat run:script --path=herd/deploy.goat uruchamia te polecenia na produkcji w<br>ramach deployu. Operacje destrukcyjne, zmiany typów, długie blokady i<br>przebudowy indeksów są oznaczane ostrzeżeniami i wymagają ręcznej oceny.
Sprawdzanie migracji
Przed zastosowaniem zmian schematu na bazie zawierającej istotne dane sprawdź wygenerowany lub przygotowany SQL.
Warto zweryfikować przede wszystkim:
- czy migracja nie usuwa danych,
- czy zmiana typu kolumny jest możliwa dla istniejących wartości,
- czy nowe pola posiadają odpowiednie wartości domyślne,
- czy nowe ograniczenia są spełnione przez istniejące rekordy,
- czy migracja nie powoduje kosztownej blokady dużej tabeli,
- czy możliwe jest bezpieczne wycofanie zmiany.
Szczególnie ostrożnie traktuj operacje takie jak:
DROP TABLE
DROP COLUMN
ALTER COLUMNponieważ mogą prowadzić do nieodwracalnej utraty danych.
Czyszczenie lokalnej bazy
Aby wyczyścić lokalną bazę danych, uruchom:
goat run:script --path=herd/db/clean.goatSkrypt usuwa dane i strukturę lokalnej bazy, umożliwiając rozpoczęcie pracy od czystego stanu.
Może to być przydatne między innymi gdy:
- chcesz ponownie przetestować inicjalizację projektu,
- zmieniłeś model w sposób niekompatybilny z lokalną bazą,
- testujesz migracje od pustego schematu,
- chcesz odtworzyć dane fixture,
- lokalne dane przestały odpowiadać aktualnej wersji aplikacji.
Uwaga na destrukcyjne operacje
herd/db/clean.goat wykonuje operacje destrukcyjne.
Może trwale usunąć:
- tabele,
- dane,
- lokalny stan bazy.
Dlatego używaj go wyłącznie wtedy, gdy masz pewność, że konfiguracja wskazuje na właściwe środowisko.
Przed uruchomieniem warto sprawdzić wartości GOAT_DB_MAIN_* w .env, szczególnie jeśli projekt może łączyć się z więcej niż jedną bazą.
Nie traktuj clean.goat jako narzędzia do zarządzania środowiskiem produkcyjnym.
Typowy workflow podczas zmiany modelu
W środowisku developerskim zmiana struktury danych może wyglądać następująco:
# zmień model
vim herd/_model.goat
# wygeneruj zmiany
goat re
# sprawdź wygenerowany SQL i kod
git diff
# wykonaj migracje
goat run:script --path=herd/db/migrate.goat
# uruchom testy
goat run:script --path=herd/test.goatJeżeli lokalna baza znajduje się w stanie niekompatybilnym z aktualnym modelem i zachowanie danych nie jest potrzebne, możesz ją wyczyścić:
goat run:script --path=herd/db/clean.goat
goat run:script --path=herd/db/migrate.goatMigracje na środowiskach współdzielonych
Podejście wygodne podczas lokalnego developmentu nie powinno być automatycznie przenoszone na środowiska testowe, stagingowe lub produkcyjne.
W przypadku bazy używanej przez innych sama zmiana modelu nie wystarcza.
Przed wdrożeniem zmiany schematu:
- sprawdź różnicę pomiędzy aktualnym i docelowym schematem,
- przejrzyj SQL wykonywany przez migrację,
- oceń wpływ migracji na istniejące dane,
- wykonaj aktualną kopię zapasową,
- przetestuj migrację na kopii rzeczywistych danych,
- oszacuj czas wykonania i możliwe blokady,
- przygotuj sposób wycofania lub naprawy nieudanej migracji,
- dopiero wtedy wykonaj zmianę na środowisku docelowym.
Szczególnie ważne jest testowanie migracji na danych o strukturze i rozmiarze zbliżonym do produkcji. Migracja działająca poprawnie na pustej lokalnej bazie może zachowywać się zupełnie inaczej na dużej tabeli zawierającej miliony rekordów.
Generator nie zastępuje strategii migracji danych
Goat może wygenerować strukturę wynikającą z modelu, ale nie powinien być traktowany jako automatyczna odpowiedź na każdy problem związany ze zmianą danych.
Istnieje istotna różnica pomiędzy:
docelowym schematema:
bezpiecznym sposobem przejścia
z obecnego schematu
do docelowego schematuPrzykładowo dodanie wymaganej kolumny może wymagać kilku etapów:
dodanie kolumny opcjonalnej
↓
uzupełnienie istniejących danych
↓
wdrożenie kodu korzystającego z nowego pola
↓
dodanie ograniczenia NOT NULLPodobnie zmiana typu danych lub struktury relacji może wymagać migracji danych wykonywanej etapami.
Generator pomaga utrzymać strukturę aplikacji, ale odpowiedzialność za bezpieczeństwo danych pozostaje po stronie procesu migracji.
Dobra praktyka
Traktuj herd/_model.goat jako opis modelu aplikacji, a nie jako gwarancję bezpiecznej migracji istniejących danych.
Przy zmianach lokalnych możesz szybko regenerować aplikację i odbudowywać bazę.
Przy zmianach dotyczących środowisk współdzielonych lub produkcyjnych zawsze przeanalizuj konsekwencje dla istniejącego schematu i danych.
Najważniejsza zasada brzmi:
Generator może opisać docelową strukturę bazy, ale bezpieczne przejście pomiędzy kolejnymi wersjami danych wymaga świadomie zaprojektowanej migracji.