본문으로 건너뛰기

11.3 흔한 장애 시나리오

Patroni 클러스터에서 반복적으로 마주치는 장애는 유형이 정해져 있습니다. 이 챕터는 다섯 가지 시나리오를 놓고, 각 상황에서 Patroni가 어떻게 반응하도록 설계되어 있는지와 DBA가 어디부터 확인해야 하는지를 정리합니다.

시나리오 A: DCS 장애​

etcd 클러스터가 quorum을 잃거나 네트워크가 끊겨 DCS에 닿지 못하면, primary는 leader lock을 갱신하지 못합니다. 이때의 기본 동작을 공식 문서는 "leader lock 갱신에 실패하면 PostgreSQL은 즉시 demote되어 read-only로 시작된다"고 기술합니다. DCS가 안 보이는 상황에서는 다른 노드가 승격했는지 알 길이 없으므로, 이중 primary를 막는 쪽으로 보수적으로 움직이는 것입니다. 결과적으로 DCS 전체 장애는 클러스터 전체의 쓰기 중단으로 번집니다.

이 강등을 피하는 장치가 failsafe_mode(기본 false)입니다. DCS는 죽었지만 멤버끼리는 통신이 되는 상황에서, primary가 모든 멤버에게 REST 요청을 보내 전원이 응답하면 primary를 유지합니다. 한 멤버라도 응답하지 않으면 demote됩니다. 동작 조건과 한계는 2.5 DCS 장애와 failsafe mode에서 다뤘습니다.

DCS 안의 클러스터 정보만 유실된 경우는 다릅니다. etcd 데이터가 지워졌어도 PostgreSQL이 살아 있으면, Patroni는 이미 실행 중인 클러스터를 감지하고 leader race를 거쳐 DCS 키를 다시 만듭니다. 이 경우는 재초기화가 아니라 키 재생성으로 끝납니다.

확인 순서는 patronictl list로 클러스터 상태를 보고, 각 노드의 GET /patroni에서 cluster_unlocked와 failsafe_mode_is_active를 읽는 것입니다. patroni_dcs_last_seen이 정체된 시각이 곧 장애 시작점입니다.

시나리오 B: replication slot 유실과 잔존​

slot 관련 장애는 방향이 반대인 두 유형으로 나뉩니다. slot이 사라져서 나는 장애와, slot이 남아서 나는 장애입니다.

먼저 사라지는 쪽입니다. 멤버가 내려가면 upstream에 있던 그 멤버의 slot은 member_slots_ttl(기본 30분, 4.0.0 이상) 경과 후 제거됩니다. slot이 사라진 뒤에는 그 멤버가 필요로 하던 WAL이 재활용되므로, 장기간 다운됐던 멤버는 재기동해도 복제를 이어가지 못하고 patronictl reinit로 데이터 디렉토리를 다시 만들어야 합니다. 계획된 장기 정지가 예상되면 member_slots_ttl을 늘리거나, 해당 멤버의 slot을 permanent slot으로 선언해 두는 방법이 있습니다(3.2.0 이상). standby cluster의 원본 쪽 slot은 Patroni가 자동 관리하지 않으므로 처음부터 수동 생성 또는 permanent slot 대상입니다. slot 관리 체계는 5.4 replication slot 관리에서 다뤘습니다.

반대로 남아서 나는 장애가 있습니다. permanent slot은 failover를 넘어 유지되는 대신, 소비자(백업 도구, CDC, standby cluster)가 죽어도 사라지지 않습니다. 소비가 멈춘 slot은 restart_lsn이 정체된 채 primary의 pg_wal을 계속 점유하고, 방치하면 디스크 풀(시나리오 D)로 이어집니다. 이 부분은 Patroni가 아니라 PostgreSQL 일반의 대책이 필요한 영역으로, max_slot_wal_keep_size로 보존 상한을 두고 slot 별 누적량을 모니터링하는 것이 통상의 접근입니다.

SELECT slot_name, slot_type, active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
경고

max_slot_wal_keep_size는 디스크를 지키는 대신 상한을 넘긴 slot을 무효화합니다. 그 slot의 소비자가 standby cluster나 CDC 파이프라인이라면 해당 소비자 쪽에서 재구축이 필요해집니다. 상한 설정과 slot 모니터링은 한 쌍으로 운영해야 합니다.

시나리오 C: WAL archive 실패​

archive_command 실패는 Patroni 관점에서는 장애가 아닙니다. Patroni가 감시하는 것은 leader lock과 replication 상태이고, archiving은 PostgreSQL 설정의 영역이라 공식 문서에도 archive 실패에 대한 Patroni의 대응은 기술되어 있지 않습니다. failover도 경보도 일어나지 않은 채 조용히 진행되는 장애라는 점이 위험합니다.

