🔥 Multi-AZ와 장애 조치: 가용성의 기제
강의 목차

처음 RDS 콘솔에서 인스턴스를 만들 때 Availability & durability 카드 안에 체크박스 하나가 들어 있었다. 이름은 Multi-AZ deployment. 옆에 작은 안내 문구가 붙어 있는데, 다른 가용영역에 standby 인스턴스를 하나 띄워서 동기 복제로 보호한다는 한 문장이었다. 체크박스 한 칸이라 별 생각 없이 켜고 인스턴스를 만들었다.
그달 청구서를 보고서야 그 체크박스 하나의 의미를 똑똑히 깨달았다. 인스턴스 시간 비용이 정확히 두 배로 나왔다. AWS는 standby 인스턴스가 트래픽을 받지 않아도 primary와 같은 인스턴스 시간 단가로 청구한다는 사실을 그제야 알았다. 그리고 한 달 뒤 운영 환경에서 첫 failover를 겪었을 때 또 한 가지를 알게 됐다. RDS는 자동으로 standby로 넘어갔지만, 애플리케이션의 connection pool은 그 사이 한 번 끊고 다시 연결하느라 분 단위 시간을 썼다. 자동이라는 말이 어디까지 닿고 어디서 멈추는지를 그때 처음 의식했다.
앞 편 엔진 선택: MySQL·PostgreSQL·Aurora에서 호환 엔진과 순정 엔진의 운영 거동이 다른 지점을 짚었다. 가용성 옵션은 엔진 선택과 또 다른 축이다. 같은 엔진을 골라도 그 위에 어떤 옵션을 얹느냐에 따라 청구서와 장애 거동이 모두 바뀐다. RDS의 Multi-AZ가 정확히 무엇을 보호하고, 무엇은 보호하지 않는지부터 짚는다.
Multi-AZ는 한 단어가 아니라 두 변형이다
Multi-AZ라는 단어는 RDS 안에서 두 가지 다른 토폴로지를 동시에 가리킨다. 처음엔 같은 옵션의 이름인 줄 알고 헷갈렸는데, 콘솔에서 라디오 버튼이 두 개로 나뉘어 있는 대목이 있었다.
첫 번째는 Multi-AZ instance deployment다. 한 AZ에 primary 인스턴스가 있고, 다른 AZ에 standby 인스턴스가 한 대 떠 있다. standby는 동기 복제로 primary와 같은 데이터를 들지만, 클라이언트가 standby에 연결할 수는 없다. standby의 역할은 오직 primary가 죽었을 때 그 역할을 이어받는 것이다. RDS for MySQL·PostgreSQL·MariaDB·Oracle·SQL Server·Db2 모두 이 모델이 기본이다. 2010년경부터 가용했다.
두 번째는 Multi-AZ DB cluster deployment다. 2022년 GA된 더 새로운 모델로, MySQL과 PostgreSQL에서만 동작한다. 한 cluster에 인스턴스가 세 대 있다. primary 한 대 + reader standby 두 대다. 두 standby는 클라이언트가 읽기 쿼리를 날릴 수 있다. 이 변형은 같은 Multi-AZ라는 단어 아래 들어가지만, 비용 구조와 failover 시간이 모두 다르다. AWS 공식 비교 표는 새 primary가 워크로드를 받기까지 보통 35초 미만(typically under 35 seconds)이라고 적었다. instance deployment의 60–120초와 비교하면 절반 이하다.
이 둘이 같은 이름으로 묶이는 게 처음 봤을 때 가장 헷갈리는 대목이었다. 콘솔의 같은 라디오 그룹에 Single instance, Multi-AZ instance, Multi-AZ DB cluster 세 옵션이 함께 있는데, 셋이 가용성 등급의 차이라기보다 청구 모델과 토폴로지 자체가 다른 세 메뉴라고 읽는 편이 정확하다.

