
Supabase DB Migration이 꼬여서 배포를 못 하고 있습니다
팀원과 동시에 DB 스키마를 수정했더니 `supabase db push`가 실패합니다. 마이그레이션 파일 충돌 원인과 `migration repair` 명령어로 해결하는 방법을 정리해봤습니다.

팀원과 동시에 DB 스키마를 수정했더니 `supabase db push`가 실패합니다. 마이그레이션 파일 충돌 원인과 `migration repair` 명령어로 해결하는 방법을 정리해봤습니다.
서버를 끄지 않고 배포하는 법. 롤링, 카나리, 블루-그린의 차이점과 실제 구축 전략. DB 마이그레이션의 난제(팽창-수축 패턴)와 AWS CodeDeploy 활용법까지 심층 분석합니다.

새벽엔 낭비하고 점심엔 터지는 서버 문제 해결기. '택시 배차'와 '피자 배달' 비유로 알아보는 오토 스케일링과 서버리스의 차이, 그리고 Spot Instance를 활용한 비용 절감 꿀팁.

내 서버가 해킹당하지 않는 이유. 포트와 IP를 검사하는 '패킷 필터링'부터 AWS Security Group까지, 방화벽의 진화 과정.

왜 넷플릭스는 멀쩡한 서버를 랜덤하게 꺼버릴까요? 시스템의 약점을 찾기 위해 고의로 장애를 주입하는 카오스 엔지니어링의 철학과 실천 방법(GameDay)을 소개합니다.

