Aurora PostgreSQL 14 → 16 Blue/Green 업그레이드에서 삽질한 새벽 이야기
지난주 화요일 새벽 2시, 메이저 버전 업그레이드를 한다고 멘션이 와있었고 나는 콘솔 앞에 있었다. 사전 리허설은 두 번 했다. 스위치오버 30초, 길어야 1분. 그런데 실제 작업은 7분 걸렸다. 7분이라는 숫자 자체보다, 그 7분 동안 회사 메인 서비스의 결제 API가 절반쯤 죽어있었다는 게 문제였다.
이 글은 그날 무슨 일이 있었는지, 그리고 다음에 같은 작업을 또 한다면 뭘 다르게 할지에 대한 기록이다. AWS 공식 문서나 베스트 프랙티스 글들이 말하지 않는 것들이 좀 있더라.
시작은 평범했다
대상은 Aurora PostgreSQL 14.10 클러스터다. 14가 2026년 11월에 standard support가 끝난다는 공지가 작년 말에 나왔고, 우리는 6월 안에 16으로 올리기로 했다. 17이 아니라 16을 고른 이유는 단순했다 — 우리가 쓰는 PostGIS extension의 호환 매트릭스에서 17은 아직 노란불이었다.
Blue/Green Deployment를 쓰기로 한 이유는 in-place 업그레이드가 정말 무서워서다. 우리 클러스터는 1.2TB짜리 writer 하나에 reader 3개. in-place로 가면 다운타임이 최소 8~12분은 나온다는 사전 테스트가 있었고, 그동안 결제는 정지된다. Blue/Green이라면 그 시간을 30초 수준으로 줄일 수 있다는 게 매력이었다.
콘솔에서 deployment를 만들고 두 시간쯤 기다리니 green 환경이 16.4로 올라왔다. 여기까지는 깔끔.
첫 번째 함정: pg_partman
green 환경이 만들어진 후 곧바로 replication lag 알람이 떴다. 5초, 10초, 30초까지 올라가더니 더 이상 줄지 않았다. CloudWatch의 OldestReplicationSlotLag 그래프가 우상향이었다.
원인은 pg_partman이었다. 우리는 결제 이벤트 로그 테이블을 월 단위로 파티셔닝하는데, pg_partman의 background worker가 새 파티션을 자동으로 만들어준다. 문제는 이게 CREATE TABLE DDL을 날린다는 점이다. 그리고 Blue/Green deployment에서 사용하는 logical replication은 DDL을 복제하지 못한다.
AWS 문서에는 분명히 적혀있다 — "pg_partman은 blue/green 생성 시 disable해야 한다." 사전 체크리스트에 이걸 포함시켰는데, 우리가 한 일은 pg_partman_bgw.interval을 0으로 만든 거였다. 그런데 그것만으로는 부족했다. 이미 스케줄된 partition maintenance job이 cron table에 남아있어서, 어떤 시간에는 새 파티션이 만들어지고 있었다.
진짜 disable은 이런 식이다:
-- background worker 중단만으로는 부족
ALTER SYSTEM SET pg_partman_bgw.interval = 0;
SELECT pg_reload_conf();
-- 추가로 partman config의 자동 maintenance도 끄기
UPDATE partman.part_config
SET automatic_maintenance = 'off'
WHERE parent_table IN (SELECT parent_table FROM partman.part_config);
-- 명시적으로 run_maintenance가 호출되지 않도록 cron job 비활성화
-- (pg_cron을 함께 쓰는 경우)
UPDATE cron.job SET active = false
WHERE command LIKE '%run_maintenance%';
이걸 안 한 상태에서 deployment를 진행하면, 어딘가에서 DDL이 한 번 터지는 순간 그 테이블의 logical replication이 깨진다. 우리는 deployment를 한 번 지웠다가 다시 만들었다. 두 시간 더.
두 번째 함정: 자체 logical replication slot
여기가 사전 리허설에서 못 잡았던 부분이다. 우리 서비스는 데이터 분석팀이 쓰는 별도 Aurora 클러스터로 일부 테이블을 logical replication으로 보내고 있다. 즉, 메인 DB가 publisher이고, 분석용 DB가 subscriber다.
Blue/Green deployment가 자체적으로 사용하는 internal replication slot 말고, 우리가 만든 publication과 slot이 별도로 있었다는 얘기다. 그리고 switchover 직전에 RDS가 이런 메시지를 띄웠다.
"The blue environment is a logical replication source. Switchover may cause replication subscriptions to fail."
문서에는 이렇게 적혀있다 — "self-managed replication slot은 switchover 전에 drop하고, 끝나면 다시 만들라." 그런데 그 "다시 만들라"가 사소한 작업이 아니다. 분석 DB의 subscription을 새 slot에 연결하려면 LSN 동기화를 다시 해야 하고, 우리 경우 그게 25분짜리 작업이었다.
우리는 처음에는 그냥 무시하고 진행했다. 결과는 분석 파이프라인이 다음 날 오전까지 30분쯤 지연된 데이터로 살게 됐다는 것. 큰 사고는 아니었지만, 데이터팀에서 "왜 미리 말 안 했어?"라는 메시지가 왔다. 마음이 무거웠다.
세 번째 함정: 스위치오버 7분
이게 진짜 문제였다. RDS 문서와 여러 블로그에서 "switchover takes about 30 seconds to 1 minute"이라고 광고하는 그 작업. 우리는 7분이었다.
이유를 사후에 분석해보니 두 가지였다.
첫째, switchover 시작 직전에 blue에서 큰 트랜잭션이 하나 돌고 있었다. 정확히는 정산용 배치 잡이 동시간대에 시작되도록 cron이 잘못 들어가 있었다. RDS는 switchover를 시작하기 전에 replication lag를 0으로 만들고, 그 다음 blue를 read-only로 바꾼다. 큰 트랜잭션이 commit될 때까지 기다리는 동안 4분 정도가 흘렀다.
둘째, 우리 reader endpoint가 3개의 reader를 가지고 있고 그 중 하나가 hot-standby 용도로 큰 long-running query를 받고 있었다. RDS는 reader도 같이 옮기는데, 이 reader의 query가 자연 종료되거나 timeout 될 때까지 또 1~2분.
그러니까 30초 약속은, "당신이 깨끗한 상태일 때"의 약속이다. 우리 같이 트랜잭션이 항상 살아있는 운영 DB는 그렇지 못하다.
다음에는 이렇게 할 거다:
- switchover 30분 전에 모든 배치 잡과 cron을 차단하는 maintenance flag를 코드에 박는다
- 4분 이상 걸리는 long-running query를 자동으로 cancel하는 timeout을 일시적으로 깐다
- reader endpoint를 미리 빈 reader 하나로만 좁혀둔다
그래서 결론
업그레이드는 결국 성공했다. 16.4 클러스터에서 한 주를 무사히 돌았고, blue 환경은 일주일 동안 데이터 정합성 검증을 한 뒤에 정리했다. 그런데 마음에 남은 건 "거의 다운타임 없음"이라는 표현을 너무 쉽게 믿었다는 점이다.
PostgreSQL 17부터는 logical replication slot이 메이저 업그레이드 시 보존되도록 바뀌었다고 한다. 그러니까 PG17 → PG18 같은 케이스에서는 self-managed slot 문제가 좀 더 가벼워질 거 같다. 다만 Aurora의 Blue/Green이 그 PG17 동작을 그대로 활용하는지는 우리가 직접 테스트해봐야 알 수 있을 듯하다. 이건 다음 분기에 검증할 항목으로 잡아뒀다.
혹시 Blue/Green으로 메이저 업그레이드 경험 있으신 분 있으면, 어떤 함정을 만나셨는지 공유해주시면 감사하겠다. 우리는 아마 내년에 17이나 18로 또 가야 한다.