동기 복제가 정확히 무엇을 보장하는가
Multi-AZ instance deployment 쪽 메커니즘을 더 깊게 짚자. primary가 트랜잭션을 commit할 때 무엇이 일어나는가. InnoDB 같은 storage engine이 redo log 한 줄을 적고 디스크에 sync 호출을 부르는데, 그 sync는 EBS 한 곳에서 끝나지 않는다. primary의 EBS에 적은 redo log를 standby의 EBS에도 동시에 보내고, standby가 자기 EBS에 적은 뒤 ack를 돌려줄 때까지 primary는 commit을 멈춘 채 응답을 받는다. 이게 동기 복제다.
이 모델의 결과로 두 가지가 따라온다. 첫째는 데이터 손실 0이다. primary가 갑자기 죽어도 standby가 같은 redo log를 이미 적었기 때문에 commit된 트랜잭션은 한 건도 잃지 않는다. RPO(Recovery Point Objective)가 0초인 가용성 모델이다. 둘째는 commit 지연이 더 늘어난다는 것이다. 다른 AZ까지 패킷이 한 번 왕복해야 하므로, 같은 AZ 내 단일 인스턴스 대비 commit latency가 일관되게 더 높다. 서울 리전(ap-northeast-2)의 AZ 간 지연은 보통 한 자릿수 밀리초이지만, 트랜잭션이 초당 수백 건 들어오는 워크로드에서는 그 한 자릿수 밀리초가 쌓여 처리량 차이를 만든다.

여기서 잠깐 운영적으로 worked example 하나를 짚자. 한 트랜잭션에 commit이 평균 5번 일어나는 워크로드가 있다고 하자. 같은 AZ 단일 인스턴스에서 commit 한 번이 1ms라면 트랜잭션당 5ms가 commit에 들어간다. Multi-AZ instance deployment로 옮기고 AZ 간 왕복이 2ms를 더하면 commit 한 번에 3ms가 든다. 트랜잭션당 15ms다. 차이는 10ms처럼 보이지만 초당 1000 트랜잭션 워크로드라면 1초당 10초어치 commit 시간이 늘어난 것과 같다. write-heavy 워크로드에서 이 commit 단계가 병목이 될 수 있다는 점을 미리 알아 두면 인스턴스 사이즈를 한 단계 키울지 판단할 때 도움이 된다.
이 commit 지연을 줄이려고 만들어진 변형이 Multi-AZ DB cluster deployment의 quorum 기반 복제다. 세 인스턴스 중 두 곳에서 ack가 돌아오면 commit이 성공한다(2 of 3 quorum). 가장 느린 한 곳을 기다리지 않으니 일관된 tail latency가 줄어든다. AWS 발표가 instance deployment 대비 commit latency가 최대 2배 빠르다고 표현하는 근거가 이 quorum 모델이다.
Failover 타임라인을 한 번 따라가 보기
failover가 시작된 시점부터 클라이언트가 다시 쿼리를 날릴 수 있는 시점까지 사이에 무엇이 일어나는지 단계별로 짚자. 콘솔이나 CLI는 한 마디로 “automatic failover”라고만 표시하는데, 그 안에 분명한 단계가 있다.
t=0초: primary 인스턴스가 응답하지 않기 시작한다. RDS의 health check가 정해진 간격으로 primary를 찌르고 있는데, 응답이 없는 상태가 정해진 횟수 이상 누적되면 장애로 판정한다. 이 판정 시간은 보통 10–30초 안쪽이다.
t≈30초: RDS가 standby 승격을 시작한다. RDS가 standby의 redo log가 가장 최신 위치까지 적용됐는지 확인하고, standby를 새 primary로 띄운다. Multi-AZ instance deployment에서는 동기 복제로 redo log가 이미 같은 위치에 있어야 하므로 적용 시간이 짧다. 새 인스턴스 부팅이나 OS 띄우기 단계가 없다.
t≈60초: DNS 갱신이 일어난다. 이게 가장 사용자에게 보이는 단계다. AWS 공식 문서가 표현한 그대로 옮기면 다음과 같다. RDS가 단순히 DB 인스턴스의 CNAME(canonical name) 레코드를 standby를 가리키도록 뒤집는다는 것(원문: Amazon RDS simply flips the canonical name record (CNAME) for your DB instance to point at the standby). primary endpoint의 DNS 이름은 그대로지만 그 이름이 가리키는 IP가 바뀐다. RDS endpoint의 DNS TTL은 5초로 짧게 설정돼 있어서 이론적으로는 5초 안에 새 IP가 보여야 한다.
t≈60–120초: 클라이언트 측 connection이 다시 살아난다. 이론과 실제 사이의 가장 큰 갭이 이 단계에 있다. 애플리케이션의 connection pool이 끊어진 connection을 감지하고, DNS 캐시가 새 IP를 받아서, 새 connection을 standby로 다시 만들어야 한다. 이 사이에 실패한 트랜잭션은 애플리케이션이 재시도해야 한다.

