LLM WikiAccess-protected knowledge portal
← 스터디 홈
5편 · 약 26분

고가용성 패턴: primary-replica, failover, quorum, split-brain 방지

복구보다 빠른 길이 있다

앞 장에서 PITR로 복구하면 수십 분이 걸린다고 했다. 노드 장애라면 훨씬 빠른 길이 있다. Primary가 죽었을 때 이미 준비된 Replica를 곧바로 Primary로 올리면 장애 시간이 수초~수분으로 줄어든다. 이것이 고가용성(HA) 아키텍처가 하는 일이다.

그러나 HA는 단순히 "replica 하나 더 두면 되는 것"이 아니다. 자동 failover를 구현하는 순간 split-brain 문제가 생기고, quorum 없이 failover하면 데이터가 갈라질 수 있다. 이 장에서는 HA의 핵심 패턴과 그 뒤에 숨어있는 분산 시스템의 긴장관계를 정리한다.


Primary-Replica 복제 기초

모든 HA의 출발점은 primary-replica 복제다. Primary는 쓰기를 받고, 변경 사항을 로그(MySQL binlog, PostgreSQL WAL)로 내보낸다. Replica는 이 로그를 수신·적용하며 Primary와 동기화를 유지한다.

Application 쓰기 → Primary
읽기 → Primary 또는 Replica
(프록시 경유)
Primary 쓰기 처리
binlog / WAL 생성
현재 Leader
Replica 1 읽기 트래픽 분산
failover 후보 1순위
Replica 2 failover 후보 2순위
또는 다른 AZ 대기
비동기 복제 (기본): Primary는 replica의 확인을 기다리지 않고 commit 반환.
장점: 낮은 레이턴시. 단점: replica 지연 → failover 시 최신 데이터 손실 가능 (RPO > 0)
반동기 복제 (semi-sync): 1개 이상 replica가 수신 확인 후 commit.
장점: RPO ≈ 0 보장. 단점: 레이턴시 증가. replica 없으면 동기 대기로 전환.
Primary-Replica 복제 아키텍처

복제 지연(Replication Lag)

Replica가 Primary를 얼마나 뒤따르는지를 복제 지연(replication lag)으로 측정한다.

-- MySQL: replica에서 실행
SHOW REPLICA STATUS\G
-- Seconds_Behind_Source: 현재 지연 초

-- PostgreSQL: primary에서 실행
SELECT client_addr,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

복제 지연이 크면 failover 시 데이터 손실이 발생한다. 비동기 복제 환경에서 unexpectedly한 failover가 일어나면, primary에는 있었으나 replica에는 없는 트랜잭션이 사라진다. 이를 errant transaction이라고 부른다.


수동 Failover vs 자동 Failover

수동 Failover

운영자가 직접 실행한다.

1. Primary 장애 확인 (네트워크 분리? DB 프로세스 죽음? 디스크 꽉 참?)
2. 가장 복제 진행이 많은 replica 선택
3. STOP REPLICA; (MySQL) / pg_ctl promote (PostgreSQL)
4. 다른 replica들의 replication source를 새 primary로 변경
5. VIP / DNS / 프록시가 새 primary를 가리키도록 변경
6. 앱 재연결 확인

RTO는 수 분~수십 분. 사람이 개입하기 때문에 오판 가능성이 있다. Primary가 실제로 죽은 건지, 네트워크 일시 장애인지 구분하기 어려운 순간이 많다.

자동 Failover

HA 관리 소프트웨어가 Primary 장애를 감지하고 자동으로 새 primary를 선출한다.

도구DB특징
PatroniPostgreSQLetcd/Consul/ZooKeeper로 quorum, watchdog fencing
pg_auto_failoverPostgreSQL경량, monitor 노드 1개로 운영
OrchestratorMySQL토폴로지 자동 감지, semi-sync 인식, GTID 기반
MHA (Master HA)MySQL성숙한 도구, binlog 보완 기능
ProxySQL + OrchestratorMySQL프록시 연동 자동 라우팅

Quorum: 다수결로 리더를 정하는 이유

자동 failover의 가장 큰 위험은 split-brain이다. Primary A가 죽은 것처럼 보여서 Replica B를 새 primary로 올렸는데, A가 실제로는 살아있어서 둘 다 primary로 쓰기를 받는 상황이다. 이 시점부터 두 DB는 서로 다른 데이터를 갖게 되고, 일관성이 영구적으로 깨진다.

이를 막는 핵심 메커니즘이 quorum(쿼럼)이다. 클러스터에서 과반수(n/2 + 1)의 노드가 동의해야만 새 primary를 선출할 수 있다.

