1P by GN⁺ | ★ favorite | 댓글과 토론
  • k8s 기반 PostgreSQL 클러스터에서 네트워크 장애 시 복제 지연(replication lag) 이 누적되며 안전한 페일오버가 불가능해지는 구조적 약점을 해결한 방법
  • 기존 구조는 가용성(availability)내구성(durability) 보다 우선시해, 프라이머리가 쓰기를 계속 받는 동안 복제본이 뒤처지면서 데이터 손실 없이 승격할 후보가 사라짐
  • 해결책으로 페일오버 후보에 동기식 복제(synchronous replication) 를 적용하고, 오픈소스 고가용성 관리자 Patroni로 조율
  • 리더 풀 스탠바이만 동기식 복제에 참여하고 읽기 복제본은 비동기를 유지하는 하이브리드 복제 모델로, 내구성과 지연 시간 사이 균형 확보
  • remote_apply 모드 적용 시 쓰기 지연 53% 증가 등 성능 비용에도, 5가지 장애 시나리오 검증을 통해 안전한 자동 페일오버 달성

게임데이에서 드러난 문제

  • Datadog은 시스템과 프로세스의 빈틈을 선제적으로 찾기 위해 게임데이를 정기 실시, 플랫폼에 의도적 부하를 가해 실제 조건에서의 반응 학습
  • 한 게임데이에서 스테이징 환경에 가용 영역(AZ) 장애를 시뮬레이션하며 네트워크 지연을 유발, PostgreSQL 아키텍처의 약점 노출
    • 여러 Kubernetes 기반 PostgreSQL 클러스터의 프라이머리/라이터 노드가 영향받은 AZ에서 동작 중
    • 네트워크 지연 급증으로 프라이머리가 복제본과 안정적으로 통신 불가, 복제 지연 증가 → 쓰기 정체 → 애플리케이션이 오래된 데이터 제공
  • 충분히 최신 상태인 복제본이 없어 페일오버가 안전하지 않았고, 클러스터가 사실상 멈춤
  • 이 구조는 정상 조건에서는 잘 작동했으나, 특정 네트워크 장애 상황에서 가용성을 내구성보다 우선시해 안전한 복구 경로가 없음
    • 프라이머리는 복제가 지연되는 동안에도 쓰기를 계속 수용, 복제 지연이 커지며 복제본이 더 뒤처짐
    • 결과적으로 데이터 손실 위험 없이는 페일오버 후보를 승격할 수 없었고, 지연이 잦아들고 복제본이 따라잡기를 기다리는 것이 유일한 선택지
  • 목표는 PostgreSQL 성능 특성을 필요 이상으로 훼손하지 않으면서 페일오버를 자동이면서 안전하게 만드는 것

기준 아키텍처: Kubernetes 위의 PostgreSQL

  • Kubernetes 기반 PostgreSQL 클러스터는 leader poolread replica pool 두 풀로 구성, PostgreSQL은 단일 라이터(single-writer) 시스템
    • 읽기/쓰기 분리로 리더 부담 없이 읽기 확장 가능, 쓰기 지연은 예측 가능하고 안정적으로 유지
  • leader pool은 모든 쓰기를 처리하는 단일 활성 라이터 노드 1개와 스탠바이 노드 2개로 구성
    • 스탠바이는 애플리케이션 트래픽을 처리하지 않으나 리더 장애 시 승격 가능
  • read replica pool은 읽기 전용 트래픽을 처리하는 다수 노드로 구성, 읽기 확장성과 쿼리 격리에 최적화되어 페일오버 대상에서 의도적으로 제외