영향은 세 갈래입니다. 실패가 지속되면 archive 대기 WAL이 pg_wal에 쌓여 디스크 고갈(시나리오 D)로 이어지고, restore_command로 WAL을 받아오는 노드(standby cluster, nostream tag 노드)의 복구가 멈추며, 백업 기반 PITR이 그 시점 이후로 불가능해집니다.

식별은 PostgreSQL 쪽 지표가 기본입니다. pg_stat_archiver의 실패 카운트와 마지막 실패 시각을 봅니다. Patroni 쪽에서는 patronictl list의 State 컬럼이 단서가 되는데, streaming으로 돌아야 할 노드가 in archive recovery 상태에 오래 머물면 archive 경로에 의존해 돌고 있다는 뜻이므로 복제와 archive 양쪽을 함께 점검합니다. 같은 정보가 메트릭으로는 patroni_postgres_in_archive_recovery로 노출됩니다.

시나리오 D: 디스크 풀​

디스크 풀 상황의 동작을 Patroni 공식 문서가 별도로 기술하지는 않습니다. 일반적으로 PostgreSQL은 WAL을 쓸 공간이 없으면 PANIC으로 정지하고, Patroni는 정지한 PostgreSQL의 재시작을 시도합니다. 문제는 공간이 없다는 원인이 그대로이니 재시작도 실패를 반복하고, 그 사이 leader lock이 만료되면 failover가 일어난다는 점입니다. WAL 누적이 원인이라면 replica 쪽 디스크도 같은 경로로 차오르고 있을 가능성이 높아, failover가 지연에 그칠 소지가 있습니다.

이럴 때 통상의 접근은 pause로 자동 관리를 먼저 멈추는 것입니다. pause는 major upgrade나 corruption recovery 같은 유지보수를 위해 만들어진 모드이고, 재시작 루프와 원치 않는 failover를 막은 상태에서 공간을 확보하는 이 상황도 그 용례에 준합니다. 공간 확보 후 원인을 짚을 때는 앞의 두 시나리오와 겹치는 항목부터 봅니다.

patronictl pause my-cluster --wait
# 공간 확보와 원인 제거 후
patronictl resume my-cluster --wait
  • pg_replication_slots에서 restart_lsn이 정체된 slot (시나리오 B)
  • pg_stat_archiver의 archive 실패 누적 (시나리오 C)
  • log.dir 등 로그와 임시 파일의 로테이션 설정

WAL 누적에서 디스크 고갈로 이어지는 장애 연쇄

시나리오 E: 이중 primary​

pause 중에는 Patroni가 이중 primary를 해소하지 않습니다. 공식 문서 기준으로, paused 상태에서 병렬로 뜬 primary를 감지하면 경고 로그를 남길 뿐 demote는 하지 않습니다. pause 중에 수동으로 promote했거나, 관리 밖에서 PostgreSQL을 직접 조작한 경우 이 상태가 만들어집니다. 평상시의 이중 primary 방지 체계는 2.4 split brain 방지와 watchdog에서 다뤘습니다.

경고

이중 primary를 남긴 채 patronictl resume을 실행하면 안 됩니다. resume 전에 primary가 하나뿐인 상태로 수동 정리를 마쳐야 합니다. 두 primary가 각자 쓰기를 받았다면 timeline이 갈라진 것이므로, 한쪽을 내리는 것으로 끝나지 않고 갈라진 뒤의 데이터를 어떻게 합칠지도 판단해야 합니다.

정리 절차를 문서가 규정하지는 않지만, 판단 재료는 분명합니다. 양쪽의 timeline과 LSN, 그리고 애플리케이션 트래픽이 실제로 어느 쪽에 붙어 있었는지를 확인해 정본을 정합니다. 정본이 아닌 쪽은 PostgreSQL을 정지하고, leader lock이 정본 쪽에 있는지 확인한 뒤 resume합니다. 갈라진 노드는 patronictl reinit로 재구축해 복귀시킵니다.

정리​

  • DCS 장애의 기본 반응은 demote 후 read-only입니다. 쓰기 중단을 감수하고 이중 primary를 막는 설계이며, 완화 장치는 failsafe_mode입니다.
  • slot은 사라져도(장기 다운 멤버의 reinit) 남아도(pg_wal 잠식) 장애가 됩니다. member_slots_ttl, permanent slot, max_slot_wal_keep_size와 slot 모니터링을 짝으로 씁니다.
  • archive 실패와 디스크 풀은 Patroni가 대신 감지해 주지 않는 영역입니다. PostgreSQL 지표로 조기에 잡고, 수습 중 자동 동작이 방해되면 pause를 씁니다.