AWS가 instance deployment 기준 "보통 60초에서 2분 사이"라고 적은 wall-clock은 t=0부터 t≈120초까지의 전체다. 단계 중 어느 하나(특히 클라이언트 측)가 느려지면 이 wall-clock도 그만큼 늘어난다. DB cluster deployment는 같은 단계지만 t=0부터 새 primary 가용까지 보통 35초 안쪽에서 끝난다. 인스턴스 부팅 단계가 없는 hot standby 모델 위에서 quorum이 합의를 더 빨리 끝내기 때문이다.
DNS 갱신은 자동, 클라이언트 재연결은 수동
내가 failover를 운영하면서 가장 자주 부딪힌 함정은 DNS 캐시다. RDS는 DNS를 5초 TTL로 자동 갱신하지만, 클라이언트 쪽 OS resolver와 언어 런타임이 그 TTL을 무시하면 자동이 무용지물이 된다.
가장 유명한 사례가 JVM이다. Java의 networkaddress.cache.ttl 값이 음수면(-1) DNS 응답을 영구 캐시한다. AWS SDK for Java 문서가 이 함정을 직접 다룬다. 보안 관리자(SecurityManager)가 설치된 JVM 환경에서는 이 값이 기본 -1로 설정된 채 출발한다는 것이다. 일반 JVM에서는 60초 미만으로 짧은 편이지만, 환경에 따라 다르다. AWS의 권장은 60초 이하로 명시적으로 설정하라는 것이다. JDBC 드라이버 옵션이 아니라 JVM 단위의 보안 속성이라 -D 플래그로는 안 잡히고 java.security 파일이나 Security.setProperty 호출로 설정해야 한다. 운영하다가 이 속성을 안 만져 두면 failover 후에 JVM 재시작 전까지 옛 IP로 계속 시도하는 사고가 난다.
JVM뿐 아니라 connection pool 자체의 동작도 짚어야 한다. HikariCP나 pgbouncer 같은 풀은 풀에 들고 있는 idle connection이 더 이상 살아 있지 않다는 사실을 다음 사용 시점에 가서야 안다. validation query 옵션을 켜 두지 않으면 pool에서 꺼낸 첫 쿼리가 한 번 실패하고, 그 시점에 처음 새 connection을 만든다. failover 직후 30초~1분간 애플리케이션 로그에 SQLException이 한 번씩 찍히는 패턴이 여기서 온다.