Patroni와 ZooKeeper의 역할

  • Patroni가 복제, 페일오버, 리더 선출을 관리하며, 분산 구성 저장소(DCS)로 ZooKeeper 사용
    • ZooKeeper는 현재 리더 키/락, 클러스터 구성, 각 멤버의 복제 상태(최신 LSN 등) 메타데이터 저장
    • Patroni는 이 정보로 승격·강등을 보수적으로 판단, 공격적 페일오버보다 데이터 일관성 우선
  • 새 노드가 클러스터에 합류하면 먼저 ZooKeeper에서 리더 존재 여부 확인
    • 리더가 없으면 임시 znode 생성으로 리더 키 획득 시도, ZooKeeper가 단일 노드만 키 획득을 보장해 다중 프라이머리 형성 방지
    • 리더가 이미 있으면 자신을 복제본으로 구성하고 스트리밍 복제 시작
  • 네트워크 분할(network partition) 시 리더 또는 ZooKeeper와의 연결이 끊긴 복제본은 클러스터 상태를 확인할 수 없어, Patroni가 해당 노드를 일시 중지 또는 강등
  • 리더가 연결을 잃으면 Patroni가 ZooKeeper와 협력해 적격 스탠바이 하나만 리더 락 획득, 부분 네트워크 장애에서도 통제된 페일오버 보장
    • 연결 복구 후 기존 리더는 리더 락 재획득에 실패하면 스스로 스탠바이로 강등, split brain 방지

안전한 페일오버가 불가능했던 이유

  • 단일 라이터 모델에서 장애 시 Patroni가 정상 스탠바이 중 새 리더를 선출
  • 데이터 손실 방지를 위해 Patroni는 승격 전 안전 점검 수행, 핵심은 복제 지연maximum_lag_on_failover 임계값 이내인지 확인
    • 스탠바이가 리더보다 뒤처졌으면 승격 시 데이터 누락·불일치 발생 가능
  • 게임데이에서 프라이머리가 연결을 잃자 모든 스탠바이의 복제 지연이 임계값 초과, Patroni가 페일오버를 정확히 거부
    • 클러스터가 안전한 쓰기 가능 프라이머리 없이 남은 것은 Patroni 때문이 아니라 안전한 승격 후보가 없었기 때문

스트리밍 복제의 두 가지 모드

  • 스트리밍 복제에서 리더는 모든 변경을 담은 write-ahead log(WAL) 를 복제본에 지속 전송, 복제본은 WAL을 로컬에 적용해 동기화 유지
  • 비동기식 복제 (기본값)

    • 리더가 트랜잭션 커밋 전 복제본의 확인을 기다리지 않음
    • 쓰기 지연 최소화, 높은 처리량 지원
    • 단, 리더 장애 시 프라이머리에 커밋됐으나 아직 복제되지 않은 트랜잭션이 승격 과정에서 손실 가능
  • 동기식 복제

    • 리더가 클라이언트에 응답을 보내기 전 최소 1개 복제본의 확인 대기
    • 적어도 하나의 복제본이 너무 뒤처질 위험을 크게 줄이고, 커밋된 트랜잭션이 다른 노드에 존재함을 확인 후 응답하므로 더 강한 내구성 보장
    • 페일오버 후보가 최신 상태일 가능성이 높아 데이터 분기 위험 없이 승격 가능

하이브리드 복제 설정

  • 내구성·지연·처리량 균형을 위해 하이브리드 복제 모델 채택
    • leader pool의 스탠바이 노드는 동기식 복제에 참여, 리더가 지정 동기 스탠바이의 확인 후 쓰기 커밋
    • read replica는 비동기식 복제 유지, 읽기 전용 트래픽만 처리하고 페일오버 대상이 아니므로 리더 풀 복제 부담 제한
  • 이를 통해 읽기 복제본에 동일한 지연 비용을 부과하지 않으면서 페일오버 후보에만 엄격한 내구성 보장 적용

안전한 페일오버를 위한 PostgreSQL·Patroni 튜닝

  • 동기식 복제 활성화를 위해 PostgreSQL과 Patroni 양쪽에서 파라미터 조정
  • 조정한 주요 파라미터

    • synchronous_mode: Patroni 동기식 복제 활성화, truesynchronous_node_count에 따라 동기 스탠바이 확인 후 커밋 (기본값 false → true, Patroni 관리, 필수)
    • synchronous_node_count: 동기 스탠바이 노드 수, synchronous_standby_names 목록 생성에 사용 (기본값 1 → 1, Patroni 관리, 선택)
    • synchronous_mode_strict: 엄격 동기 모드 강제, true이고 가용 복제본이 없으면 비동기 전환 대신 쓰기 차단 (기본값 false → true, Patroni 관리, 선택)
    • synchronous_commit: PostgreSQL 커밋 내구성 설정 (기본값 on → remote_apply, PostgreSQL 관리, 선택)
  • 적용 후 리더는 동기 스탠바이가 데이터 수신·적용을 확인한 뒤에만 클라이언트에 트랜잭션 응답 전송