정상 상태 (3노드 클러스터)
Primary Replica 1 Replica 2
3개 노드 모두 통신
etcd/Consul에 리더 키 유지
쿼럼 = 2/3 → 정상
Primary 장애 → Failover
Primary Replica 1
→ 승격
Replica 2
Replica 1, 2가 2/3 쿼럼 충족
Replica 1을 새 Primary로 선출
구 Primary는 차단
네트워크 분리 (Split)
Primary
(단독)
Replica 1 Replica 2
Primary: 1/3 쿼럼 미달 → 자동 강등
Replica 1+2: 2/3 쿼럼 → 새 Primary 선출
Split-brain 방지 ✓
Quorum 기반 리더 선출과 Split-Brain 방지

Patroni의 quorum 동작

Patroni는 DCS(Distributed Configuration Store: etcd, Consul, ZooKeeper)에 리더 키를 등록하고, Primary가 주기적으로 갱신한다. Primary가 키를 갱신하지 못하면(장애 또는 네트워크 단절) 키가 만료되어 새 선출이 시작된다.

# patroni.yml 핵심 설정
bootstrap:
  dcs:
    ttl: 30          # 리더 키 만료 시간(초)
    loop_wait: 10    # 상태 확인 주기(초)
    retry_timeout: 10

postgresql:
  parameters:
    synchronous_commit: "on"        # 동기 복제 활성화
    synchronous_standby_names: "*"  # 1개 이상 standby 확인 후 commit

Fencing과 STONITH: 쓰기를 강제로 막는 방법

Quorum만으로는 충분하지 않다. 쿼럼에서 탈락한 구 Primary가 리더 키를 잃었음을 모르고 계속 쓰기를 받는 경우가 있다. 이를 막기 위해 fencing(격리)이 필요하다.

STONITH (Shoot The Other Node In The Head): 다른 노드를 강제로 셧다운하거나 네트워크를 차단해 확실하게 격리하는 메커니즘이다.

Linux Watchdog Patroni watchdog 활성화
DCS 리더십 상실 감지 시
즉시 자기 자신 kill
(self-STONITH)
설정: watchdog_device = /dev/watchdog
가장 안전한 방법
클라우드 API 격리 AWS: EC2 Stop/Terminate API
GCP: instance stop
새 Primary가 구 Primary를
클라우드 API로 강제 중지
IAM 권한 필요
네트워크 가용 시 동작
네트워크 차단 Security Group / ACL로
구 Primary의 DB 포트 차단
앱이 연결 불가 상태로
격리 효과
빠르지만 DB 프로세스 자체는
살아 있음 — 완전하지 않음
Fencing 메커니즘 유형

Fencing이 없으면 HA가 split-brain을 허용하는 구조가 된다. 따라서 자동 failover를 운영한다면 반드시 fencing 전략을 함께 설계해야 한다.


VIP와 프록시: 앱이 모르게 failover 처리하기

Failover가 일어나면 새 Primary의 IP가 바뀐다. 앱이 Primary IP를 직접 쓴다면 재시작하거나 재설정해야 한다. 이를 투명하게 처리하는 두 가지 방법이 있다.

VIP (Virtual IP): 클러스터 중 현재 Primary 노드가 VIP를 보유한다. Failover 시 새 Primary로 VIP가 이동(ARP 재공지). 앱은 항상 동일한 IP로 연결된다.

프록시 레이어: ProxySQL, HAProxy, PgBouncer 같은 프록시가 현재 Primary를 추적하고 쓰기 트래픽을 라우팅한다. 앱은 프록시 주소만 안다.

Application 항상 같은
엔드포인트
VIP 또는 프록시
(ProxySQL/HAProxy)
현재 Primary 감지
쓰기 라우팅
Primary
(현재)
Replica
Failover 발생 →
Patroni/Orchestrator가 새 Primary 선출
VIP 이전 또는 프록시 라우팅 업데이트
앱 재시작 없이 새 Primary로 연결
Failover 시 트래픽 전환 경로

HA 구성 유형 비교

구성최소 노드 수RTORPOSplit-brain 위험비용
Primary + 1 Replica (수동 failover)2수 분~수십 분비동기면 수 초낮음 (사람이 판단)낮음
Primary + 2 Replica (Patroni quorum)330초~3분동기면 ≈0매우 낮음중간
Primary + Replica + Witness (DCS 분리)2DB + 1 Witness30초~3분비동기낮음중간
Multi-Primary (Galera, InnoDB Cluster)3+거의 0≈0낮음 (쿼럼 내장)높음

Witness 노드: DB를 실행하지 않고 쿼럼 투표만 참여하는 경량 노드다. 2 DB 노드로 쿼럼을 구성하기 어려울 때 낮은 비용으로 홀수 클러스터를 만든다.


MySQL InnoDB Cluster / Group Replication

MySQL 8.0+의 InnoDB Cluster는 MySQL Shell + MySQL Router + Group Replication을 결합해 built-in HA를 제공한다.

Group Replication은 Paxos 기반의 분산 합의 프로토콜을 내장해 별도 DCS 없이 쿼럼과 자동 failover를 처리한다.