Multi-AZ는 RDS 쪽에서 데이터와 인스턴스를 안전하게 standby로 넘겨준다. 그 다음 다리, 즉 클라이언트가 새 primary를 바라보게 만드는 일은 자동의 영역 바깥이다. 자동이라는 단어가 어디까지 닿는지를 정확히 알아 두는 게 운영자의 책임으로 남는다.
Aurora의 Multi-AZ는 같은 단어 다른 메커니즘
여기서 한 가지만 짚고 가자. 앞 편에서 본 Aurora는 같은 Multi-AZ라는 단어를 쓰지만 메커니즘이 완전히 다르다. RDS for MySQL·PostgreSQL의 Multi-AZ는 인스턴스 단위의 복제다. AWS는 primary 인스턴스의 EBS와 standby 인스턴스의 EBS를 동기 복제로 묶는다. Aurora는 인스턴스가 아니라 storage 계층 자체가 3 AZ에 분산된 6 복제 위에 떠 있다. failover는 storage 복제가 아니라 reader 인스턴스 한 대를 writer로 promote하는 것뿐이다.
같은 단어 다른 메커니즘의 결과 두 가지가 따라온다. 첫째, Aurora의 failover는 보통 30초 이내로 RDS Multi-AZ instance보다 빠르다. storage가 이미 공유돼 있어서 reader 승격에 데이터 적용 단계가 없다. 둘째, Aurora 콘솔에는 Multi-AZ 체크박스가 없다. 기본 토폴로지가 이미 분산이라 토글할 일이 아니다. 대신 reader 인스턴스를 한 대 이상 띄우는 게 가용성 옵션이 된다.
이름이 같다는 이유로 같은 메커니즘이라고 가정하면 운영 거동을 잘못 예측하게 된다. RDS for MySQL Multi-AZ로 운영하던 코드를 Aurora로 옮길 때 failover 동작이 다르다는 사실을 미리 알아 두면 connection retry 정책을 한 번 다시 잡아야 할 지점을 찾아낼 수 있다.
Multi-AZ를 끄는 게 답인 곳
관리형이라고 해서 늘 답은 아니다. RDS는 쓰되 Multi-AZ는 끄는 게 합리적인 경우가 셋 있다.

첫째는 단일 AZ 사이드 프로젝트와 dev/staging 환경이다. 이런 환경은 인스턴스 한 대만 있어도 운영이 거의 멈추지 않는다. 가끔 한두 분 다운이 나도 사람만 다시 띄우면 된다. 그런데 Multi-AZ를 켜면 인스턴스 비용이 정확히 두 배가 된다는 점은 앞에서 짚었다. 가용성 보장이 거의 살지 않는데 비용은 두 배로 낸다.
둘째는 수 분의 RTO를 허용하는 워크로드다. 모든 워크로드가 60초 안에 복구돼야 하는 건 아니다. 야간 배치, 데이터 분석 마트, 사내 도구처럼 사용자 트래픽이 안 들어오거나 응답 시간 SLA가 분 단위로 느슨한 시스템이라면 인스턴스 한 대를 띄우고, 자동 백업과 PITR(Point-In-Time Recovery)에 의존하는 편이 운영 부담이 더 작다. 백업·PITR은 다음 편들에서 따로 다룬다.
셋째는 complete cluster failure가 더 중요한 워크로드다. Multi-AZ는 한 AZ 안의 단일 인스턴스 장애와 한 AZ 전체 장애로부터 보호하지만, region 전체가 멈추는 사고는 그 범위 밖이다. 그런 워크로드는 multi-region read replica + cross-region failover 같은 다른 솔루션이 답이다. Multi-AZ를 끄라는 게 아니라, Multi-AZ만 켜고 끝이라고 생각하지 말라는 뜻이다.
처음 콘솔의 체크박스 한 칸 앞에서 별 생각 없이 켰던 곳으로 돌아가 보면, 이제 그 한 칸이 인스턴스 청구를 두 배로 만들고 RPO 0초의 동기 복제를 가져오고 60–120초 wall-clock의 자동 failover를 켜는 한 묶음임을 또렷이 안다. 자동이라는 말이 RDS 측의 standby 승격과 DNS 갱신까지만 닿고, 클라이언트의 connection pool 재연결과 DNS 캐시 무효화는 운영자 책임으로 남는다는 점도 함께 알게 됐다. 다음 편의 주제는 Multi-AZ의 동기 복제와 성격이 다른 비동기 복제, 즉 Read Replica다. 가용성을 위한 복제와 읽기 부하를 위한 복제가 어떻게 다른지가 다음 단계다.