내구성과 지연 시간의 균형

  • 동기식 복제는 내구성을 개선하나, 리더가 동기 스탠바이의 확인을 기다리므로 쓰기 지연 증가, 지속 부하 시 처리량 영향 가능
  • 성능 영향은 동기 스탠바이 수(synchronous_node_count)와 synchronous_commit로 설정한 내구성 수준에 좌우
  • synchronous_commit 내구성 단계별 트레이드오프

    • remote_apply: 복제본이 WAL을 쓰고 플러시하고 재생할 때까지 대기, 가장 강한 일관성·가장 높은 지연
    • on(내부적으로 remote_flush): 복제본이 WAL을 디스크에 플러시할 때까지 대기, 강한 내구성이나 스탠바이에서 아직 읽기 불가
    • remote_write: WAL이 복제본 OS 캐시(디스크 아님)에 도달할 때까지 대기, 낮은 지연이나 OS 크래시에 취약
    • local: 스탠바이 대기 없이 로컬 디스크 플러시 후 커밋, 노드 간 내구성 보장 없음
    • off: 로컬 WAL 플러시 전 커밋, 가장 낮은 지연·가장 높은 데이터 손실 위험

동기식 복제 성능 벤치마킹

  • 각 커밋이 하나 이상 스탠바이의 확인을 기다리므로 동기식 복제는 지연을 추가, 영향 정량화를 위해 PostgreSQL 표준 부하 테스트 도구 pgbench로 벤치마크 수행 (Patroni 버전 3.2.1)
  • 단순 읽기·쓰기 혼합을 시뮬레이션하는 TPC-B 트랜잭션 스위트 사용, 두 지표 측정
    • 평균 지연: 트랜잭션당 평균 처리 시간(ms)
    • 초당 트랜잭션 수(tps): 연결 설정 시간 제외 완료 트랜잭션 수
  • 테스트 파라미터

    • 스케일 팩터(데이터베이스 크기), 클라이언트 수(동시 사용자 트래픽), 스레드 수(CPU·병렬성), 트랜잭션 수(워크로드 강도)를 변화시켜 운영 유사 조건 모사
    • 쿼럼 커밋 모드의 동기식 복제는 이번 벤치마크에서 테스트 미실시
  • 벤치마크 결과

    • 쓰기 지연 증가율: remote_apply 53%, on 46%, remote_write 38%, local 32%
    • 처리량(tps) 감소율: remote_apply 34%, on 31%, remote_write 27%, local 23%
    • remote_apply는 복제본의 WAL 재생·적용까지 대기해 일관되게 가장 높은 지연·가장 낮은 처리량을 보였으나, 가장 강한 일관성으로 안전한 페일오버에 적합
  • 프로덕션 적용

    • 벤치마킹 후 여러 고쓰기 클러스터에 remote_apply 배포, 지속적인 프로덕션 부하에서도 애플리케이션 수준 쓰기 지연·처리량에 유의미한 영향 없음
    • 성능 위험 완화를 위해 데이터센터·워크로드 계층별로 단계적 롤아웃, 단계 사이에 베이크인 기간과 지속 모니터링 적용
    • 예: 고처리량 리소스 처리 워크로드는 동기식 복제 활성화 후 DB 쓰기 지연 증가에도 처리 지연이나 하류 백로그 없이 계속 동작
    • synchronous_commit은 다운타임 없이 patronictl edit-config로 즉시 조정 가능, 초고처리량 워크로드의 커밋 내구성을 신속히 낮출 유연성 제공

