7 min

Migracje PostgreSQL z Claude Code — prompty do bezpiecznych zmian

Naucz się, jak używać Claude Code do migracji PostgreSQL bezpiecznie: expand-contract, backfille, plan rollbacku i co sprawdzić w stagingu przed wydaniem.

Migracje PostgreSQL z Claude Code — prompty do bezpiecznych zmian

Co sprawia, że zmiana schematu PostgreSQL jest ryzykowna

Zmiana schematu PostgreSQL wydaje się prosta, dopóki nie spotka się z prawdziwym ruchem i prawdziwymi danymi. Ryzyko zwykle nie tkwi w samym SQL. Pojawia się wtedy, gdy kod aplikacji, stan bazy i timing wdrożenia przestają być spójne.

Większość awarii jest praktyczna i bolesna: wdrożenie łamie się, bo stary kod odwołuje się do nowej kolumny, migracja blokuje gorącą tabelę i pojawiają się timeouty, albo „szybka” zmiana cicho usuwa lub przepisuje dane. Nawet gdy nic nie pada, możesz wypuścić subtelne błędy: złe wartości domyślne, uszkodzone ograniczenia czy indeksy, które nigdy się nie zbudowały.

Migracje generowane przez AI dokładają kolejną warstwę ryzyka. Narzędzia mogą wygenerować poprawny SQL, który i tak jest niebezpieczny dla twojego obciążenia, wolumenu danych czy procesu wydania. Mogą też zgadywać nazwy tabel, pominąć długotrwałe blokady albo zbagatelizować rollback, bo down migracje są trudne. Jeśli używasz Claude Code do migracji, potrzebujesz zabezpieczeń i konkretnego kontekstu.

Gdy w tym poście mówimy, że zmiana jest "bezpieczna", oznacza to trzy rzeczy:

  • Zgodna wstecz: stare i nowe wersje aplikacji mogą działać podczas rolloutu.
  • Obserwowalna: możesz mierzyć postęp i szybko wykryć problemy.
  • Odwracalna: masz plan rollbacku, który da się wykonać pod presją.

Celem jest, aby migracje stały się rutynową pracą: przewidywalną, testowalną i nudną.

Zasady bezpieczeństwa, których trzymaj się zanim napiszesz prompt

Zacznij od kilku niepodważalnych reguł. Pomagają skupić model i chronią przed wysłaniem zmiany, która działa tylko na twoim laptopie.

Rozdziel pracę na małe kroki. Zmiana schematu, backfill danych, zmiana w aplikacji i krok cleanup to różne ryzyka. Ich zgrupowanie utrudnia ustalenie, co się zepsuło i utrudnia rollback.

Preferuj operacje addytywne przed destrukcyjnymi. Dodanie kolumny, indeksu czy tabeli zwykle jest niskiego ryzyka. Zmiany nazwy lub usunięcia to miejsca, gdzie zdarzają się outage'y. Zrób najpierw bezpieczną część, przenieś aplikację, a stare elementy usuń dopiero gdy będziesz pewien, że są nieużywane.

Spraw, by aplikacja tolerowała obie struktury przez pewien czas. Kod powinien móc czytać albo starą kolumnę, albo nową w czasie rolloutu. To zapobiega popularnemu wyścigowi, gdy niektóre serwery uruchomione są z nowym kodem, a baza jest jeszcze stara (lub odwrotnie).

Traktuj migracje jak kod produkcyjny, a nie szybki skrypt. Nawet jeśli budujesz na platformie takiej jak Koder.ai (backend w Go z PostgreSQL, plus klienci w React lub Flutter), baza jest współdzielona przez wszystko. Błędy kosztują dużo.

Jeśli chcesz krótkiego zestawu zasad, które możesz wkleić na górę każdego żądania SQL, użyj czegoś w stylu:

  • Jedna zmiana na migrację: expand, potem backfill, potem zmiana kodu, potem cleanup.
  • Unikaj długich blokad: używaj tworzenia indeksów CONCURRENTLY i małych batchy do aktualizacji.
  • Wymagaj planu rollback dla każdego kroku, włącznie z tym jak zatrzymać backfill w połowie.
  • Wymagaj zapytań weryfikacyjnych i metryk sukcesu (liczba wierszy, udział NULL, czasy).
  • Wymagaj runbooka: jak uruchomić, na co patrzeć i kogo powiadomić.

