1분

Claude Code PostgreSQL 마이그레이션: 안전한 변경을 위한 프롬프트

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

Claude Code PostgreSQL 마이그레이션: 안전한 변경을 위한 프롬프트

PostgreSQL 스키마 변경의 위험 요소

PostgreSQL 스키마 변경은 실제 트래픽과 데이터에 마주치기 전까지는 단순해 보입니다. 위험한 부분은 보통 SQL 자체가 아니라 앱 코드, 데이터베이스 상태, 배포 타이밍이 어긋날 때 생깁니다.

대부분의 실패는 현실적이고 고통스럽습니다. 예를 들어, 배포가 깨져서 구버전 코드가 새 컬럼을 건드려 오류가 나거나, 마이그레이션이 뜨거운 테이블에 락을 걸어 타임아웃이 급증하거나, "빠른" 변경이 조용히 데이터를 삭제하거나 덮어쓰는 경우입니다. 심지어 아무것도 충돌하지 않더라도 잘못된 기본값, 깨진 제약조건, 완전히 생성되지 못한 인덱스 같은 미묘한 버그가 배포될 수 있습니다.

AI가 생성한 마이그레이션은 또 다른 위험층을 추가합니다. 도구는 유효한 SQL을 만들 수 있지만, 귀하의 워크로드, 데이터 볼륨, 릴리스 프로세스에는 안전하지 않을 수 있습니다. 테이블 이름을 추측하거나, 장기 실행 락을 놓치거나, 다운 마이그레이션이 어렵다고 롤백을 흐리게 설명할 수 있습니다. Claude Code를 마이그레이션에 사용한다면 가드레일과 구체적 컨텍스트가 필요합니다.

이 글에서 "안전"하다고 할 때는 세 가지를 의미합니다:

  • 이전 및 새로운 앱 버전이 롤아웃 중에도 함께 동작할 수 있다(역호환성).
  • 진행 상황을 측정하고 문제를 빠르게 탐지할 수 있다(관찰 가능성).
  • 압박 상황에서 실행 가능한 롤백 플랜이 있다(가역성).

목표는 마이그레이션을 일상적인 작업으로 만드는 것입니다: 예측 가능하고, 테스트 가능하며, 지루할 정도로 안전하게.

자주 묻는 질문

SQL이 단순해 보여도 왜 PostgreSQL 스키마 변경이 프로덕션을 깨뜨리나요?

스키마 변경은 앱 코드, 데이터베이스 상태, 배포 타이밍이 일치하지 않을 때 위험해집니다.

일반적인 실패 양상:

  • 구버전 앱이 새 컬럼이나 제약조건을 만나 오류 발생
  • 바쁜 테이블에서 마이그레이션이 강한 락을 걸어 요청이 타임아웃됨
  • “작은” 변경이 데이터 일부를 조용히 덮어쓰거나 삭제함
  • 인덱스/제약 작업이 예상보다 오래 걸려 쿼리가 느려짐
다운타임 없이 스키마를 변경하는 가장 안전한 기본 방식은 무엇인가요?

가장 안전한 기본 방법은 확장/수축(expand/contract) 패턴을 따르는 것입니다:

  • Expand(확장): 호환 가능한 방식으로 새 nullable 컬럼/테이블/인덱스를 추가
  • Compatibility(호환): 앱이 옛 구조와 새 구조를 모두 읽고 쓸 수 있도록 배포
  • Backfill(백필): 체크포인트를 두고 작은 배치로 데이터를 복사
  • Contract(수축): 전체 릴리스 사이클 후 제약을 강화하고 오래된 필드를 제거

이 방식은 롤아웃 중에 구버전과 신버전 앱이 동시에 작업할 수 있게 해 줍니다.

AI가 생성한 마이그레이션은 어떤 추가 위험을 가져오나요?

모델은 유효한 SQL을 생성할 수 있지만 우리의 워크로드에는 안전하지 않을 수 있습니다.

AI 관련 위험 예시:

  • 테이블/컬럼 이름을 추측하거나 중요한 제약을 놓침
  • 롤백 옵션을 없애는 단일 “빅뱅” 마이그레이션 제안
  • 락 동작, 트랜잭션 한계, 오래 걸리는 인덱스 빌드를 무시
  • 데이터 변형/삭제 시 손쉬운 롤백을 약속하는 식의 생략

AI 출력은 초안으로 다루고, 실행 전에는 반드시 런플랜, 체크, 롤백 단계를 요구하세요.

Claude Code가 추측하지 않도록 프롬프트에 무엇을 붙여넣어야 하나요?

