Claude Code로 PostgreSQL 마이그레이션 시 확장-수축 변경, 백필, 롤백 계획을 안전하게 수행하는 프롬프트와 출시 전에 스테이징에서 확인해야 할 항목을 알아보세요.

스키마 변경은 앱 코드, 데이터베이스 상태, 배포 타이밍이 일치하지 않을 때 위험해집니다.\n\n일반적인 실패 양상:\n\n- 구버전 앱이 새 컬럼이나 제약조건을 만나 오류 발생\n- 바쁜 테이블에서 마이그레이션이 강한 락을 걸어 요청이 타임아웃됨\n- “작은” 변경이 데이터 일부를 조용히 덮어쓰거나 삭제함\n- 인덱스/제약 작업이 예상보다 오래 걸려 쿼리가 느려짐
가장 안전한 기본 방법은 확장/수축(expand/contract) 패턴을 따르는 것입니다:\n\n- Expand(확장): 호환 가능한 방식으로 새 nullable 컬럼/테이블/인덱스를 추가\n- Compatibility(호환): 앱이 옛 구조와 새 구조를 모두 읽고 쓸 수 있도록 배포\n- Backfill(백필): 체크포인트를 두고 작은 배치로 데이터를 복사\n- Contract(수축): 전체 릴리스 사이클 후 제약을 강화하고 오래된 필드를 제거\n\n이 방식은 롤아웃 중에 구버전과 신버전 앱이 동시에 작업할 수 있게 해 줍니다.
모델은 유효한 SQL을 생성할 수 있지만 우리의 워크로드에는 안전하지 않을 수 있습니다.\n\nAI 관련 위험 예시:\n\n- 테이블/컬럼 이름을 추측하거나 중요한 제약을 놓침\n- 롤백 옵션을 없애는 단일 “빅뱅” 마이그레이션 제안\n- 락 동작, 트랜잭션 한계, 오래 걸리는 인덱스 빌드를 무시\n- 데이터 변형/삭제 시 손쉬운 롤백을 약속하는 식의 생략\n\nAI 출력은 초안으로 다루고, 실행 전에는 반드시 런플랜, 체크, 롤백 단계를 요구하세요.
모델이 추측하지 않도록 프롬프트에 필요한 사실만 넣으세요:\n\n- 관련 CREATE TABLE 스니펫(인덱스, FK, UNIQUE/CHECK 제약, 트리거 포함)\n- Postgres 버전과 마이그레이션 실행 방식(단일 트랜잭션 vs 여러 단계)\n- 스케일: 행 수, 테이블 크기, 쓰기율, 피크 트래픽\n- 앱이 데이터를 어떻게 사용하는지(핵심 읽기/쓰기/잡)\n- 하드 제약(다운타임 금지, 테이블 리라이팅 회피 등)\n- 납품물: UP SQL + 검증 쿼리 + 롤백 플랜 + 런북\n\n이렇게 하면 모델의 추측을 줄이고 올바른 순서를 강제할 수 있습니다.
기본 규칙: 분리하세요.\n\n실용적인 분할 예:\n\n- 마이그레이션 1: 스키마 확장(새 컬럼/테이블, 필요하면 NOT VALID 제약)\n- 앱 배포: 호환성 코드(읽기 폴백 또는 듀얼-라이트)\n- 백필 작업: 진행 추적이 있는 배치 업데이트\n- 마이그레이션 2: 수축(제약 검증, NOT NULL 설정, 오래된 컬럼 삭제)\n\n모든 것을 한 번에 묶으면 실패 시 진단과 롤백이 더 어려워집니다.
권장 패턴은 다음과 같습니다:\n\n1) ADD COLUMN ... NULL (기본값 없이, 빠름)\n2) 배치로 백필\n3) 새 행을 위한 DEFAULT 설정\n4) 검증 후 NOT NULL 추가\n\n일부 Postgres 버전에서는 non-null 기본값 추가가 테이블 전체를 다시 쓰게 되어 위험할 수 있습니다. 즉시 기본값이 필요하면 해당 버전의 락 동작을 설명하고 대체 계획을 요구하세요.
대형/핫 테이블에는 CREATE INDEX CONCURRENTLY를 요청하세요. 주의할 점:\n\n- CONCURRENTLY는 트랜잭션 블록 내부에서 실행할 수 없습니다(툴링이 이를 지원해야 함)\n- 예상 실행 시간과 모니터링 항목(락 대기, 쿼리 지연)을 포함하세요\n\n검증으로는 인덱스 존재 여부와 사용 여부(예: 스테이징에서 EXPLAIN 전후 비교)를 포함시키세요.
큰 테이블에 FK를 추가할 때는 NOT VALID로 먼저 추가하고 나중에 검증하세요:\n\n- 초기 단계에서는 NOT VALID로 추가해 파급력을 낮춤\n- 여유가 있을 때 별도 단계로 VALIDATE CONSTRAINT 실행\n\n이 방식은 새 쓰기에 대해서는 FK를 강제하면서도 비용이 큰 검증을 통제할 수 있게 해줍니다.
좋은 백필은 배치화되어 있고, 멱등적이며, 재시작 가능해야 합니다.\n\n실용적 요구사항:\n\n- 기본키 범위나 시간 창(created_at)으로 배치화\n- 아직 처리되지 않은 행만 업데이트(WHERE new_col IS NULL)\n- 배치는 짧은 트랜잭션으로 실행; 필요시 배치 사이에 sleep\n- 진행 상태 추적(마지막 처리 ID, 업데이트된 행 수, 시작 시간)\n- 백필 동안 앱이 올바르게 작동하도록 듀얼-라이트, 트리거, 또는 읽기 폴백 사용\n\n이렇게 설계하면 실제 트래픽에서도 백필을 견딜 수 있습니다.
롤백의 현실적인 목표는 앱 호환성을 빠르게 복구하는 것입니다. 데이터 전체 복원이 항상 가능하진 않습니다.\n\n작동하는 롤백 플랜에는 다음이 포함되어야 합니다:\n\n- DOWN SQL이 정말 안전한지 여부; 안전하지 않다면 런북으로 대체\n- 정확한 순서: 백필 작업 중지/일시중지, 앱 구성/코드 전환, 스키마 단계\n- 명확한 롤백 트리거(오류율·지연·락 대기 등 임계값)\n- 롤백 가능한 항목(스키마)과 불가능한 항목(데이터)에 대한 명시\n\n많은 경우 가장 안전한 롤백은 새 컬럼을 남겨두고 읽기를 다시 옛 필드로 되돌리는 방식입니다.