Praktyczny przykład: zamiast zmienić nazwę kolumny, od której zależy aplikacja, dodaj nową kolumnę, backfill ją powoli, wdroż kod, który czyta najpierw nową, potem starą, i dopiero później usuń starą kolumnę.

Co zawrzeć w promptcie, żeby Claude Code pozostał uziemiony

Claude potrafi napisać sensowny SQL na podstawie ogólnego polecenia, ale bezpieczne migracje potrzebują kontekstu. Traktuj prompt jak mini brief projektowy: pokaż, co istnieje, wyjaśnij, co nie może się zepsuć, i zdefiniuj, co znaczy „bezpieczne” dla twojego rolloutu.

Zacznij od wklejenia tylko faktów o bazie, które mają znaczenie. Dołącz definicję tabeli oraz istotne indeksy i ograniczenia (klucze główne, UNIQUE, FK, CHECK, triggery). Jeśli zaangażowane są powiązane tabele, dołącz ich fragmenty też. Mały, dokładny wycinek zapobiega zgadywaniu nazw lub pominięciu istotnego ograniczenia.

Dodaj skalę rzeczywistą. Liczba wierszy, rozmiar tabeli, tempo zapisów i ruch szczytowy powinny wpływać na plan. "200M wierszy i 1k zapisów/s" to inna migracja niż "20k wierszy i głównie odczyty". Dołącz też wersję Postgresa i sposób uruchamiania migracji w twoim systemie (jedna transakcja vs wieloetapowo).

Opisz, jak aplikacja używa danych: ważne odczyty, zapisy i joby backgroundowe. Przykłady: "API czyta po emailu", "workerzy aktualizują status" albo "raporty skanują po created_at". To określa, czy potrzebujesz expand/contract, feature flagów i jak bezpieczny będzie backfill.

Na koniec bądź eksplicytny co do ograniczeń i rezultatów. Prosta struktura działa dobrze:

  • Fragmenty obecnego schematu i cel
  • Założenia skali (liczba wierszy, zapisów/s, okno konserwacji jeśli jest)
  • Zależności aplikacji (zapytania/endpointy/job'y)
  • Twarde ograniczenia (bez przestojów, unikać długich blokad, unikać pełnych przepisów tabel)
  • Rezultaty: SQL plus plan w języku potocznym, weryfikacja i rollback

Poproszenie o SQL i plan uruchomienia zmusza model do myślenia o kolejności, ryzyku i co sprawdzić przed releasem.

Expand/contract po ludzku (i kiedy stosować)

Wzorzec expand/contract zmiany w PostgreSQL pozwala zmieniać bazę bez łamania aplikacji w czasie pracy. Zamiast ryzykownego jednego przełącznika, baza obsługuje zarówno stary, jak i nowy kształt przez pewien okres.

Pomyśl o tym jak: dodaj nowe rzeczy bezpiecznie (expand), przenieś ruch i dane stopniowo, a dopiero potem usuń stare elementy (contract). To szczególnie przydatne przy pracy wspomaganej AI, bo zmusza do zaplanowania „brudnego środka”.

Cztery fazy

Praktyczny przebieg wygląda tak:

  • Expand: dodaj nową nullable kolumnę lub tabelę, dodaj indeks jeśli potrzeba i dodaj ograniczenia w sposób unikający blokowania (np. dodać constraint jako NOT VALID, gdy to odpowiednie).
  • Kompatybilność: zaktualizuj aplikację, by obsługiwała oba pola. To może być dual-write (zapis do obu miejsc) lub read-fallback (czytaj nową, jeśli brak to stara).
  • Backfill: kopiuj stare dane do nowych w małych batchach, z checkpointami i możliwością wznowienia.
  • Contract: gdy wszystko używa nowej ścieżki, zaostrz reguły (SET NOT NULL, VALIDATE), a potem usuń starą kolumnę lub tabelę.

Używaj tego wzorca zawsze, gdy użytkownicy mogą być na starym kodzie podczas zmiany bazy. To obejmuje deploymenty wieloinstancyjne, aplikacje mobilne aktualizujące się wolno lub jakikolwiek release, gdzie migracja może trwać minuty lub godziny.

Przydatną taktyką jest zaplanowanie dwóch wydań. Wydanie 1 wykonuje expand plus kompatybilność, tak by nic nie psuło się, jeśli backfill jest niekompletny. Wydanie 2 robi contract dopiero po potwierdzeniu, że nowy kod i nowe dane są na miejscu.

Bezpieczny szablon prompta dla migracji expand/contract

Skopiuj ten szablon i wypełnij nawiasy. Wymusza on na Claude Code wygenerowanie wykonalnego SQL, kontroli potwierdzających wykonanie oraz realistycznego planu rollbacku.

You are helping me plan a PostgreSQL expand-contract migration.

Context
- App: [what the feature does, who uses it]
- Database: PostgreSQL [version if known]
- Table sizes: [rough row counts], write rate: [low/medium/high]
- Zero/near-zero downtime required: [yes/no]

Goal
- Change: [describe the schema change]
- Current schema (relevant parts):
  [paste CREATE TABLE or \\d output]
- How the app will change (expand phase and contract phase):
  - Expand: [new columns/indexes/triggers, dual-write, read preference]
  - Contract: [when/how we stop writing old fields and remove them]

Hard safety requirements
- Prefer lock-safe operations. Avoid full table rewrites on large tables when possible.
- If any step can block writes, call it out explicitly and suggest alternatives.
- Use small, reversible steps. No “big bang” changes.

Deliverables
1) UP migration SQL (expand)
   - Use clear comments.
   - If you propose indexes, tell me if they should be created CONCURRENTLY.
   - If you propose constraints, tell me whether to add them NOT VALID then VALIDATE.

