IL-0219 GoCardless · 2017-10-10database
Postgres 프라이머리의 디스크 어레이가 망가진 뒤 Pacemaker 가 새 프라이머리를 승격하지 못해 GoCardless API 와 대시보드가 1시간 50분 멈췄다
GoCardless 는 가장 중요한 데이터를 프라이머리, 동기 복제본, 비동기 복제본 세 노드의 Postgres 클러스터에 두고, 각 노드의 Pacemaker 가 장애 때 새 프라이머리를 승격하고 클라이언트가 접속하는 가상 IP 를 옮긴다. 평소라면 프라이머리가 망가져도 1~2분이면 동기 복제본이 승격된다. 2017년 10월 10일 15:09 BST 에 API 와 대시보드가 완전히 멈췄다. 프라이머리의 RAID 컨트롤러가 디스크 3개를 동시에 잃었지만 클러스터는 동기 복제본을 승격하지 않았다.
대응 팀은 15:17 BST 에 망가진 프라이머리를 끄고, 15:18 BST 에 crm resource cleanup 으로 오류 횟수를 지웠지만 승격은 일어나지 않았다. 한 시간 동안 여러 방법을 해 봤고, 유지보수 모드에서 recovery.conf 의 복제본 설정을 지워도 Pacemaker 가 원래 설정으로 되돌렸다. 16:18 BST 에 마지막으로 유지보수 모드로 두고 동기 복제본을 프라이머리로 설정해 Postgres 를 직접 띄웠고, 가상 IP 대신 새 프라이머리의 실제 IP 로 백엔드 애플리케이션을 다시 설정했다. 16:46 BST 에 수동 승격한 프라이머리가 동작했고, 16:59 BST 에 API 와 대시보드가 돌아와 장애는 1시간 50분 이어졌다. 그 뒤 세 번째 노드를 비동기 복제본으로 띄우고, 손으로 관리할 가상 IP 를 하나 더 두고, 데이터 무결성을 점검해 손상이 없음을 확인했다.
팀은 Docker 컨테이너로 띄운 클러스터에서 장애를 재현했다. 동기 복제본이 유효하지 않은 WAL 파일을 받은 것은 망가진 프라이머리가 마지막으로 백업 서버에 보낸 파일 때문이었고, 승격 실패와는 관계가 없었다. 승격을 막은 조건은 셋이었다. 첫째는 0이 아닌 default-resource-stickiness 설정이었다. 둘째는 Backup VIP 에 건 -INF colocation 규칙이었다. 이 규칙은 백업 프로세스를 복제본에서 돌리려고 Backup VIP 를 Postgres 프라이머리와 같은 서버에 두지 않게 했다. 셋째는 프라이머리와 동기 복제본의 프로세스가 거의 동시에 죽었다는 점이었다. 장애 때 Backup VIP 는 승격돼야 할 동기 복제본에 있었다. 규칙을 Backup VIP 와 복제본 사이의 INF 선호로 뒤집자 같은 조건에서도 승격이 됐다. 팀은 기존 클러스터에 손을 너무 많이 댔다고 보고 Pacemaker 가 관리하는 새 클러스터를 만들었다. PgBouncer 로 쿼리를 잠시 멈추는 무중단 페일오버 절차를 고쳐 써서 새 클러스터로 옮겼고, 옛 클러스터를 내렸다.
- 심각도
- –
- 영향 범위
- GoCardless API and Dashboard: every request failed with an error for 1 hour and 50 minutes; no data corruption found
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- 1h 50m
증상
2017년 10월 10일 15:09 BST 에 모니터링이 API 와 대시보드가 완전히 멈춘 것을 찾았다. 그동안 들어온 요청은 모두 실패하고 오류를 받았다.
15:11 BST 에 프라이머리의 디스크 어레이가 망가진 증거가 보였다. RAID 컨트롤러는 어레이에서 디스크 3개를 동시에 잃었다고 기록했고, 그 뒤의 읽기와 쓰기는 모두 실패했다. Linux 커널은 그 컨트롤러 위의 파일시스템을 읽기 전용으로 바꿨지만 읽기조차 되지 않았다.
클러스터는 동기 복제본으로 페일오버하지 않았다. Pacemaker 는 프라이머리의 Postgres 가 비정상이라는 것을 제대로 알아채고 새 프라이머리를 거듭 승격하려 했지만, 매번 프라이머리를 어디서 돌릴지 정하지 못했다.
동기 복제본에서는 프라이머리의 디스크 어레이가 망가질 무렵 Postgres 하위 프로세스 하나가 죽어 Postgres 가 재시작했다. 재시작 뒤 동기 복제본은 restore_command 로 WAL 파일을 복원하려 했고, 시도마다 파일이 유효하지 않다는 메시지를 내며 실패했다. 이 메시지는 나중에 승격 실패와 관계가 없는 것으로 밝혀졌다.
대응 팀은 15:17 BST 에 망가진 프라이머리 노드의 전원을 끄면 클러스터가 새 프라이머리를 승격할 것으로 봤지만 그렇지 않았다. 15:18 BST 에 crm resource cleanup 으로 Pacemaker 의 오류 횟수를 지워도 아무 효과가 없었다.
대응 팀이 유지보수 모드에서 동기 복제본의 recovery.conf 에서 복제본 설정을 지우고 유지보수 모드를 풀 때마다, Pacemaker 는 원래 recovery.conf 로 복제본을 다시 띄웠다. 클러스터에는 프라이머리가 없는 채로 남았다.
원인
원인 하드웨어 고장
- 촉발
- 10월 10일 오후 Postgres 프라이머리 노드의 RAID 컨트롤러가 디스크 3개를 동시에 잃어 디스크 어레이가 망가졌다. 거의 같은 때 동기 복제본에서 Postgres 하위 프로세스 하나가 죽었다.
감지
15:09 BST 에 모니터링이 API 와 대시보드의 완전한 중단을 찾았고 엔지니어들이 조사를 시작했다.
조치
- 15:17 BST 에 디스크 어레이가 망가진 채 켜져 있는 노드를 클러스터에 두지 않으려고 망가진 Postgres 프라이머리 노드의 전원을 껐다. 클러스터는 여전히 새 프라이머리를 승격하지 않았다.
- 15:18 BST 에 Pacemaker 가 Postgres 인스턴스 상태를 다시 파악하게 하려고 crm resource cleanup 으로 오류 횟수를 지웠지만 효과가 없었다.
- Pacemaker 클러스터를 유지보수 모드로 두고 동기 복제본의 recovery.conf 에서 복제본 설정을 지운 뒤 유지보수 모드를 풀어 직접 승격하려 했지만, 매번 원래 recovery.conf 로 돌아갔다.
- 16:18 BST 에 클러스터를 마지막으로 유지보수 모드로 두고 동기 복제본을 프라이머리로 설정해 Postgres 를 직접 띄웠다.
- 클러스터가 유지보수 모드라 가상 IP 를 옮길 수 없었으므로 백엔드 애플리케이션이 새 프라이머리의 실제 IP 주소로 접속하게 다시 설정했다.
- 평소 수준의 이중화를 되찾으려고 세 번째 노드를 비동기 복제본으로 띄웠다.
- Pacemaker 가 유지보수 모드라 자동 페일오버가 없었으므로 인프라 팀이 손으로 관리할 가상 IP 를 하나 더 두어, 프라이머리가 망가지면 동기 복제본을 승격하고 가상 IP 를 직접 옮길 수 있게 했다.
- 디스크 어레이 장애였으므로 생각할 수 있는 모든 시험으로 데이터 무결성을 확인했고 데이터 손상의 증거는 없었다.
- Pacemaker 가 다시 관리하는 새 데이터베이스 클러스터를 만들고, 유지보수 시간을 공지한 뒤 문제없이 옮겼다. 옛 클러스터는 내렸다.
후속 조치
- Backup VIP 의 colocation 규칙을 Postgres 프라이머리와의 -INF 선호 대신 복제본과의 INF 선호로 바꿔, 같은 장애 조건에서도 동기 복제본이 승격되게 했다.
- 특정 Postgres 인스턴스로 트래픽을 보내는 가상 IP 를 없애고, 애플리케이션 서버에서 클러스터 상태에 따라 알맞은 노드로 트래픽을 보내는 프록시를 돌려 Pacemaker 가 관리하는 자원 수를 줄인다.
- Postgres 클러스터를 더 거칠게 시험하고, Chaos Monkey 처럼 계속 장애를 주입하는 자동화를 검토한다.
- 장애 대응 훈련인 게임 데이에 임의의 제약을 넣는 방안을 검토한다. 예를 들어 자동화가 실패하고 원인도 찾을 수 없는 상황에서 다른 방법으로 서비스를 되살리게 한다.
교훈
GoCardless 는 단순해 보이는 Pacemaker 설정이 매우 이상한 동작을 부를 수 있다는 것을 배웠다. 두 자원이 함께 돌면 안 된다는 규칙과 함께 돌아야 한다는 규칙은 반대말처럼 보이지만, 특정 장애 조건에서는 시스템이 완전히 다르게 움직였다.
프로세스 두 개가 거의 동시에 죽을 때만 드러나는 설정 오류는 기본 시험이나 평소 운영으로는 찾을 수 없고, 장애 주입으로만 드러나는 버그가 있다. 자동화가 2년 동안 장애를 잘 처리하자 그 아래 인프라를 손으로 다루는 기술이 녹슬었다. 런북을 써 두어도 몇 년 뒤에 꺼내 보면 거의 확실히 낡아 있다.
출처
관련 사례
IL-0306Neon us-east-1 에서 pageserver 를 돌리던 EC2 인스턴스가 예고 없이 죽어 프로젝트 0.4% 의 Postgres 가 최대 2시간 동안 멈췄다Neon · 2024같은 원인 · 공통 데이터베이스, 데이터베이스 장애 조치 관리자
IL-0289updown.io 의 주 MongoDB 서버가 쿼리에 평소보다 약 200배 느리게 응답해 사이트와 모니터링 데몬이 거의 멈췄는데 자동 페일오버가 일어나지 않았다updown.io · 2019같은 원인 · 공통 데이터베이스, 데이터베이스 장애 조치 관리자
같은 원인 · 공통 데이터베이스
IL-0370Frankfurt 에서 노드 하드웨어 장애 뒤 Render Postgres 데이터베이스가 다른 노드에서 시작하지 못해 최대 1시간 5분 멈췄다Render · 2022같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0219
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0219/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.