새로운 기능을 위해 profiles 테이블에 nickname 컬럼을 추가했습니다.
로컬에서 supabase db diff로 마이그레이션 파일을 만들고, 잘 돌아가는 걸 확인했습니다.
그리고 자신 있게 배포 명령어를 쳤습니다.
supabase db push
그런데 터미널이 빨간색 에러를 토해냈습니다.
Error: migration 20251212000000_add_nickname.sql mismatch
Remote history is different from local history.
"아니, 로컬 히스토리랑 리모트랑 다르다니? 내가 방금 짰는데?" 알고 보니 다른 팀원(혹은 어제의 나)이 서버 DB를 건드린 적이 있었던 겁니다.
저는 마이그레이션 충돌이 Git 코드 충돌(Merge Conflict)과 같은 건 줄 알았습니다. 그냥 코드 합치듯이 SQL 파일 합치면 되는 거 아닌가?
하지만 데이터베이스는 상태(State)를 가집니다.
Git은 코드가 꼬이면 다시 풀면 되지만,
DB는 ALTER TABLE을 순서대로 실행해야지, 순서가 꼬이면 데이터가 날아가거나 테이블이 깨집니다.
그래서 Supabase(Postgres)는 supabase_migrations라는 테이블에 "지금까지 실행한 마이그레이션 파일 목록"을 엄격하게 기록해둡니다.
이 기록(History)과 내 로컬 파일 목록이 단 하나라도 다르면, 안전을 위해 배포를 거부하는 것입니다.
이걸 "블록체인 장부"에 비유하니 이해가 됐습니다.
서버 장부에는 [A -> B -> C]라고 적혀 있는데,
제 로컬 파일에는 [A -> B -> D]라고 되어 있으면,
"어? 너 장부 조작했어? 너랑은 거래 안 해!" 하고 거부하는 겁니다.
제가 C를 건너뛰고 D를 들이밀었거나, C 내용을 몰래 바꿔치기했기 때문입니다.
상황: 서버엔 2025..._team_change.sql이 적용되어 있는데, 내 로컬엔 그 파일이 없습니다.
가장 정석적인 방법은 서버의 변경 사항을 로컬로 가져와서 싱크를 맞추는 겁니다.
supabase db pull
이러면 서버에만 있던 스키마 변경 사항이 내 로컬 DB에 반영됩니다.
그리고 나서 다시 diff를 뜨면, 내 변경 사항만 깔끔하게 새 파일로 나옵니다.
만약 "서버에 적용된 건 실수니까 무시하고, 내 걸로 덮어씌워야 해!" 라는 상황이라면? (예: 개발 서버라서 데이터를 날려도 될 때)
migration repair 명령어를 씁니다. 이건 서버의 장부를 강제로 고치는 위험한 명령어입니다.
# 서버에서 특정 버전을 '실행됨(applied)' 처리 (실제 SQL 실행 X)
supabase migration repair --status applied 20251212000000
반대로, 실제로는 실행 안 됐는데 실행된 것처럼 되어 있다면:
# 서버 기록에서 특정 버전을 삭제 (reverted)
supabase migration repair --status reverted 20251212000000
이 명령어를 통해 로컬과 리모트의 "버전 기록"을 강제로 일치시킨 후 push 하는 것입니다.
혼자 개발할 땐 db push 막 써도 됩니다. 하지만 팀이라면 규칙이 필요합니다.
migrations 파일로만 수정하세요.추천 워크플로우:
supabase start.localhost:54323)에서 테이블 수정.supabase db diff -f add_users -> 마이그레이션 파일 생성.supabase db push 실행.마이그레이션을 배포했는데 심각한 버그가 있어서 revert해야 한다면 어떻게 할까요?
Git은 revert 커밋을 하면 코드가 되돌아가지만, DB는 그렇지 않습니다.
20251212_add_table.sql을 취소하려면, 그 반대인 DROP TABLE을 수행하는 새로운 마이그레이션 파일을 만들어야 합니다.
Supabase CLI는 기본적으로 Up migration만 생성해줍니다.
그래서 중요한 스키마 변경 시에는 수동으로 Down 스크립트를 미리 준비해두는 것이 좋습니다. (혹은 db diff로 지우는 변경사항을 생성하거나요.)
가장 좋은 건 "롤백할 일이 없게 만드는 것"입니다. 이때 Expand and Contract 패턴을 씁니다.
username 컬럼을 email로 이름을 바꾸고 싶었습니다.
순진하게 ALTER TABLE users RENAME COLUMN username TO email을 날렸습니다.
결과: 서비스 중단 (Downtime).
배포되는 순간, 아직 업데이트되지 않은 구버전 API 서버들은 여전히 username을 찾고 있었고, 쿼리 에러가 500 Internal Server Error로 이어졌습니다.
email 컬럼을 새로 추가합니다. (기존 username도 유지). 둘 다 쓰게 합니다.username의 데이터를 email로 복사합니다.email만 읽고 쓰도록 배포합니다.username 컬럼을 삭제합니다.이렇게 하면 서비스 중단 없이 스키마를 변경할 수 있습니다. 마이그레이션은 "코드"보다 "타이밍"이 더 중요합니다.
"개발 서버에선 잘 되는데 왜 운영 서버에선 안 되지?" 로컬(Local), 스테이징(Staging), 프로덕션(Production) 환경의 DB 스키마가 미세하게 다르기 때문입니다.
Supabase는 프로젝트별로 별도의 Git 리모트가 아닙니다.
config.toml에 project_id를 명시해서 관리해야 합니다.
가장 추천하는 방식은 GitHub Actions에서 환경변수로 분기하는 것입니다.
- run: supabase db push --project-ref $PROJECT_ID
env:
PROJECT_ID: ${{ github.ref == 'refs/heads/main' && secrets.PROD_ID || secrets.STAGING_ID }}
이렇게 하면 develop 브랜치에 푸시하면 Staging DB가 업데이트되고, main에 머지하면 Production DB가 업데이트됩니다.
절대 내 로컬 컴퓨터에서 supabase link --project-ref 1234 명령어로 프로덕션에 직접 연결하지 마세요. 실수로 db reset이라도 치면 끝장입니다.
마이그레이션은 "스키마(뼈대)"만 관리합니다. 하지만 "공통 코드(예: 은행 목록, 카테고리 목록)" 같은 기초 데이터는요?
supabase/seed.sql 파일을 활용하세요.
하지만 주의할 점은, seed.sql은 db reset 할 때만 실행됩니다.
이미 운영 중인 서버에 데이터를 밀어넣으려면 별도의 Data Migration 파일을 만들어야 합니다.
-- 20251214_add_banks.sql
INSERT INTO banks (name, code) VALUES ('KakaoBank', '090')
ON CONFLICT (code) DO NOTHING;
ON CONFLICT 처리는 필수입니다. 이미 들어있을 수도 있으니까요.
마이그레이션이 영원히 멈춰있다가 실패하는 경우가 있습니다. 주로 Lock 때문입니다.
ALTER TABLE 같은 명령어는 테이블 전체에 락(Access Exclusive Lock)을 겁니다.
만약 누군가 해당 테이블을 읽거나 쓰고 있다면(트랜잭션 중), 마이그레이션은 그 작업이 끝날 때까지 무한정 기다립니다.
SET lock_timeout = '2s'; -- 2초 안에 락 못 얻으면 에러 발생시키고 종료
ALTER TABLE users ADD COLUMN age INT;
pg_stat_activity를 조회해서 멈춘 쿼리를 강제 종료하세요.GitHub Actions로 배포 자동화를 해두면 충돌을 미리 막을 수 있습니다.
name: Deploy Migrations
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
- run: supabase db push
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
이제 main 브랜치에 합쳐지기만 하면 DB는 알아서 최신 상태가 됩니다.
db diff로 파일을 만들어서 Git으로 관리해라. 꼬였을 땐 db pull이나 migration repair로 장부를 맞춰라.