2) Verification queries
   - Queries to confirm the new schema exists.
   - Queries to confirm data is being written to both old and new structures (if dual-write).
   - Queries to estimate whether the change caused bloat/slow queries/locks.

3) Rollback plan (realistic)
   - DOWN migration SQL (only if it is truly safe).
   - If down is not safe, write a rollback runbook:
     - how to stop the app change
     - how to switch reads back
     - what data might be lost or need re-backfill

4) Runbook notes
   - Exact order of operations (including app deploy steps).
   - What to monitor during the run (errors, latency, deadlocks, lock waits).
   - “Stop/continue” checkpoints.

Output format
- Separate sections titled: UP.sql, VERIFY.sql, DOWN.sql (or ROLLBACK.md), RUNBOOK.md

Dwie dodatkowe linie, które pomagają w praktyce:

  • Poproś, by oznaczył każdy krok blokujący zapisy jako RISK: blocks writes, plus kiedy go uruchomić (off-peak vs anytime).
  • Wymuś szczerość na temat blokad: "If you're not sure whether a statement takes an ACCESS EXCLUSIVE lock, say so and offer a safer option."

(Te dwie linie w promptcie pomagają wyciągnąć od modelu informacje o ryzyku.)

Typowe operacje schematu i jak prosić o bezpieczniejszy SQL

Otrzymuj nagrody za udostępnianie
Podziel się tym, co zbudowałeś w Koder.ai i zdobądź kredyty za stworzony content.

Małe zmiany schematu wciąż mogą zaszkodzić, jeśli powodują długie blokady, przepisywanie dużych tabel lub awarie w połowie. Gdy korzystasz z Claude Code do migracji, proś o SQL, który unika rewritów i utrzymuje aplikację działającą, dopóki baza się nie dogoni.

Dodawanie kolumn i domyślnych wartości (bez długich blokad)

Dodanie nullable kolumny jest zwykle bezpieczne. Dodanie kolumny z NOT NULL domyślną wartością może być ryzykowne na starszych wersjach Postgresa, bo może przepisać całą tabelę.

Bezpieczniejsze podejście to zmiana w dwóch krokach: dodaj kolumnę jako NULL bez domyślnej wartości, backfill w batchach, potem ustaw domyślną wartość dla nowych wierszy i dodaj NOT NULL po oczyszczeniu danych.

