Skip to content
Published on

이중화·삼중화 완전 가이드 — 99.999% 가용성의 비밀

Authors
  • Name
    Twitter
Redundancy & HA

들어가며

"서버 하나가 죽으면 서비스도 죽는다" — 이게 단일 장애점(SPOF)입니다.

이중화, 삼중화는 이 SPOF를 제거하는 기술입니다. 은행, 증권, 항공 관제 시스템은 삼중화까지 하고, 우리가 매일 쓰는 클라우드 서비스도 이중화 이상으로 운영됩니다.

가용성(Availability) 숫자의 의미

가용성 = 정상 운영 시간 / 전체 시간 × 100%
가용성등급연간 다운타임월간 다운타임
99%Two 9s3.65일7.3시간
99.9%Three 9s8.77시간43.8분
99.99%Four 9s52.6분4.38분
99.999%Five 9s5.26분26.3초
99.9999%Six 9s31.5초2.6초

핵심: 99.9% → 99.99%로 올리는 건 8시간 → 52분. 비용은 10배 이상 증가!

이중화 (Redundancy) 패턴

1. Active-Standby (액티브-스탠바이)

정상 상태:
  [Active 서버] ←── 모든 트래픽
  [Standby 서버]    (대기, 동기화만)

장애 발생:
  [Active 서버] ✗   장애 감지!
  [Standby 서버] ←── 트래픽 전환 (Failover)
Active로 승격
# Kubernetes에서 Active-Standby: StatefulSet + Leader Election
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-ha
spec:
  replicas: 2 # 1 Active + 1 Standby
  selector:
    matchLabels:
      app: redis
  template:
    spec:
      containers:
        - name: redis
          image: redis:7
          ports:
            - containerPort: 6379
        - name: sentinel # 장애 감지 + 자동 Failover
          image: redis:7
          command: ['redis-sentinel', '/etc/sentinel.conf']

장점: 리소스 효율적 (Standby는 최소 자원) 단점: Failover 시간 (수 초~수십 초 다운타임)

2. Active-Active (액티브-액티브)

정상 상태:
  [서버 A] ←── 트래픽 50%
  [서버 B] ←── 트래픽 50%
  Load Balancer가 분산

장애 발생:
  [서버 A] ✗   장애!
  [서버 B] ←── 트래픽 100% (자동)
# Kubernetes Service = 자동 Active-Active
apiVersion: v1
kind: Service
metadata:
  name: my-api
spec:
  selector:
    app: my-api
  ports:
    - port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 2 # 둘 다 Active!
  template:
    spec:
      containers:
        - name: api
          image: my-api:latest
          readinessProbe: # 장애 감지
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 5
            failureThreshold: 3 # 3번 실패 → 트래픽 제외

장점: 다운타임 거의 0 (이미 분산 중이므로) 단점: 데이터 동기화 복잡 (충돌 해결 필요)

3. N+1 / N+2 이중화

N+1: N대가 필요한데 1 (최소 여유분)
  [서버1] [서버2] [서버3] [+예비1]
1대 죽어도 OK

N+2: N대가 필요한데 2대 더
  [서버1] [서버2] [서버3] [+예비1] [+예비2]
2대 동시에 죽어도 OK + 1대 점검 가능

2N: 전체를 2배로 (가장 비쌈)
  [서버1] [서버2] [서버3] [서버4] [서버5] [서버6]
  → 반이 죽어도 OK (미션 크리티컬)

삼중화 (Triple Redundancy)

왜 삼중화?

이중화의 치명적 약점: 스플릿 브레인 (Split Brain)

이중화에서 네트워크 단절 시:
  [서버 A] ──✗── [서버 B]
  "B가 죽었나? 내가 Active!"  "A가 죽었나? 내가 Active!"
  → 둘 다 Active → 데이터 불일치 → 재앙!

삼중화 + 쿼럼으로 해결:

삼중화 (3노드 클러스터):
  [노드 A]──[노드 B]──[노드 C]

네트워크 분할 시:
  [노드 A]    [노드 B]──[노드 C]
  1 (소수)     2 (과반수 = 쿼럼!)
A는 자진 사퇴
B,C가 계속 서비스
  → 스플릿 브레인 방지!

쿼럼 공식: 과반수 = (N/2) + 1
  3노드: 2필요 (1노드 장애 허용)
  5노드: 3필요 (2노드 장애 허용)
  7노드: 4필요 (3노드 장애 허용)

etcd (Kubernetes의 뇌) — 삼중화 필수!

# etcd 3노드 클러스터 (Raft 합의 알고리즘)
# 1노드 죽어도 쿼럼(2/3) 유지 → 서비스 계속
# 2노드 죽으면 쿼럼 상실 → 읽기만 가능

# kubeadm 기본 설정
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
etcd:
  local:
    extraArgs:
      initial-cluster: >-
        etcd-0=https://10.0.0.1:2380,
        etcd-1=https://10.0.0.2:2380,
        etcd-2=https://10.0.0.3:2380
# Raft 합의 알고리즘 (의사코드)
class RaftNode:
    def __init__(self, node_id, peers):
        self.state = "follower"  # follower → candidate → leader
        self.term = 0
        self.voted_for = None
        self.log = []

    def request_vote(self):
        """리더 선출 — 과반수 투표 필요"""
        self.state = "candidate"
        self.term += 1
        votes = 1  # 자기 자신

        for peer in self.peers:
            if peer.grant_vote(self.term, self.log):
                votes += 1

        if votes > len(self.peers) // 2:  # 쿼럼!
            self.state = "leader"
            return True
        return False

    def replicate(self, entry):
        """데이터 복제 — 과반수 확인 후 커밋"""
        self.log.append(entry)
        acks = 1

        for peer in self.peers:
            if peer.append_entry(entry):
                acks += 1

        if acks > len(self.peers) // 2:  # 쿼럼!
            self.commit(entry)  # 과반수 확인 → 안전하게 커밋

