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

1921자
23분

16:9 가로 표지 일러스트레이션. 흰 배경. 모던 플랫 미니멀 에디토리얼 디자인. 두 개 둥근 사각형이 나란히 놓여 있고 각각 'AZ a' 와 'AZ c' 라벨이 붙어 있다. 왼쪽 사각형 안에는 muted AWS-orange 'primary' 라벨이 붙은 데이터베이스 실린더, 오른쪽 사각형 안에는 lighter gray 'standby' 라벨이 붙은 같은 실린더. 두 실린더 사이를 sky-blue 화살표가 'synchronous replication' 이라는 라벨로 잇는다. 사각형 아래에 가는 sky-blue 점선이 'RDS endpoint (CNAME)' 라벨로 primary 실린더를 가리킨다. 상단 다크 슬레이트 캡션 'Multi-AZ what flips when the primary dies'. 절제된 sans-serif. 프로페셔널 IT 표지 스타일.

처음 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과 Multi-AZ DB cluster deployment의 토폴로지 비교. 왼쪽은 primary 한 대와 standby 한 대(클라이언트 읽기 불가), 오른쪽은 primary 한 대와 readable standby 두 대.

동기 복제가 정확히 무엇을 보장하는가

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 간 지연은 보통 한 자릿수 밀리초이지만, 트랜잭션이 초당 수백 건 들어오는 워크로드에서는 그 한 자릿수 밀리초가 쌓여 처리량 차이를 만든다.

동기 복제 commit 시퀀스. 애플리케이션이 primary에 commit을 요청하면 primary가 redo log를 자기 EBS에 적고 동시에 standby에 보내며, standby ack가 돌아온 뒤에야 primary가 애플리케이션에 OK를 돌려준다.

여기서 잠깐 운영적으로 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로 다시 만들어야 한다. 이 사이에 실패한 트랜잭션은 애플리케이션이 재시도해야 한다.

16:9 가로 에디토리얼 일러스트레이션. 흰 배경. 모던 플랫 미니멀 인포그래픽 스타일. 가로 타임라인 띠가 화면을 가로지르고 't=0s', 't≈30s', 't≈60s', 't≈120s' 네 시점이 표시돼 있다. 각 시점마다 muted slate gray 작은 아이콘과 캡션. (1) 't=0s' = 빨간 느낌표 + 캡션 'primary not responding', (2) 't≈30s' = 초록 위쪽 화살표 + 캡션 'standby promoted', (3) 't≈60s' = sky-blue DNS 레코드 아이콘 + 캡션 'CNAME flipped to standby', (4) 't≈120s' = 작은 체인 링크 아이콘 + 캡션 'client reconnected'. 타임라인 아래에 muted-orange 가는 막대 'failover wall-clock 60 to 120 seconds'. 상단 다크 슬레이트 캡션 'failover timeline: automatic at the database, manual at the client'. 절제된 sans-serif. 프로페셔널 IT 일러스트레이션 스타일.

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이 한 번씩 찍히는 패턴이 여기서 온다.

DNS 캐시가 일어나는 다섯 계층 stack 다이어그램. RDS endpoint 5초 TTL부터 OS resolver, JVM networkaddress.cache.ttl, JDBC connection pool, application까지 위로 쌓이며, 각 계층의 캐시가 자동 DNS 갱신을 막는 원리를 보여준다.

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는 끄는 게 합리적인 경우가 셋 있다.

16:9 가로 컨셉추얼 일러스트레이션. 흰 배경. 모던 플랫 미니멀 스타일. 큰 옥외 전기 차단기 박스 한 개가 열려 있다. 박스 안쪽에 두 개의 스위치. 왼쪽 스위치는 'single instance' 라벨이 붙어 down 위치, 가는 회색 케이블. 오른쪽 스위치는 'Multi-AZ' 라벨이 붙어 up 위치, 더 굵은 muted-AWS-orange 케이블이 두 갈래로 나뉘어 'primary instance billing' 과 'standby instance billing same hourly rate' 두 출력으로 이어진다. 오렌지 케이블에 '2x hourly cost' 라고 적힌 작은 가격표 아이콘이 매달려 있다. 상단 다크 슬레이트 캡션 'Multi-AZ one checkbox, two bills'. 절제된 sans-serif. 프로페셔널 IT metaphor 일러스트레이션 스타일.

첫째는 단일 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다. 가용성을 위한 복제와 읽기 부하를 위한 복제가 어떻게 다른지가 다음 단계다.

YouTube 영상

채널 보기
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
인공지능은 세상을 어떻게 숫자로 읽는가? - 이미지, 소리 그리고 텍스트가 행렬이 되는 원리 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학