Jeśli musisz wymusić domyślną wartość natychmiast, wymagaj wyjaśnienia zachowania blokad dla twojej wersji Postgresa i planu awaryjnego, gdy wykonanie będzie dłuższe niż oczekiwano.

Indeksy, FK, ograniczenia, usuwanie

Dla indeksów na dużych tabelach poproś o CREATE INDEX CONCURRENTLY, żeby odczyty i zapisy mogły dalej działać. Wymagaj również notatki, że nie można go uruchomić wewnątrz transakcji, co oznacza, że twoje narzędzie migracyjne musi obsłużyć krok poza transakcją.

Dla kluczy obcych bezpieczniejsza ścieżka to zwykle dodanie ograniczenia jako NOT VALID, a późniejsze zwalidowanie. To przyspiesza początkowy krok, a nadal egzekwuje FK dla nowych zapisów.

Gdy zaostrzysz reguły (NOT NULL, UNIQUE, CHECK), poproś o „najpierw oczyść, potem egzekwuj”. Migracja powinna znaleźć złe wiersze, poprawić je, a dopiero potem włączyć ostrzejsze reguły.

Jeśli chcesz krótką checklistę do wklejenia do prompta, trzymaj ją zwartą:

  • Dołącz notatki o blokadach i spodziewanym czasie wykonania.
  • Używaj CONCURRENTLY dla dużych indeksów i zaznacz ograniczenia transakcyjne.
  • Preferuj NOT VALID, a potem VALIDATE dla nowych FK.
  • Oddziel backfill od wymuszania NOT NULL/UNIQUE.
  • Usuwaj obiekty dopiero po pełnym cyklu wydania i potwierdzeniu, że nikt ich nie czyta.

Promptowanie dla backfilli powolnych, stabilnych i możliwych do odzyskania

Backfille to miejsce, gdzie pojawia się większość bólu migracji, nie ALTER TABLE. Najbezpieczniejsze promptsy traktują backfill jak kontrolowane zadanie: mierzalne, możliwe do wznowienia i łagodne dla produkcji.

Zacznij od checks akceptacyjnych, które są łatwe do uruchomienia i trudne do zakwestionowania: spodziewane liczby wierszy, docelowy udział NULL i kilka losowych sprawdzeń (np. porównaj stare vs nowe wartości dla 20 losowych ID).

Następnie poproś o plan partycjonowania. Partie skracają blokady i zmniejszają niespodzianki. Dobre żądanie określa:

  • Jak partycjonować (zakresy PK lub okna czasowe jak created_at)
  • Docelowy rozmiar partii (np. 5,000–50,000 wierszy)
  • Czy spać między partiami na gorących tabelach
  • Żeby każda partia była jedną jasną transakcją (nie jedna ogromna transakcja)

Wymagaj idempotentności, bo backfille padają w połowie. SQL powinien być bezpieczny do ponownego uruchomienia bez duplikowania lub psucia danych. Typowe wzorce to "update tylko tam, gdzie new column IS NULL" lub reguła deterministyczna, gdzie ten sam input zawsze daje ten sam output.

Również napisz, jak aplikacja pozostaje poprawna podczas backfilla. Jeśli nowe zapisy nadal przychodzą, potrzebujesz mostu: dual-write w kodzie aplikacji, tymczasowy trigger lub logika read-fallback (czytaj nową gdy jest, w przeciwnym razie starą). Powiedz, które podejście możesz bezpiecznie wdrożyć.

Na koniec zaplanuj pauzę i wznowienie. Poproś o śledzenie postępu i checkpointy, np. mała tabela przechowująca ostatnie przetworzone ID i zapytanie raportujące postęp (zaktualizowane wiersze, ostatnie ID, czas rozpoczęcia).

Przykład: dodajesz users.full_name pochodzące z first_name i last_name. Bezpieczny backfill aktualizuje tylko wiersze, gdzie full_name IS NULL, działa po zakresach ID, zapisuje ostatnie zaktualizowane ID i utrzymuje poprawność nowych rejestracji przez dual-write dopóki nie skończysz.

Jak prosić o rollbacky, które działają w praktyce

Wysyłaj szybciej z Go i Postgres
Stwórz backend w Go z PostgreSQL z poziomu czatu, a potem ewoluuj schemat bezpiecznie.