계층별 이중화 전략

전체 아키텍처

[사용자]
[DNS]Route 53 헬스체크 (다중 리전)
[CDN]CloudFront (글로벌 엣지)
[L4 LB]NLB x2 (Active-Active, AZ 분산)
[L7 LB]ALB x2 (Active-Active)
[웹 서버]Deployment replicas: 3+ (Active-Active)
[앱 서버]Deployment replicas: 3+ (Active-Active)
   ├──▶ [DB Primary] ──동기복제──▶ [DB Standby]  (Active-Standby)
   │    └── 비동기복제 ──▶ [DB Read Replica x2]
   ├──▶ [Redis Primary] ── [Redis Replica x2]  (Sentinel)
   └──▶ [MQ]RabbitMQ 3노드 (쿼럼 큐)

데이터베이스 이중화

[동기 복제] (Strong Consistency)
  Primary ──COMMIT──▶ Standby
  "양쪽 다 쓸 때까지 기다림"
  장점: 데이터 손실 0
  단점: 느림 (네트워크 RTT 추가)

[비동기 복제] (Eventual Consistency)
  Primary ──COMMIT──▶ (나중에) Standby
  "일단 Primary에만 쓰고 응답"
  장점: 빠름
  단점: 장애 시 최근 데이터 유실 가능 (RPO > 0)

[반동기 복제] (Semi-Sync)
  Primary ──COMMIT──▶ Standby (1개만 확인)
  MySQL 기본 + 금융권 선호
-- PostgreSQL Streaming Replication 설정
-- Primary (postgresql.conf)
-- wal_level = replica
-- max_wal_senders = 3
-- synchronous_standby_names = 'standby1'

-- Standby 확인
SELECT client_addr, state, sync_state
FROM pg_stat_replication;
-- client_addr | state     | sync_state
-- 10.0.0.2   | streaming | sync
-- 10.0.0.3   | streaming | async

장애 복구 지표

RPO (Recovery Point Objective):
  "얼마나 많은 데이터를 잃어도 되나?"
  동기 복제: RPO = 0 (데이터 무손실)
  비동기 복제: RPO = 수 초~수 분

RTO (Recovery Time Objective):
  "얼마나 빨리 복구해야 하나?"
  Active-Active: RTO0
  Active-Standby: RTO = 수 초~수 분
  백업 복원: RTO = 수 시간

MTBF (Mean Time Between Failures):
  장애 간 평균 시간 (길수록 좋음)

MTTR (Mean Time To Repair):
  복구까지 평균 시간 (짧을수록 좋음)

가용성 = MTBF / (MTBF + MTTR)

비용 대비 가용성

가용성     │ 구성              │ 상대 비용
───────────┼───────────────────┼──────────
99%        │ 단일 서버         │ 1x
99.9%이중화 (1 리전)2~3x
99.99%     │ 삼중화 + 자동복구  │ 5~10x
99.999%    │ 다중 리전 Active10~50x
99.9999%   │ 다중 리전 + DR50~100x

📝 퀴즈 — 이중화/삼중화 (클릭해서 확인!)

Q1. Active-Standby와 Active-Active의 가장 큰 차이는? ||Active-Standby: 평시에 1대만 트래픽 처리, Failover 시 전환 지연 있음. Active-Active: 평시에 모두 트래픽 분산, 한 대 죽어도 나머지가 즉시 흡수 (거의 무중단)||

Q2. 스플릿 브레인(Split Brain)이란 무엇이고 어떻게 방지하나? ||네트워크 단절로 양쪽 모두 자신이 Active라고 판단하는 상태. 데이터 불일치 발생. 삼중화 + 쿼럼(과반수 투표)으로 방지 — 과반수를 얻지 못한 쪽이 자진 사퇴||

Q3. etcd가 3노드인 이유는? 4노드가 아닌 이유는? ||3노드: 1노드 장애 허용 (쿼럼 2/3). 4노드: 역시 1노드만 장애 허용 (쿼럼 3/4)으로 3노드와 같은데 비용만 증가. 홀수가 효율적||

Q4. RPO와 RTO의 차이는? ||RPO: 허용 가능한 데이터 손실량 (동기복제 = 0, 비동기 = 수초). RTO: 장애 발생부터 서비스 복구까지 허용 시간. 둘 다 짧을수록 좋지만 비용 증가||

Q5. 99.99%와 99.999% 가용성의 연간 다운타임 차이는? ||99.99% = 52.6분/년, 99.999% = 5.26분/년. 약 10배 차이이며 비용은 5~10배 이상 증가||

Q6. N+1과 2N 이중화의 차이는? ||N+1: 필요 대수(N) + 여유 1대 — 비용 효율적, 1대 장애 허용. 2N: 전체를 2배 — 비용 높지만 반이 죽어도 서비스 유지, 미션 크리티컬 시스템용||

Q7. 동기 복제와 비동기 복제를 각각 어떤 상황에서 쓰나? ||동기: 금융 거래, 결제 등 데이터 무손실 필수 (RPO=0). 비동기: 읽기 부하 분산, 분석용 복제 등 약간의 지연 허용 가능한 경우 (성능 우선)||

Q8. Raft 합의에서 리더 선출 과정을 설명하라. ||Follower가 리더 heartbeat를 못 받으면 Candidate로 전환 → term 증가 → 다른 노드에 투표 요청 → 과반수 득표 시 Leader 승격 → 클라이언트 요청 처리 시작||