모델이 추측하지 않도록 프롬프트에 필요한 사실만 넣으세요:

  • 관련 CREATE TABLE 스니펫(인덱스, FK, UNIQUE/CHECK 제약, 트리거 포함)
  • Postgres 버전과 마이그레이션 실행 방식(단일 트랜잭션 vs 여러 단계)
  • 스케일: 행 수, 테이블 크기, 쓰기율, 피크 트래픽
  • 앱이 데이터를 어떻게 사용하는지(핵심 읽기/쓰기/잡)
  • 하드 제약(다운타임 금지, 테이블 리라이팅 회피 등)
  • 납품물: UP SQL + 검증 쿼리 + 롤백 플랜 + 런북

이렇게 하면 모델의 추측을 줄이고 올바른 순서를 강제할 수 있습니다.

스키마 변경과 백필을 한 마이그레이션에 합쳐야 할까요?

기본 규칙: 분리하세요.

실용적인 분할 예:

  • 마이그레이션 1: 스키마 확장(새 컬럼/테이블, 필요하면 NOT VALID 제약)
  • 앱 배포: 호환성 코드(읽기 폴백 또는 듀얼-라이트)
  • 백필 작업: 진행 추적이 있는 배치 업데이트
  • 마이그레이션 2: 수축(제약 검증, NOT NULL 설정, 오래된 컬럼 삭제)

모든 것을 한 번에 묶으면 실패 시 진단과 롤백이 더 어려워집니다.

기본값을 가진 새 컬럼을 추가할 때 긴 락을 피하려면 어떻게 하나요?

권장 패턴은 다음과 같습니다:

  1. ADD COLUMN ... NULL (기본값 없이, 빠름)
  2. 배치로 백필
  3. 새 행을 위한 DEFAULT 설정
  4. 검증 후 NOT NULL 추가

일부 Postgres 버전에서는 non-null 기본값 추가가 테이블 전체를 다시 쓰게 되어 위험할 수 있습니다. 즉시 기본값이 필요하면 해당 버전의 락 동작을 설명하고 대체 계획을 요구하세요.

`CREATE INDEX CONCURRENTLY`는 언제 사용해야 하고 단점은 무엇인가요?

대형/핫 테이블에는 CREATE INDEX CONCURRENTLY를 요청하세요. 주의할 점:

  • CONCURRENTLY는 트랜잭션 블록 내부에서 실행할 수 없습니다(툴링이 이를 지원해야 함)
  • 예상 실행 시간과 모니터링 항목(락 대기, 쿼리 지연)을 포함하세요

검증으로는 인덱스 존재 여부와 사용 여부(예: 스테이징에서 EXPLAIN 전후 비교)를 포함시키세요.

대형 테이블에 외래키를 추가할 때 가장 안전한 방법은 무엇인가요?

큰 테이블에 FK를 추가할 때는 NOT VALID로 먼저 추가하고 나중에 검증하세요:

  • 초기 단계에서는 NOT VALID로 추가해 파급력을 낮춤
  • 여유가 있을 때 별도 단계로 VALIDATE CONSTRAINT 실행

이 방식은 새 쓰기에 대해서는 FK를 강제하면서도 비용이 큰 검증을 통제할 수 있게 해줍니다.

프로덕션을 멈추지 않는 재개 가능한 백필을 요청하려면 어떻게 프롬프트해야 하나요?

좋은 백필은 배치화되어 있고, 멱등적이며, 재시작 가능해야 합니다.

실용적 요구사항:

  • 기본키 범위나 시간 창(created_at)으로 배치화
  • 아직 처리되지 않은 행만 업데이트(WHERE new_col IS NULL)
  • 배치는 짧은 트랜잭션으로 실행; 필요시 배치 사이에 sleep
  • 진행 상태 추적(마지막 처리 ID, 업데이트된 행 수, 시작 시간)
  • 백필 동안 앱이 올바르게 작동하도록 듀얼-라이트, 트리거, 또는 읽기 폴백 사용

이렇게 설계하면 실제 트래픽에서도 백필을 견딜 수 있습니다.

스키마 변경에 대한 현실적인 롤백 플랜은 어떻게 생겼나요?

롤백의 현실적인 목표는 앱 호환성을 빠르게 복구하는 것입니다. 데이터 전체 복원이 항상 가능하진 않습니다.

작동하는 롤백 플랜에는 다음이 포함되어야 합니다:

  • DOWN SQL이 정말 안전한지 여부; 안전하지 않다면 런북으로 대체
  • 정확한 순서: 백필 작업 중지/일시중지, 앱 구성/코드 전환, 스키마 단계
  • 명확한 롤백 트리거(오류율·지연·락 대기 등 임계값)
  • 롤백 가능한 항목(스키마)과 불가능한 항목(데이터)에 대한 명시

많은 경우 가장 안전한 롤백은 새 컬럼을 남겨두고 읽기를 다시 옛 필드로 되돌리는 방식입니다.

Related posts