Plan rollbacku to nie tylko "napisz down migrację." To dwa problemy: cofnięcie zmian schematu i obsługa danych, które zmieniły się gdy nowa wersja była live. Cofnięcie schematu jest często możliwe. Cofnięcie danych często nie jest, chyba że przygotowałeś snapshot.

Bądź eksplicytny co znaczy rollback dla twojej zmiany. Jeśli usuwasz kolumnę lub przepisywać wartości in-place, wymagaj realistycznej odpowiedzi typu: "Rollback przywraca kompatybilność aplikacji, ale oryginalne dane nie da się odzyskać bez snapshotu." Ta szczerość chroni.

Poproś o jasne wyzwalacze rollbacku, żeby nie było dyskusji podczas incydentu. Przykłady:

  • Wskaźnik błędów lub latencja przekracza próg przez 10 minut
  • Krytyczny plan zapytania pogarsza się (np. seq scan na gorącej tabeli)
  • Job backfill zostaje opóźniony o więcej niż N godzin
  • Sprawdzenia danych nie przechodzą (NULLy, duplikaty, brakujące wiersze)
  • Krok migracji blokuje zapisy dłużej niż X sekund

Wymagaj całego pakietu rollbacku, nie tylko SQL: DOWN SQL (tylko jeśli naprawdę bezpieczny), kroki w aplikacji potrzebne do kompatybilności i jak zatrzymać joby backgroundowe.

Ten wzorzec prompta zwykle wystarcza:

Produce a rollback plan for this migration.
Include: down migration SQL, app config/code switches needed for compatibility, and the exact order of steps.
State what can be rolled back (schema) vs what cannot (data) and what evidence we need before deciding.
Include rollback triggers with thresholds.

Zanim wypuścisz, wykonaj lekką "safety snapshot" by porównać przed i po:

  • Liczby wierszy dla dotkniętych tabel (i kluczowych podzbiorów)
  • Mały zestaw przykładowych zapytań z oczekiwanymi wynikami
  • Proste agregaty (sumy, min/max) dla zmienianych kolumn
  • Krótka lista ID do sprawdzenia przed i po

Bądź też jasny, kiedy rollbacku nie robić. Jeśli dodałeś nullable kolumnę i aplikacja dual-write, często bezpieczniej jest naprawić do przodu (hotfix, pauza backfilla, wznowienie) niż cofać i tworzyć większe rozbieżności.

Częste błędy przy migracjach wspomaganych AI

AI może szybko napisać SQL, ale nie widzi twojej produkcyjnej bazy. Większość awarii następuje, gdy prompt jest nieprecyzyjny i model wypełnia luki.

Typową pułapką jest pominięcie obecnego schematu. Jeśli nie wkleisz definicji tabeli, indeksów i ograniczeń, SQL może celować w nieistniejące kolumny lub pominąć regułę unikalności, co zamienia backfill w długotrwałą, blokującą operację.

Inny błąd to zrobienie expand, backfill i contract w jednym wdrożeniu. To odbiera punkt wyjścia. Jeśli backfill zajmie długo lub wywali się w połowie, zostaniesz z aplikacją oczekującą finalnego stanu.

Najczęstsze problemy:

  • Backfille, które nie są idempotentne i nie mają śledzenia postępu
  • Dodawanie NOT NULL, UNIQUE lub FK przed wyczyszczeniem i walidacją danych
  • Długotrwałe transakcje bez lock timeoutów lub statement timeoutów
  • Brak zapytań weryfikacyjnych, więc problemy chowają się aż użytkownicy je odkryją

Konkretny przykład: "zmień nazwę kolumny i zaktualizuj aplikację." Jeśli wygenerowany plan zmienia nazwę i robi backfill w jednej transakcji, długi backfill może trzymać blokady i złamać ruch. Bezpieczniejszy prompt wymusza małe partie, jawne timeouty i zapytania weryfikacyjne przed usunięciem starej ścieżki.

Co zweryfikować w stagingu przed wypuszczeniem

Staging to miejsce, gdzie znajdziesz problemy, które nigdy nie wychodzą na małej devowej bazie: długie blokady, niespodziewane NULLe, brakujące indeksy i zapomniane ścieżki kodu.

