7.5 연쇄 장애와 2노드 한계
7.2에서 2노드는 secondary를 잃으면 동기 복제를 끄고 쓰기를 계속한다는 것을 확인했습니다. 그렇다면 그 상태에서 primary까지 잃으면 어떻게 될까요. 남은 secondary를 다시 살리면 자동으로 승격될까요. 이 장에서는 그 연쇄 장애를 재현한 결과를 살펴봅니다.
시나리오
run2의 2노드 구성(node1 primary, node2 secondary)에서 다음 순서로 진행했습니다.
- secondary node2를 kill
- node1이
wait_primary에 도달할 때까지 대기 - node1에만 100행 INSERT (이 행은 node2에 없음)
- primary node1을 kill
- node2를 다시 기동하고 60초 관찰
- node1을 다시 기동
측정 결과
node1 reached wait_primary in 20.9s
INSERT 0 100
rows written only on node1 in wait_primary: 100
node1을 kill하고 node2를 기동한 뒤 60초가 지난 시점의 상태입니다.
Name | Node | Host:Port | TLI: LSN | Connection | Reported State | Assigned State
------+-------+------------+----------------+--------------+---------------------+--------------------
node1 | 1 | node1:5432 | 9: 0/9055468 | read-write ! | wait_primary | wait_primary
node2 | 2 | node2:5432 | 8: 0/9055468 | read-only ! | secondary | catchingup
node2의 목표 상태는 catchingup이므로 승격 후보가 아닙니다. monitor event는 node1을 unhealthy로 표시했습니다. 그러나 이후에는 새 목표를 지정하지 않았습니다.
05:41:30|node2|secondary|catchingup|Setting goal state of node 2 "node2" (node2:5432) to catchingup after it became unhealthy.
05:41:30|node1|primary|wait_primary|Setting goal state of node 1 "node1" (node1:5432) to wait_primary because none of the standby nodes in the quorum are he
05:41:39|node1|wait_primary|wait_primary|Node node 1 "node1" (node1:5432) is marked as unhealthy by the monitor
node2 keeper는 primary에 복제 연결을 만들지 못했습니다. 이 때문에 PostgreSQL을 실행하지 못한 채 같은 전이를 반복했습니다. 60초 시점에 pg_is_in_recovery()를 조회했습니다. PostgreSQL이 실행 중이 아니어서 연결 자체가 거부되었습니다.
05:42:33 11 WARN Postgres is not running and we are in state secondary
05:42:33 11 INFO FSM transition from "secondary" to "catchingup": Failed to report back to the monitor, not eligible for promotion
05:42:33 11 WARN Failed to connect to "postgres://pgautofailover_replicator@node1:5432/?application_name=pgautofailover_standby_2&sslmode=require&replication=1", retrying until the server is ready
node1을 다시 기동하자 3.4초 만에 primary로 돌아왔습니다. node2도 secondary가 되면서 100행을 받았습니다.
node1 reached primary in 3.4s
node2 reached secondary in 0.2s
node2 now has wait_primary-only rows: 100
왜 승격하지 않는가
node2에는 node1이 wait_primary 상태에서 받은 100행이 없습니다. 이 상태에서 node2를 승격하면 그 100행은 사라집니다. pg_auto_failover는 이런 승격을 자동으로 수행하지 않습니다. v2.2 설정 문서는 이 원칙을 "데이터 가용성이 서비스 가용성보다 우선한다"고 표현합니다. primary를 잃은 순간 secondary가 승격 자격을 가지고 있어야 승격합니다. secondary가 이미 unhealthy 판정을 받아 catchingup으로 내려갔다면 자격이 없습니다.
100행이 보존된 이유는 이 실험에서 node1의 디스크가 온전했기 때문입니다. 서버를 통째로 잃었다면 100행은 어디에도 없습니다. 이 결과의 결론은 데이터가 보존된다는 것이 아닙니다. wait_primary 동안 확인 없이 커밋된 쓰기가 있을 수 있습니다. 따라서 자동 승격을 거부한다는 것이 핵심입니다.
이 상태에서 서비스를 복구하려면 사람이 결정해야 합니다. node2가 secondary/catchingup인 동안에는 pg_autoctl perform promotion과 perform failover가 모두 거부됩니다(v2.2 node_active_protocol.c). promote_wal_log_threshold도 강제 승격 옵션이 아닙니다. node1을 되살릴 수 없고 확실히 격리되었음을 확인했다면 pg_autoctl drop node --force로 node1 등록을 삭제합니다. 그러면 남은 node2가 single로 전환됩니다. 이때 wait_primary 이후의 쓰기는 포기하게 됩니다. 이 절차는 문서와 소스로만 확인했으며 실습하지 않았습니다. 수동 조치 방법은 6.2와 9.4를 참고하세요. 어느 쪽이든 먼저 백업과 WAL 아카이브로 손실 범위를 확인해야 합니다.
3노드와의 차이
3노드 기본 구성(number_sync_standbys = 1)에서는 standby 하나를 잃어도 primary가 wait_primary로 내려가지 않습니다. 남은 standby가 계속 동기 확인을 보냅니다. 따라서 커밋된 쓰기는 항상 두 노드에 있습니다. 이 상태에서 primary를 잃으면 남은 standby가 승격 자격을 유지합니다. standby 두 대를 모두 잃으면 쓰기가 멈춥니다. 확인 없이 커밋되는 구간이 생기지 않습니다. 따라서 이런 연쇄 장애의 모호함도 생기지 않습니다. 대신 쓰기 가용성을 그만큼 희생합니다. 이 비교는 5.4에서 표로 정리합니다.
이 문서에서는 3노드에서 두 standby를 연달아 잃는 실험은 하지 않았습니다. 위 설명은 v2.2 architecture-multi-standby.rst의 내용입니다.
main 브랜치(미릴리스)에는 pgautofailover.guard_data_loss가 추가되었습니다. 기본값 true에서는 선출 중 LSN을 보고하지 않은 quorum 노드가 있으면 어떤 standby도 승격하지 않습니다. 강제로 진행하려면 pg_autoctl perform failover --allow-data-loss를 씁니다. v2.2 바이너리에는 이 설정과 옵션이 없습니다.
정리
2노드에서 secondary를 먼저 잃고 primary까지 잃으면 pg_auto_failover는 남은 secondary를 승격하지 않습니다. 60초를 기다려도 node2는 catchingup에 머물렀습니다. primary가 돌아온 뒤에야 서비스가 복구되었습니다. 2노드의 Business Continuity는 한 번의 장애까지만 보장합니다. 두 번째 장애는 사람이 판단해야 합니다.