- Postgres 스키마 마이그레이션은 잠금·테이블 재작성·복제 지연이 운영 장애로 이어질 수 있어, 대형 OLTP 환경에서 특히 위험도가 높음
- 위험은
DEFAULT와 NOT NULL 동시 추가, CONCURRENTLY 없는 인덱스 생성, 즉시 컬럼 삭제, 안전하지 않은 타입 변경, 검증 없는 외래 키 추가처럼 전체 스캔과 긴 잠금을 유발하는 작업에 집중됨
- PostgreSQL 11부터 일부 컬럼 추가 비용은 줄었지만, 인덱스는
CREATE INDEX CONCURRENTLY, 외래 키는 NOT VALID 후 VALIDATE CONSTRAINT처럼 운영 영향을 낮추는 절차가 필요함
- 대량 변경은 작은 배치로 나누고, 읽기 복제본·복제 지연·의존 객체·기존 애플리케이션 인스턴스의 컬럼 참조 여부까지 함께 확인해야 함
- 프로덕션 규모 데이터에서 사전 테스트하고, 파괴적 작업은 다단계 배포와 검증된 롤백 계획을 갖춘 뒤 진행해야 함
스키마 마이그레이션의 전제
- 여기서 DB 마이그레이션은 DBMS 전환이 아니라 DB 스키마 변경을 뜻함
- 대상 변경은 세 가지 성격을 가짐
- 각 변경에 고유 식별자와 자동화된 적용 절차가 있는 버전 관리형 변경
- 프로덕션 적용 뒤 수정하지 않고 새 변경만 추가하는 불변 변경
- 데이터베이스 스키마가 단계적으로 진화하는 증분 변경
- 초점은 모바일·웹 애플리케이션 같은 OLTP 사용 사례이며, 1초를 넘는 쿼리 실행은 보통 너무 느린 것으로 간주됨
- 작은 데이터베이스와 낮은 활동량에서는 일부 문제가 잘 드러나지 않지만, 약 10TiB 규모와 초당 10⁴~10⁵ 트랜잭션 부하에서는 대부분의 문제가 나타날 수 있음
- Database Lab Engine은 얇은 클론으로 개발과 테스트에 쓰이며, 10TiB 데이터베이스를 10초 안에 클론해 스키마 변경 위험을 배포 전에 확인할 수 있음
- GitLab Migration Style Guide는 다수의 Postgres 스키마 변경을 자동 배포한 경험을 담은 참고 자료임
컬럼 추가와 테이블 재작성
DEFAULT와 NOT NULL을 동시에 가진 컬럼 추가는 구버전 PostgreSQL에서 특히 위험함
- PostgreSQL 11 이전에는 전체 테이블 재작성이 필요함
- 큰 테이블에서는 몇 시간 또는 며칠이 걸릴 수 있고, 그동안 쓰기 잠금이 발생함
- 위험한 예시는 다음과 같음
ALTER TABLE users ADD COLUMN status text DEFAULT 'active' NOT NULL;
- 더 안전한 절차는 컬럼 추가, 데이터 갱신, 제약 추가를 나누는 방식임
- 먼저
NOT NULL 없이 컬럼을 추가함
- 필요하면 기존 행을 갱신함
- 이후
NOT NULL 제약을 추가함
ALTER TABLE users ADD COLUMN status text DEFAULT 'active';
-- UPDATE users SET status = 'active' WHERE status IS NULL;
ALTER TABLE users ALTER COLUMN status SET NOT NULL;
- PostgreSQL 11 이상에서는 비휘발성
DEFAULT 값을 가진 컬럼 추가가 더 이상 테이블 재작성을 요구하지 않음
인덱스 생성과 외래 키 추가
CONCURRENTLY 없이 인덱스를 만들면 표준 인덱스 생성이 테이블에 배타 잠금을 잡음
- 인덱스 생성이 끝날 때까지 모든 쓰기와 일부 읽기가 막힐 수 있음
- 위험한 예시는 다음과 같음
CREATE INDEX idx_users_email ON users(email);
- 운영 중에는
CREATE INDEX CONCURRENTLY 사용이 더 안전함
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
CONCURRENTLY에는 제약이 있음
- 더 오래 걸리지만 테이블 접근을 막지 않음
- 트랜잭션 블록 안에서 사용할 수 없음
- 실패하면 삭제가 필요한 유효하지 않은 인덱스를 남길 수 있음
- 큰 테이블에 외래 키 제약을 직접 추가하면 기존 데이터 검증을 위해 전체 테이블을 스캔하고 긴 잠금을 유발함
- 더 안전한 절차는 먼저
NOT VALID로 제약을 추가한 뒤, 트래픽이 낮은 시점에 검증하는 방식임
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_user_id;
컬럼 삭제와 타입 변경
- 프로덕션에서 컬럼을 바로 삭제하면 애플리케이션 코드가 여전히 해당 컬럼을 참조할 때 애플리케이션 오류가 발생할 수 있음
- 컬럼 삭제는 다단계로 진행해야 함
- 해당 컬럼을 사용하지 않는 애플리케이션 코드를 먼저 배포함
- 오래된 애플리케이션 인스턴스가 모두 교체될 때까지 기다림
- 별도 마이그레이션에서 컬럼을 삭제함
- 컬럼 타입 변경은 테이블 재작성이나 호환성 문제를 만들 수 있음
- 다운타임, 데이터 손실, 애플리케이션 오류로 이어질 수 있음
- 문제가 되는 예시는 다음과 같음
ALTER TABLE users ALTER COLUMN id TYPE bigint;
ALTER TABLE users ALTER COLUMN email TYPE varchar(100);
integer에서 bigint로 바꿀 때는 새 컬럼을 이용한 다단계 절차가 필요함
varchar 길이를 줄일 때는 데이터를 먼저 확인하고, 변경이 정말 필요한지 검토해야 함
대량 변경, 복제, 의존 객체
- 너무 많은 데이터를 하나의 트랜잭션에서 수정하는 마이그레이션은 피해야 함
- 잠금 경합과 메모리 사용량이 늘어남
- 문제가 생겼을 때 복구 시간이 길어짐
- 복제 지연이 커질 수 있음
- 대규모 데이터 마이그레이션은 작은 배치로 나누는 편이 더 안전함
- 마이그레이션이 읽기 복제본과 복제 지연에 미치는 영향도 함께 봐야 함
- 큰 마이그레이션은 상당한 복제 지연을 만들 수 있음
- 읽기 복제본 성능에 영향을 줄 수 있음
- 수정 대상 컬럼이나 테이블에 의존하는 객체도 확인해야 함
- 뷰, 함수, 트리거 등 의존 객체를 놓치면 연쇄 실패나 추가 수동 개입이 필요할 수 있음
테스트와 롤백 계획
- 작은 개발 데이터셋에서만 마이그레이션을 테스트하면 큰 데이터셋의 성능 특성을 확인하기 어려움
- 프로덕션 규모 데이터 클론에서 테스트해야 하며, Database Lab Engine 같은 도구를 사용할 수 있음
- 문제가 발생했을 때 마이그레이션을 되돌릴 방법이 없으면 프로덕션 이슈가 장기 다운타임으로 이어질 수 있음
- 특히 파괴적 작업에는 검증된 롤백 계획이 필요함
- 안전한 스키마 변경의 기본은 다음과 같음
- 프로덕션 규모 데이터에서 테스트함
- 위험한 작업에는 다단계 접근을 사용함
CONCURRENTLY와 NOT VALID 같은 PostgreSQL 기능을 활용함
- 성능과 복제 영향을 모니터링함
- 항상 롤백 계획을 준비함