Najpierw sprawdź, że schemat po migracji odpowiada planowi: kolumny, typy, domyślne wartości, ograniczenia i indeksy. Szybkie oko to za mało. Jednen brakujący indeks może zamienić bezpieczny backfill w wolną katastrofę.

Następnie uruchom migrację na realistycznym zbiorze danych. Najlepiej kopia produkcji z zamaskowanymi wrażliwymi polami. Jeśli nie możesz, przynajmniej odwzoruj wolumen i hot spoty produkcyjne (duże tabele, szerokie wiersze, mocno indeksowane). Zapisz czasy dla każdego kroku, żeby wiedzieć, czego spodziewać się w produkcji.

Krótka checklista stagingu:

  • Schemat odpowiada planowi (kolumny, typy, ograniczenia, indeksy)
  • Zanotowane czasy na realistycznym wolumenie danych
  • Test kompatybilności: stary kod z nowym schematem i nowy kod ze starym schematem (jeśli plan przewiduje)
  • Uruchomione zapytania weryfikacyjne: udział NULL, liczby wierszy, sprawdzenia osieroconych wierszy dla nowych FK, próbne odczyty
  • Obserwowane sygnały operacyjne podczas runu: blokady, deadlocki, timeouty, wolne zapytania

Na koniec przetestuj rzeczywiste ścieżki użytkownika, nie tylko SQL. Twórz, aktualizuj i czytaj rekordy dotknięte zmianą. Jeśli plan to expand/contract, potwierdź, że oba schematy działają dopóki nie wykonasz finalnego cleanupu.

Realistyczny przykład: zmiana kolumny bez łamania użytkowników

Zarządzaj swoją bazą kodu
Zachowaj pełną kontrolę, eksportując źródło gdy workflow migracji jest ustalony.

Wyobraź sobie, że masz kolumnę users.name przechowującą pełne imiona jak "Ada Lovelace." Chcesz first_name i last_name, ale nie możesz zniszczyć rejestracji, profili ani panelu admina podczas rolloutu.

Zacznij od kroku expand, który jest bezpieczny nawet jeśli nie wypuścisz od razu kodu: dodaj nullable kolumny, zostaw starą kolumnę i unikaj długich blokad.

ALTER TABLE users ADD COLUMN first_name text;
ALTER TABLE users ADD COLUMN last_name text;

Następnie zaktualizuj zachowanie aplikacji, żeby wspierała oba schematy. W Wydaniu 1 aplikacja powinna czytać z nowych kolumn jeśli są wypełnione, w przeciwnym razie fallbackować do name, i zapisywać do obu, aby nowe dane były spójne.

Potem backfill. Uruchom job, który aktualizuje małe porcje wierszy na raz, zapisuje postęp i można go bezpiecznie wstrzymać. Na przykład: aktualizuj użytkowników gdzie first_name jest null w kolejności rosnących ID, po 1,000 na raz i loguj ile wierszy zmieniono.

Zanim zaostrzysz reguły, zweryfikuj w stagingu:

  • Nowe rejestracje wypełniają first_name i last_name i nadal ustawiają name
  • Istniejący użytkownicy wyświetlają się poprawnie nawet jeśli mają tylko name
  • Backfill można zatrzymać i wznowić bez duplikacji pracy
  • Po zakończeniu backfilla nie pozostają nieoczekiwane NULLe
  • Podstawowe zapytania na users nie są zauważalnie wolniejsze

Wydanie 2 przełącza odczyty na nowe kolumny. Dopiero potem dodawaj ograniczenia (np. SET NOT NULL) i usuwaj name, najlepiej w kolejnym, oddzielnym wdrożeniu.

Dla rollbacku trzymaj to prosto. Aplikacja dalej czyta name podczas przejścia, a backfill można zatrzymać. Jeśli musisz cofnąć Wydanie 2, przełącz odczyty z powrotem na name i pozostaw nowe kolumny dopóki wszystko nie będzie stabilne.

Kolejne kroki: zamień swoje promptsy w powtarzalny rytuał migracji

Traktuj każdą zmianę jak mały runbook. Celem nie jest perfekcyjny prompt, lecz rutyna, która wymusza właściwe szczegóły: schemat, ograniczenia, plan uruchomienia i rollback.

