행이 많은 주문 테이블에 `fulfillment_status` 필수 컬럼을 한 번에 추가하고 모든 행을 갱신하면 어떻게 될까요. 로컬의 빈 DB에서는 즉시 끝나지만 운영에서는 긴 갱신과 잠금이 쓰기 요청을 밀어낼 수 있습니다. RLS(Row Level Security)는 사용자별로 읽고 고칠 수 있는 행을 제한하는 정책입니다. 새 컬럼과 이 정책의 배포 순서를 틀리면 구버전의 정상 요청까지 막힐 수 있습니다.
핵심 운영 스키마 변경은 새 구조 추가, 작은 단위 백필, 읽기 전환, 제약 강화, 옛 경로 제거의 순서로 진행합니다. 백필은 기존 행의 빈 값을 새 규칙에 맞게 채우는 작업입니다. 에이전트에는 SQL 작성보다 현재 변경 이력과 쓰기 경로를 조사하게 하고, 원격 적용은 행 수·잠금 시간·롤백 조건을 확인한 담당자가 승인합니다.
원격 SQL이 아닌 계획 모드에서 시작합니다
테이블 규모와 배치 수치는 모두 예시로만 다루며 실제 값은 운영자가 제공합니다.
claude --permission-mode plan
주문 상태 컬럼의 단계별 마이그레이션을 계획하세요. 수정·원격 적용 금지.
읽기: CLAUDE.md, supabase/migrations/**, 주문 쓰기 코드, RLS 테스트, 배포 규칙
허용: Read, Grep, 로컬 Supabase 명령 조회
금지: 원격 SQL·Dashboard 변경, 운영 백필, 비밀 조회
반환: 호환 단계 / 잠금 위험 / 검증 SQL / 승인 지점 / 롤백 한계
중단: 실제 행 수·트래픽·구버전 호환을 확인할 수 없을 때
DB 담당자가 단계와 기준을 승인한 뒤 파일만 작성합니다
승인된 첫 산출물은 기존 열을 지우지 않고 새 구조만 더하는 마이그레이션 파일과 로컬 테스트입니다. DB 담당자는 `supabase db reset`, 프로젝트 테스트, 마이그레이션 변경 차이를 확인하고 나서야 다음 단계로 넘깁니다.
원격 상태가 이력과 다르거나 RLS 역할 테스트가 실패하면 중단합니다. 재개 기록에는 마지막 적용 단계, 남은 백필과 다음 승인자를 적습니다.
한 번에 완성하려던 SQL이 배포 순서를 깨뜨립니다
처음 제안은 컬럼을 `not null default pending`으로 추가한 뒤 모든 주문을 상태별로 갱신하는 한 파일이었습니다. 앱 서버가 순차 교체되는 동안에는 구버전과 신버전이 함께 씁니다. 새 필드만 읽는 코드가 먼저 나가면 미백필 행에서 오류가 나고, 제약이 먼저 나가면 구버전 쓰기가 실패합니다.
마이그레이션은 최종 스키마 모양이 아니라 전환 중에도 양쪽 버전이 살아 있는 경로를 설계해야 합니다. 이 사례에서는 빈 값을 허용하는 새 컬럼 추가가 첫 배포이며 기존 `shipped_at` 값을 읽는 코드는 그대로 둡니다.
에이전트에는 운영 적용 대신 증거 목록을 요청합니다
조사 범위에는 마이그레이션 파일, 주문 쓰기 코드, RLS 정책, 인덱스와 배포 순서가 들어갑니다. 운영 SQL 실행과 원격 대시보드 변경은 금지합니다. Supabase 공식 문서는 마이그레이션을 사용하기 시작한 뒤 원격 대시보드에서 스키마를 직접 바꾸면 이력과 어긋나 `db push` 동기화 오류를 만들 수 있다고 안내합니다.
대상: public.orders의 fulfillment_status 추가
현재 행 수: 약 1,200,000
허용: 로컬 DB reset, 스키마 diff, 테스트 작성
금지: 원격 SQL, Dashboard 스키마 변경, 운영 백필
반환: 구버전 호환성, 잠금 위험, 단계별 SQL, RLS 검증, 중단 조건
첫 배포는 되돌리기 쉬운 추가만 합니다
첫 파일은 컬럼과 조회용 부분 인덱스만 추가합니다. 트랜잭션 잠금이 길어지면 공식 문서가 안내하는 `lock_timeout` 조정을 검토할 수 있지만, 시간을 무작정 늘리기보다 트래픽이 낮은 창과 작은 변경을 먼저 고릅니다. 로컬에서는 `supabase db reset`으로 처음부터 모든 이력이 재현되는지 확인합니다.
alter table public.orders
add column fulfillment_status text;
create index if not exists orders_fulfillment_pending_idx
on public.orders (created_at)
where fulfillment_status is null;
작은 백필 뒤 실제 지연을 확인합니다
백필 작업은 기본키 순서로 작은 범위만 갱신하고 처리한 마지막 식별자를 기록합니다. 첫 배치의 잠금 대기, API 쓰기 지연, 오류와 복제 지연을 관찰해 다음 크기를 정합니다. 서비스가 승인한 한계를 넘으면 중단합니다. 같은 배치를 다시 실행해도 이미 값이 있는 행은 건너뛰도록 만듭니다.
전체 백필 후 빈 값이 없는지 확인하고 신버전이 새 컬럼과 옛 값을 동시에 쓴 기간을 둡니다. 그 다음 읽기를 새 컬럼으로 전환합니다. `not null` 제약은 마지막 호환 기간이 끝난 뒤 별도 마이그레이션으로 추가합니다.
정상 사용자는 통과해도 RLS가 새 컬럼 때문에 샐 수 있습니다
로컬 테스트는 소유자가 자기 주문을 읽고 수정하는 경우, 다른 사용자가 같은 주문 ID를 요청하는 경우, 인증 없는 요청의 세 역할로 나눕니다. 서비스 역할 키로만 테스트하면 RLS를 우회하므로 실제 클라이언트 권한을 검증하지 못합니다. 다른 사용자의 조회가 0행이고 갱신이 거절되는지까지 확인해야 합니다.
정책 변경과 데이터 백필을 한 배포에 묶지 않습니다. RLS 테스트가 실패하면 컬럼 추가는 유지해도 읽기 전환과 제약 강화는 중단할 수 있어야 합니다.
완료는 파일 생성이 아니라 원격 이력의 일치입니다
로컬 `db reset`, 테스트 환경 적용, 애플리케이션 양방향 호환, 백필 수치, RLS 역할 테스트를 차례로 통과한 뒤에만 원격 적용을 승인합니다. 이미 원격 대시보드에서 변경이 발생했다면 먼저 공식 절차로 차이를 캡처하고 이력을 맞춘 뒤 새 변경을 시작합니다.
2026년 7월 29일 Supabase 공식 마이그레이션 문서를 기준으로 확인했습니다. 행 수와 허용 지연은 서비스마다 다르므로 1만 건이라는 숫자를 정답으로 복사하지 말고, 첫 작은 배치의 실제 시간과 운영 중단 기준으로 크기를 정해야 합니다.