RDS PostgreSQL 16→17 업그레이드 새벽 작업기 — replication slot에 또 당했다
지난주 토요일 새벽 2시. RDS PostgreSQL 16에서 17로 메이저 업그레이드를 돌렸다. 작업 자체는 한 시간 안에 끝날 줄 알았는데, 결국 새벽 6시까지 책상에 앉아있었다. 또 replication slot이었다.
분명히 사전 점검 체크리스트에 "logical replication slot 확인" 항목을 넣어뒀다. 그런데도 당했다. 어떻게 당했는지, 그리고 이번에 알게 된 게 뭔지 적어둔다. 다음에 또 같은 데서 미끄러지지 않으려고.
사전 점검은 했는데
작업 전 점검은 평소대로 돌렸다.
SELECT slot_name, plugin, slot_type, database, active, restart_lsn
FROM pg_replication_slots;
결과는 비어있었다. 우리 팀이 직접 관리하는 publisher 슬롯은 다 정리한 상태였다. DMS도 안 쓰고, pglogical도 한 달 전에 걷어냈다. "OK, 깨끗하다." 하고 업그레이드 시작.
aws rds modify-db-instance --engine-version 17.x 던지고, 모니터링 화면 띄워놓고 커피 한 잔. 그런데 5분쯤 지나니까 인스턴스 상태가 upgrading에서 멈춰 있다. 10분, 15분. 멘탈이 조금씩 흔들리기 시작했다.
CloudWatch Events를 다시 보니 이런 메시지가 와 있었다.
Database instance has a logical replication slot but was upgraded to a major version. Slot information will be lost.
뭐? 슬롯 없었는데?
알고 보니 read replica가 범인
원인을 파악하는 데 한참 걸렸다. 사실 작업 직전 슬롯 점검은 primary 인스턴스에서만 돌렸다. read replica 두 대가 붙어 있었는데, 거기는 점검 대상으로 아예 안 봤다. 평소엔 read replica가 slot을 들고 있을 일이 없으니까.
근데 두 달 전쯤 데이터 분석팀이 read replica 한 대에 외부 분석 도구를 붙였다. 그 도구가 logical decoding을 쓰고 있었고, read replica 위에 자기 슬롯을 만들어 둔 상태였다. 우리 팀은 이 사실을 모르고 있었다. 운영 위키 어디에도 안 적혀 있었다.
PostgreSQL 17부터는 primary의 logical replication slot은 메이저 업그레이드 후에도 살아남는다(올해 초 AWS가 공지했다). 그런데 read replica의 슬롯은 여전히 유지가 안 된다. 이 부분이 함정이었다. "17이니까 슬롯 걱정 안 해도 되겠지" 하는 안일함이 한 발 더 추가됐다.
업그레이드는 일단 완료됐지만, read replica 슬롯은 사라졌고, 분석 도구는 그 시간부터 데이터를 못 받고 있었다. 새벽 4시에 분석팀 슬랙 채널에 "데이터 멈췄어요?" 메시지가 올라오기 시작했다.
복구 과정에서 또 한 가지 배웠다
read replica를 재생성하고 분석 도구 재연결까지 마쳤다. 여기까지가 새벽 6시. 그런데 분석팀이 "처음부터 데이터 다시 보내달라"고 했다. 그게 가능한가?
logical replication slot은 restart_lsn을 기준으로 거기서부터 재개된다. 슬롯이 사라졌다는 건 그 LSN 위치도 같이 사라졌다는 뜻. 사실상 새 슬롯으로는 "현재 시점부터"의 변경분만 받을 수 있다. 과거 데이터 backfill은 슬롯과는 별개로 dump-and-load든 다른 방법이든 따로 처리해야 한다.
분석팀에는 "지금부터의 변경분은 다시 받을 수 있지만, 업그레이드 시작~지금 사이의 5시간치 변경분은 슬롯에서 복구 불가능"이라고 안내했다. 결국 그 구간은 pg_dump 스냅샷으로 전체 테이블을 다시 적재하는 식으로 처리했다.
점검 스크립트를 고쳤다
다음 업그레이드를 위해 사전 점검 스크립트를 갈아엎었다. primary뿐 아니라 모든 read replica를 순회하면서 슬롯을 확인하도록.
#!/usr/bin/env bash
set -euo pipefail
CLUSTER_ID="$1"
# primary와 모든 read replica의 endpoint 수집
ENDPOINTS=$(aws rds describe-db-instances \
--query "DBInstances[?DBClusterIdentifier=='${CLUSTER_ID}'].[DBInstanceIdentifier,Endpoint.Address]" \
--output text)
while read -r INSTANCE ENDPOINT; do
echo "[${INSTANCE}] checking replication slots..."
PGPASSWORD="$DB_PASSWORD" psql -h "$ENDPOINT" -U "$DB_USER" -d postgres -tAc \
"SELECT slot_name || ' (' || slot_type || ', active=' || active || ')'
FROM pg_replication_slots;" \
| tee "/tmp/slots_${INSTANCE}.txt"
done <<< "$ENDPOINTS"
# 슬롯이 하나라도 있으면 비-zero exit
if find /tmp -name "slots_*.txt" -not -empty | grep -q .; then
echo "ERROR: replication slots found. Aborting upgrade."
exit 1
fi
bash로 짜기엔 좀 지저분하지만, 이번엔 일단 빠르게 막는 게 목적이었다. 운영 위키에도 "read replica에 외부 도구 붙일 때는 #infra 채널에 공유하기" 항목을 추가했다. 그게 진짜 근본 대책이긴 하다.
결국은 사람 문제였다
기술적으로 정리하면 read replica의 logical slot이 메이저 업그레이드에서 유실되는 동작은 PostgreSQL 17에서도 그대로다. 이건 알고 있어야 할 사실이다. 하지만 진짜 원인은 그게 아니라 "우리 팀 모르게 read replica에 무언가 붙어있었다"는 점이었다.
운영 환경의 모든 변경을 한 팀이 다 알 수는 없다. 그래도 사이드이펙트가 있는 변경(slot 생성, trigger 추가, extension 설치 등)은 어떻게든 트래킹할 장치가 필요하다. 다음 분기에는 RDS Event subscription에 replication-slot-created 같은 이벤트를 SNS로 받아서 슬랙에 흘리는 걸 만들어볼까 한다. 아직 검증 중이긴 하다.
새벽 작업은 자주 안 하는데, 할 때마다 새로운 걸 하나씩 배운다. 이번에는 비싼 수업이었다.
'IT > DB 운영' 카테고리의 다른 글
| Postgres에서 한 줄 설정으로 막을 수 있는 idle 사고 (0) | 2026.06.05 |
|---|---|
| PgBouncer transaction mode에 prepared statement 켰다가 새벽에 깬 이야기 (0) | 2026.05.25 |
| Aurora PostgreSQL 14 → 16 Blue/Green 업그레이드에서 삽질한 새벽 이야기 (0) | 2026.05.20 |
| PgBouncer transaction pooling, prepared statement 함정에서 빠져나온 이야기 (0) | 2026.05.14 |
| Redis Cluster slot migration 중에 P99이 4초까지 튄 새벽 (0) | 2026.05.07 |