Ustandaryzuj, co musi zawierać każde żądanie migracji:

  • Obecny schemat i dokładna zmiana (tabele, kolumny, indeksy)
  • Ograniczenia i fakty o ruchu (rozmiar tabeli, tempo zapisów, dozwolony downtime)
  • Sekwencja wydań (expand, deploy aplikacji, backfill, contract)
  • Jak będziesz obserwować postęp (zapytania/metryki, oczekiwany czas)
  • Kroki rollbacku (co cofnąć najpierw, jakie dane mogą zostać)

Zdecyduj, kto odpowiada za każdy krok zanim ktoś uruchomi SQL. Prosty podział zapobiega "wszyscy myśleli, że ktoś inny to zrobi": deweloperzy odpowiadają za prompt i kod migracji, ops za timing produkcji i monitoring, QA weryfikuje zachowanie w stagingu i edge-cases, a jedna osoba podejmuje ostateczne go/no-go.

Jeśli budujesz aplikacje przez czat, pomocne jest opisanie sekwencji zanim wygenerujesz SQL. Dla zespołów korzystających z Koder.ai, Planning Mode to naturalne miejsce, żeby zapisać tę sekwencję, a snapshoty i rollback zmniejszają powierzchnię rażenia, jeśli coś niespodziewanego zdarzy się podczas rolloutu.

Po wypuszczeniu zaplanuj cleanup contract natychmiast, póki kontekst jest świeży, żeby stare kolumny i tymczasowy kod kompatybilności nie wisiały miesiącami.

Często zadawane pytania

Dlaczego zmiany schematu PostgreSQL psują produkcję, nawet gdy SQL wygląda prosto?

Zmiana schematu jest ryzykowna, gdy kod aplikacji, stan bazy i timing wdrożenia przestają być zgodne.

Typowe tryby awarii:

  • Stary kod aplikacji trafia na nową kolumnę/konstraint i się wywala
  • Migracja blokuje gorące tabele i żądania kończą się timeoutami
  • „Mała” zmiana cicho nadpisuje lub usuwa dane
  • Prace nad indeksami/konstraintami trwają dłużej niż się spodziewano i powodują wolne zapytania
Jaki jest najbezpieczniejszy domyślny sposób zmiany schematu bez downtime?

Użyj podejścia expand/contract:

  • Expand: dodaj nowe kolumny/tabele/indeksy w sposób kompatybilny
  • Compatibility: wdroż kod aplikacji, który potrafi czytać/zapisywać oba kształty danych
  • Backfill: kopiuj dane w małych partiach z checkpointami
  • Contract: zaostrzyć reguły i usunąć stare pola dopiero po pełnym cyklu wydania

To pozwala obu wersjom aplikacji działać podczas rolloutu.

Jakie dodatkowe ryzyka wprowadzają migracje generowane przez AI?

Model może wygenerować poprawny SQL, który jednak jest niebezpieczny dla twojego obciążenia.

Typowe ryzyka specyficzne dla AI:

  • Zgadywanie nazw tabel/kolumn lub pomijanie istotnego ograniczenia
  • Proponowanie jednorazowej migracji "big bang", która odbiera opcje rollbacku
  • Ignorowanie zachowania blokad, ograniczeń transakcyjnych i długotrwałych budów indeksów
  • Unikanie realistycznego planu rollbacku (szczególnie gdy dane są przekształcane lub usuwane)

Traktuj wynik AI jako szkic i wymagaj planu uruchomienia, zapytań weryfikacyjnych i kroków rollbacku.

Co powinienem wkleić do prompta, żeby Claude Code nie zgadywał?

Wklej tylko fakty, od których zależy migracja:

  • Odpowiednie fragmenty CREATE TABLE (plus indeksy, FK, UNIQUE/CHECK, triggery)
  • Wersja Postgresa i sposób uruchamiania migracji (jedna transakcja vs wiele kroków)
  • Skala: liczba wierszy, rozmiar tabeli, tempo zapisów, ruch szczytowy
  • Jak aplikacja używa danych (kluczowe odczyty/zapisy/prace backgroundowe)
  • Twarde ograniczenia (brak downtime, unikać pełnych przepisów tabeli, limity blokad)
  • Rezultaty: UP SQL + zapytania weryfikacyjne + plan rollback + runbook