장애 시나리오를 통한 페일오버 검증

  • 동기식 복제와 엄격한 페일오버 제어가 데이터 무결성 보호, split-brain 방지, 자동 복구를 보장하는지 검증
  • 시나리오 1: 동기 스탠바이 1개 손실

    • 동기 스탠바이 손실 시 Patroni가 다른 적격 스탠바이를 할당해 동기식 복제 유지 시도
    • 리더 노드의 Patroni가 pg_stat_replication으로 끊김·정체·지연 스트리밍 연결을 감지하고 ZooKeeper로 복제본 멤버십 추적
    • 정상·적격 스트리밍 복제본 목록을 재계산하고 synchronous_node_count에 따라 synchronous_standby_names 갱신, 동기식 복제 활성 상태로 계속 동작
  • 시나리오 2: 모든 동기 스탠바이 손실

    • 동작은 synchronous_mode_strict 값에 좌우
    • 비엄격 모드: 쓰기 가용성 우선

      • Patroni가 synchronous_standby_names를 비워 동기식 복제를 일시 비활성화, 정상 복제본 재합류 전까지 리더가 비동기로 전환해 쓰기 계속 허용
    • 엄격 모드: 안전을 위해 쓰기 차단

      • Patroni가 synchronous_standby_names*로 설정, 명시적 동기 스탠바이가 없어도 PostgreSQL이 쓰기 트랜잭션을 수용·로컬 커밋하나 복제본이 WAL을 확인할 때까지 클라이언트 응답 차단
      • 동기식 복제에 합류 가능한 적합 복제본이 재연결되면 Patroni가 동기 스탠바이 역할 부여
  • 시나리오 3: 모든 스탠바이·복제본 노드 불가용

    • 모든 복제본이 불가용하고 synchronous_mode_strict = true이면 PostgreSQL이 최소 1개 적격 복제본 복귀까지 트랜잭션 확인 보류
    • 데이터 일관성은 유지되나 애플리케이션 수준에서 일시적 쓰기 불가 발생
  • 시나리오 4: 동기 커밋 중 리더 장애

    • 리더가 동기 스탠바이 확인을 기다리다 확인 수신 전 중단되는 엣지 케이스
    • 흔한 원인: 커밋 중 클라이언트의 트랜잭션 취소, 리더 PostgreSQL 프로세스 크래시·종료, 커밋 단계 중 네트워크 분할
    • PostgreSQL이 WAL을 로컬에 플러시했으나 스탠바이로 복제 실패 시, 확인이 없어 트랜잭션이 복제본에 보이지 않음
    • 리더가 동기 스탠바이로 WAL 복제 전 크래시하고 해당 스탠바이가 승격되면 트랜잭션 손실 가능, 기존 리더와 신규 프라이머리의 히스토리 분기
    • 기존 리더는 pg_rewind로 타임라인 분기 지점을 식별하고 데이터 디렉터리를 신규 프라이머리 타임라인에 맞춰 되감아, 복제되지 않은 로컬 변경을 폐기 후 스탠바이로 재합류
    • 이 동작은 Patroni가 아닌 PostgreSQL의 동기 커밋 내부 처리 결과, 쿼럼 설정과 확인 메커니즘의 신중한 튜닝·모니터링 필요성 부각
  • 시나리오 5: ZooKeeper 불가용

    • ZooKeeper 불가용 시 Patroni가 리더십 확인·신규 선출 불가, 데이터 불일치 방지를 위해 보수적 동작으로 전환
    • failsafe 모드 비활성화 시

      • ZooKeeper 도달 불가여도 리더가 접근 가능하고 모든 노드가 정상이면 쓰기 계속, 단 리더 락 TTL 만료까지만 유지
      • 리더 키 갱신 루프 시간이 지나고 락 갱신 불가 시 Patroni가 리더를 강등하고 클러스터를 읽기 전용으로 전환
    • failsafe 모드 활성화 시

      • 리더가 ZooKeeper 연결을 잃으면 Patroni가 REST API로 모든 클러스터 멤버 도달 가능 여부를 지속 확인
      • 모든 멤버가 접근 가능할 때만 쓰기 계속, 그렇지 않으면 읽기 전용으로 강등

