Post

Aurora DB 마이그레이션 락킹 이슈 - nullable 컬럼 추가가 블로킹이 되는 경우

Aurora DB 마이그레이션 락킹 이슈 - nullable 컬럼 추가가 블로킹이 되는 경우

사건

운영 중인 테이블에 90개의 nullable 컬럼을 추가하는 Django 마이그레이션을 실행했다. ACCESS EXCLUSIVE LOCK이 걸리면서 해당 테이블에 의존하는 모든 요청이 약 2분간 타임아웃됐다.

gantt
    title 장애 타임라인
    dateFormat HH:mm
    axisFormat %H:%M
    section DB
    마이그레이션 시작 (LOCK 획득) :crit, a1, 14:40, 20min
    section 서비스
    요청 타임아웃 발생 :crit, b1, 14:40, 20min
    section 복구
    마이그레이션 완료 (LOCK 해제) :done, c1, 15:00, 1min

통설: “nullable 컬럼 추가는 안전하다”

PostgreSQL에서 ALTER TABLE ADD COLUMN으로 nullable 컬럼(default 없이)을 추가하면 테이블을 재작성하지 않는다. 메타데이터만 변경되므로 빠르게 완료되어야 한다.

실제로 직접 SQL을 실행하면:

1
2
ALTER TABLE my_table ADD COLUMN new_col VARCHAR(100);
-- → 45ms에 완료

그런데 Django 마이그레이션으로 같은 작업을 실행하면 약 2분간 락이 걸렸다.

1
2
직접 SQL 실행:       45ms ✓
Django 마이그레이션:  ~2분 ✗  (약 2,600배 차이)

왜?


가설 검증

가설 1: ALTER 구문 자체가 락을 유발한다

PostgreSQL 공식 문서에 따르면 nullable 컬럼 추가(default 없음)는 non-blocking이다. 직접 SQL 실행 시 45ms로 확인했다.

판정: 기각

가설 2: 데이터 볼륨이 원인이다

개발 DB에 프로덕션과 동일한 데이터를 복제한 뒤 마이그레이션을 실행했지만, 2분 락이 재현되지 않았다.

판정: 기각

가설 3: Aurora 레플리카 동기화가 원인이다

개발과 프로덕션의 핵심 차이를 분석했다:

flowchart LR
    subgraph dev["개발 환경"]
        A[Aurora Writer<br>단일 인스턴스]
    end
    subgraph prod["프로덕션 환경"]
        B[Aurora Writer] --> C[Read Replica 1]
        B --> D[Read Replica 2]
    end
sequenceDiagram
    participant App as 애플리케이션
    participant Writer as Aurora Writer
    participant Rep1 as Read Replica 1
    participant Rep2 as Read Replica 2
    
    App->>Writer: ALTER TABLE ADD COLUMN
    Writer->>Writer: ACCESS EXCLUSIVE LOCK 획득
    Writer->>Rep1: 메타데이터 동기화
    Writer->>Rep2: 메타데이터 동기화
    Note over Writer,Rep2: 동기화 완료까지 LOCK 유지 (~2분)
    Rep1-->>Writer: 동기화 완료
    Rep2-->>Writer: 동기화 완료
    Writer->>Writer: LOCK 해제
    Writer-->>App: 마이그레이션 완료

Aurora는 DDL 실행 시 레플리카와 자동으로 동기화한다. 이 동기화가 완료될 때까지 ACCESS EXCLUSIVE LOCK이 해제되지 않는 것으로 추정된다.

판정: 유력 (개발 DB에 레플리카를 추가하여 추가 검증 필요)


교훈

1. 로컬/개발 환경 검증 ≠ 프로덕션 보장

flowchart TD
    A["로컬 PostgreSQL"] -->|"동작함 ✓"| D{프로덕션에서도<br>안전한가?}
    B["개발 Aurora (단일)"] -->|"동작함 ✓"| D
    C["프로덕션 Aurora (복제)"] -->|"락 발생 ✗"| D
    D -->|"NO"| E["환경의 토폴로지 차이를<br>반드시 확인하자"]
    style E fill:#fff3cd,stroke:#856404

“nullable 컬럼 추가는 안전하다”는 단일 인스턴스 기준의 이야기다. 레플리카가 있는 환경에서는 동작이 달라질 수 있다.

2. 대량 DDL 실행 전략

90개 컬럼을 한 번에 추가하는 것 자체가 위험하다. 안전한 접근법:

1
2
3
4
5
6
7
8
-- 방법 1: lock_timeout으로 장시간 락 방지
SET lock_timeout = '5s';
ALTER TABLE my_table ADD COLUMN new_col INTEGER;
-- 5초 내 락 획득 못하면 실패 → 재시도

-- 방법 2: 배치 분할 (90개를 10개씩 나눠서 실행)

-- 방법 3: 저트래픽 시간대 실행

3. 클라우드 매니지드 DB의 특수성

항목바닐라 PostgreSQLAurora
nullable 컬럼 추가메타데이터만 변경, 즉시 완료레플리카 동기화 대기 가능
DDL 실행 범위단일 인스턴스클러스터 전체에 영향
테스트 재현단일 인스턴스로 충분동일한 레플리카 구성 필요

느낀 점

“안전하다고 알려진” 작업이라도 프로덕션 환경의 특수성을 고려해야 한다. 클라우드 매니지드 DB(Aurora, Cloud SQL 등)는 바닐라 PostgreSQL과 다른 동작을 할 수 있다.

DDL 실행 전에는 프로덕션과 동일한 토폴로지(레플리카 포함)에서 테스트하는 것이 확실하다. 못하겠으면 최소한 lock_timeout을 설정하고 저트래픽 시간대에 실행하자.

This post is licensed under CC BY 4.0 by the author.