To zapobiega zgadywaniu i wymusza właściwą kolejność działań.

Czy powinienem łączyć zmiany schematu i backfill w jednej migracji?

Domyślna zasada: separuj je.

Praktyczny podział:

  • Migracja 1: expand schematu (nowe kolumny/tabele, ewentualnie NOT VALID constraints)
  • Wdrożenie aplikacji: kod kompatybilności (read-fallback lub dual-write)
  • Job backfill: skompresowane aktualizacje z trackingiem postępu
  • Migracja 2: contract (walidacja constraintów, ustawienie NOT NULL, usunięcie starych kolumn)

Łączenie wszystkiego utrudnia diagnozę i rollback.

Jak dodać nową kolumnę z domyślną wartością bez długich blokad?

Preferuj ten wzorzec:

  1. ADD COLUMN ... NULL bez domyślnej wartości (szybkie)
  2. Backfill w partiach
  3. Ustawienie domyślnej wartości dla nowych wierszy
  4. Dodanie NOT NULL dopiero po weryfikacji

Dodanie nie-null default może być ryzykowne w niektórych wersjach, bo może przepisać całą tabelę. Jeśli potrzebujesz natychmiastowego defaultu, wymagaj wyjaśnienia zachowania blokad dla twojej wersji Postgresa i planu awaryjnego.

Kiedy używać CREATE INDEX CONCURRENTLY i jaki jest haczyk?

Poproś o:

  • CREATE INDEX CONCURRENTLY dla dużych/gorących tabel
  • Uwagę, że nie można tego uruchomić wewnątrz bloku transakcyjnego (twoje narzędzie musi to obsłużyć)
  • Spodziewany czas wykonania i metryki do monitorowania (lock waits, latencja zapytań)

Dla weryfikacji dołącz szybkie sprawdzenie, że indeks istnieje i jest używany (np. porównanie EXPLAIN przed/po w stagingu).

Jaki jest najbezpieczniejszy sposób dodania klucza obcego w dużej tabeli?

Użyj NOT VALID najpierw, potem VALIDATE:

  • Dodaj FK jako NOT VALID, żeby początkowy krok był mniej inwazyjny
  • Uruchom VALIDATE CONSTRAINT w osobnym kroku, gdy możesz go obserwować

To nadal wymusza FK dla nowych zapisów, a jednocześnie pozwala kontrolować kosztowną walidację.

Jak poprosić o backfill, który nie „stopi” produkcji i da się wznowić?

Dobry backfill jest partycjonowany, idempotentny i możliwy do wznowienia.

Praktyczne wymagania:

  • Partycjonuj po zakresach PK lub oknach czasowych
  • Aktualizuj tylko wiersze, które tego nadal potrzebują (np. WHERE new_col IS NULL)
  • Trzymaj partie w krótkich transakcjach; opcjonalnie dawkuj przerwy między partiami
  • Śledź postęp (ostatnie przetworzone ID, zaktualizowane wiersze, czas startu)
  • Zapewnij, że aplikacja pozostaje poprawna podczas backfilla (dual-write, trigger lub read-fallback)

To powoduje, że backfille przetrwają realny ruch produkcyjny.

Jak wygląda realistyczny plan rollbacku dla zmian schematu?

Domyślny cel rollbacku: szybko przywrócić kompatybilność aplikacji, nawet jeśli dane nie zostaną idealnie cofnięte.

Działający plan rollback powinien zawierać:

  • Czy DOWN SQL jest naprawdę bezpieczny; jeśli nie, przygotuj runbook zamiast SQL
  • Dokładną kolejność: zatrzymaj/pauzuj joby backfill, wdroż zmianę w aplikacji, potem kroki schematu
  • Jasne wyzwalacze rollbacku (wzrost błędów, latencji, lock waits, nieudane sprawdzenia danych)
  • Oświadczenie, co da się cofnąć (schemat) vs czego nie (dane)

Często najbezpieczniejszy rollback to przełączenie odczytów z powrotem na stare pole, pozostawiając nowe kolumny.

Related posts