-- InnoDB Cluster 상태 확인 (MySQL Shell)
-- JS 모드에서 실행
var cluster = dba.getCluster();
cluster.status();
/*
{
  "clusterName": "prodCluster",
  "defaultReplicaSet": {
    "status": "OK",
    "topology": {
      "db1:3306": { "mode": "R/W", "status": "ONLINE" },
      "db2:3306": { "mode": "R/O", "status": "ONLINE" },
      "db3:3306": { "mode": "R/O", "status": "ONLINE" }
    }
  }
}
*/

HA 설계 시 자주 놓치는 것

1. 두 노드 클러스터의 함정

2개 노드만 있으면 쿼럼을 구성할 수 없다. 한 노드가 죽으면 나머지 1개는 1/2로 과반수가 안 된다. Patroni는 이 경우 failover를 거부한다. 최소 3노드를 쓰거나 witness를 추가해야 한다.

2. DCS 자체의 가용성

Patroni는 etcd/Consul이 있어야 동작한다. DCS 클러스터가 quorum을 잃으면 Patroni는 failover를 못 한다. DCS 자체도 3노드 이상으로 구성해야 한다.

3. 읽기 트래픽의 일관성

Replica에 읽기를 분산할 때 복제 지연을 고려해야 한다. "방금 내가 쓴 데이터가 안 보인다"는 read-your-write 불일관성이 발생할 수 있다. 세션 기반으로 write 이후의 read를 primary로 강제 라우팅하거나, synchronous_commit을 높여야 한다.

4. 복구 후 구 Primary 복귀

Failover 후 구 Primary가 복구되면 그냥 cluster에 붙이면 안 된다. 구 Primary에만 있던 errant transaction이 있을 수 있어 데이터 충돌이 생긴다. 구 Primary를 새 Replica로 rebind하기 전에 반드시 토폴로지를 확인하고 diverged transaction을 처리해야 한다.

-- MySQL GTID 기반 errant transaction 확인
-- 새 Primary에서
SELECT @@global.gtid_executed;
-- 구 Primary에서
SELECT @@global.gtid_executed;
-- 두 값 비교 → 구 Primary에 남아있는 GTID set이 errant

운영 체크리스트

HA 구성 확인

  • [ ] Primary 장애 시 자동 failover가 동작하는가?
  • [ ] Failover 후 VIP/프록시가 새 Primary로 자동 전환되는가?
  • [ ] Fencing(watchdog/클라우드 API)이 구성되어 있는가?
  • [ ] DCS(etcd/Consul) 자체가 3노드 이상으로 HA 구성인가?
  • [ ] 클러스터가 홀수 노드인가? (quorum 구성 가능 여부)

Failover 검증

  • [ ] 지난 6개월 이내에 실제 failover 드릴을 해봤는가?
  • [ ] 비계획적 primary 중단 시 앱 재시작 없이 재연결되는가?
  • [ ] Failover 후 구 primary를 replica로 안전하게 복귀시키는 절차가 있는가?
  • [ ] Replica에 읽기 트래픽을 붙였을 때 read-your-write 정합성 요구사항을 검토했는가?

기억할 문장

HA는 "노드가 많으면 안전하다"가 아니다. Quorum이 올바르게 구성되고, Fencing이 동작하며, Failover가 주기적으로 검증된 클러스터가 실제로 고가용성이다. Split-brain은 장애보다 더 나쁜 결과를 만든다. HA를 넣었다면 반드시 Fencing도 함께 넣어야 한다.

다음 장에서는 데이터베이스의 장기적 안정성을 위한 용량 계획(CPU, memory, IOPS, connection, disk growth 예측)을 다룬다.

References

  • Patroni Documentation, Watchdog — https://patroni.readthedocs.io/en/latest/watchdog.html
  • Patroni GitHub, How Patroni manages split brain — https://github.com/patroni/patroni/issues/1114
  • PostgreSQL Documentation, High Availability — https://www.postgresql.org/docs/current/high-availability.html
  • Percona Blog, High Availability for MySQL — https://www.percona.com/blog/high-availability-for-mysql/
  • Stormatics, Split-Brain Scenarios in Highly Available PostgreSQL Clusters — https://stormatics.tech/blogs/understanding-split-brain-scenarios-in-highly-available-postgresql-clusters
  • Google Cloud Architecture Center, High Availability PostgreSQL clusters on Compute Engine — https://docs.cloud.google.com/architecture/architectures-high-availability-postgresql-clusters-compute-engine
  • MySQL Documentation, InnoDB Cluster — https://dev.mysql.com/doc/mysql-shell/8.0/en/mysql-innodb-cluster.html
  • EnterpriseDB Blog, The Do's and Don'ts of Postgres HA Part 3 — https://www.enterprisedb.com/blog/dos-donts-postgres-high-availability-pt-3-tools-rules
  • ProxySQL Blog, Orchestrator for PostgreSQL — https://proxysql.com/blog/orchestrator-postgresql-intro/
  • Aerospike Blog, Understanding Failover Mechanisms for HA — https://aerospike.com/blog/understanding-failover-mechanisms/