동기식 복제하의 수동 페일오버·스위치오버

  • Patroni는 헬스 체크와 ZooKeeper 조율 기반 자동 페일오버·스위치오버 외에 patronictl 명령으로 수동 작업도 지원, 동기식 복제 활성 시 모든 스탠바이가 유효 후보는 아니므로 데이터 무결성 보호용 가드레일 적용
  • 비동기 스탠바이로의 페일오버

    • patronictl failover: 선택 노드가 비동기면 실패
    • patronictl switchover: 선택 노드가 비동기면 실패
    • 동기식 복제 활성 중 비동기 노드로 페일오버를 강제하면 내구성 보장을 우회해 데이터 손실 가능
  • 동기 스탠바이를 대상으로 할 때

    • patronictl failover: 성공, 리더가 동기 스탠바이로 전환
    • patronictl switchover: 성공, 리더와 동기 스탠바이 간 우아한 핸드오프 수행
  • 다양한 synchronous_commit 모드 동작과 Patroni 가드레일 검증 후, 고쓰기·고읽기·혼합 워크로드 프로덕션 클러스터에 동기식 복제 활성화, 지연·처리량에 측정 가능한 영향 없음
  • 문제가 생기면 synchronous_mode: false 설정으로 다운타임 없이 비동기 복제로 안전하게 되돌리기 가능

DRBD를 선택하지 않은 이유

  • 고가용성 평가에서 블록 수준 복제 시스템 DRBD(Distributed Replicated Block Device) 도 검토, PostgreSQL 데이터 디렉터리와 WAL 파일을 포함한 전체 볼륨을 서버 간 미러링해 거의 실시간 스탠바이 복제본 생성
  • DRBD는 PostgreSQL 내장 스트리밍 복제보다 낮은 지연을 제공할 수 있으나, 신규 인프라·모니터링·운영 플레이북을 포함한 상당한 아키텍처 전환 필요
  • 성숙한 Kubernetes 기반 설정과 PostgreSQL 동기식 복제의 세밀한 제어를 고려해, 가시성·유연성·운영 신뢰가 더 나은 데이터베이스 수준 복제 선택

동기식 복제 모니터링

  • 동기식 복제 활성화 후 복제 상태와 페일오버 준비도를 면밀히 모니터링, 특히 두 신호가 대규모 안정성 유지에 기여
  • SyncRep 대기 이벤트

    • 프라이머리가 커밋 완료·상태 반환 전 동기 스탠바이의 확인을 기다릴 때 발생, 일부는 정상이나 길거나 잦은 대기는 복제본 성능 문제나 노드 간 네트워크 지연을 시사
    • 중요한 이유: 장기 대기는 쓰기 지연 증가·처리량 감소 유발
    • 추적 대상: SyncRep·WalSenderWaitForReply 대기 이벤트의 지속 시간·빈도, wait_event:SyncRep 태그로 필터링한 postgresql.activity.waits Datadog 메트릭(내부적으로 pg_stat_activity 테이블 쿼리)에서 수집
  • 동기 스탠바이 미감지

    • Patroni가 정상 동기 스탠바이를 장기간 감지하지 못하면 클러스터가 안전한 페일오버 능력 상실
    • 중요한 이유: 동기 스탠바이가 없으면 페일오버 시 데이터 손실에 취약
    • 알림 기준: patroni_sync_standby가 비어 있는 상태가 지속되면 고가용성(HA) 헬스 알림 발생, OpenMetrics 데이터에서 수집하며 네이티브 Datadog 통합 없음
  • 동기식 복제는 내구성을 개선하나 복제본이 비정상·도달 불가일 때 가용성·성능 저하, 대기 시간과 스탠바이 가용성 모니터링이 부하 상황에서 가용성·성능 유지에 필수

설계 단계부터 안전한 페일오버

  • 시뮬레이션된 AZ 장애가 PostgreSQL 아키텍처의 치명적 약점을 드러냄, 복제본이 리더보다 뒤처져 네트워크 장애를 기다리거나 데이터 분기를 감수하는 양자택일 강요, 프로덕션에서 수용 불가한 트레이드오프
  • Patroni 기반 동기식 복제 채택과 내구성·지연 튜닝으로 저하된 네트워크 조건에서도 페일오버를 가능하고 안전하게 구현, 벤치마킹과 반복 장애 시뮬레이션으로 대규모 성능 훼손 없이 예측 가능한 복구 확인
  • 동기식 복제 장애 중 쓰기를 차단함으로써 실패를 상류 서비스에 명시적으로 노출, 비동기 복제처럼 쓰기가 조용히 손실되지 않고 재시도·큐잉 등으로 대응 가능해 장애 모드가 더 가시적이고 복구 가능
  • 향후 쿼럼 기반 커밋 모드와 복제 상태에 대한 더 깊은 관측성 탐색 중

댓글과 토론