무중단 Postgres 마이그레이션 잠금 피하는 법
Postgres에서 CREATE INDEX CONCURRENTLY와 NOT VALID를 써서 쓰기 잠금 없이 마이그레이션하는 흐름을 정리했어요.
무중단 Postgres 마이그레이션 잠금 피하는 법
운영 중인 API에서 제일 무서운 마이그레이션은 실패보다 잠금이에요. 배포는 끝났는데 INSERT와 UPDATE가 잠깐이라도 멈추면, 사용자 입장에서는 바로 장애처럼 느껴질 수 있거든요. 혹시 ALTER TABLE 한 줄이 생각보다 무겁게 느껴진 적 없나요?
먼저 보는 기준은 하나예요
제가 PG17 공식 문서를 다시 읽어보니, 핵심은 복잡하지 않았어요. 이 변경이 쓰기를 막느냐, 아니냐예요. 일반 CREATE INDEX는 기존 큰 테이블에서 INSERT와 UPDATE를 막을 수 있지만, CREATE INDEX CONCURRENTLY는 정상 작업을 계속 허용한 채 인덱스를 만들 수 있어요.
이 차이는 운영 환경에서 꽤 커요. 인덱스 1개를 추가하는 작업이라도, 트래픽이 있는 시간대라면 체감 차이가 바로 납니다. 제가 순서를 적어보니 인덱스 작업은 “한 번에 끝내기”보다 “잠금을 쪼개기”가 더 중요했어요. 다만 CONCURRENTLY는 transaction block 안에서 실행할 수 없고, 실패하면 invalid 인덱스가 남을 수 있어서 정리 절차도 같이 준비해야 해요.
제약 추가는 검증을 분리해요
큰 테이블에 제약을 붙일 때도 같은 원칙이 적용돼요. ADD CONSTRAINT를 바로 검증까지 묶으면 기존 행 전체를 스캔하면서 쓰기 경로를 압박할 수 있어요. 그래서 NOT VALID로 먼저 붙이고, 나중에 VALIDATE CONSTRAINT로 검증을 분리하는 방식이 안전해요.
제가 이 패턴을 다시 정리해 보니 장점이 분명했어요. 1차 배포에서는 제약을 빠르게 추가하고, 2차 작업에서 데이터 정합성을 맞춘 뒤 검증하면 돼요. 공식 문서 기준으로 검증 단계는 SHARE UPDATE EXCLUSIVE 잠금을 잡기 때문에 일반 읽기와 쓰기를 막지 않아요. 운영 중 백엔드에서는 이 2단계 분리가 꽤 실용적이에요.
실전에서는 3단계로 나누면 편해요
제가 배포 흐름을 적어보면 보통 3단계예요. 먼저 additive migration으로 컬럼, 인덱스, 제약을 안전하게 추가해요. 다음으로 애플리케이션 코드를 새 스키마에 맞춰 배포하고, 필요한 경우 백필을 따로 돌려요. 마지막에 검증과 정리를 붙여서 스키마를 닫아요.
이 방식은 라이프케어로그처럼 작은 도구를 혼자 운영할 때도 유효해요. 예를 들어 플랜씨(plan-c) 같은 서비스도 로그인 없이 바로 계산이 돌아가야 하니, 짧은 DDL 하나 때문에 쓰기 경로가 흔들리면 안 되거든요. 직접 구조를 나눠 보니 마이그레이션은 SQL 문법보다 배포 순서가 더 중요했어요.
무중단 마이그레이션은 거창한 기술보다 잠금을 피하는 습관에 가까워요. 다음 배포에서 인덱스 1개, 제약 1개를 추가해야 한다면 먼저 “이 변경이 쓰기를 막는가”부터 체크해 보세요. 그 질문 하나가 다운타임을 많이 줄여줘요.
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.