postmortem

2 개의 포스트

포스트모템: 20 (새 탭에서 열림)

2020년 4월 29일 발생한 Figma의 서비스 장애는 메인 데이터베이스인 PostgreSQL의 메모리 부족(OOM)으로 인해 발생했습니다. 근본 원인은 시스템 카탈로그 테이블인 `pg_attribute`의 비정상적인 팽창(Bloat)이었으며, 이는 장기 실행 트랜잭션이 데이터 정리를 방해하면서 발생한 연쇄적인 성능 저하의 결과였습니다. 결과적으로 모든 데이터베이스 연결이 지연되고 메모리가 고갈되면서 전체 서비스가 중단되는 사태에 이르렀습니다. ### 시스템 카탈로그(pg_attribute)의 비정상적 팽창 * PostgreSQL에서 테이블 열(column) 정보를 저장하는 `pg_attribute` 테이블이 약 64GB까지 비대해지는 현상이 발생했습니다. * 모든 SQL 쿼리는 실행 계획을 세울 때 이 시스템 카탈로그를 참조해야 하므로, 이 테이블의 성능 저하는 곧 모든 DB 작업의 지연으로 직결되었습니다. * 평소 1ms 미만이던 메타데이터 조회 시간이 수 초 단위로 늘어났고, 이는 데이터베이스 연결(Connection)의 급증과 메모리 점유율 상승으로 이어졌습니다. ### VACUUM 작동 불능과 장기 실행 트랜잭션 * PostgreSQL의 가비지 컬렉션 역할을 하는 VACUUM 프로세스가 `pg_attribute` 내의 불필요한 행(dead tuples)을 정리하지 못하는 상태였습니다. * 장애 발생 당시 며칠 동안 유지되고 있던 '장기 실행 트랜잭션(Long-lived transaction)'이 원인이었습니다. 이 트랜잭션이 특정 시점의 데이터 스냅샷을 붙잡고 있어, VACUUM이 그 이후에 생성된 쓰레기 데이터를 삭제할 수 없게 방해했습니다. * 이 상태에서 대량의 임시 테이블 생성 및 삭제 작업이 반복되자, 정리되지 못한 행들이 기하급수적으로 쌓이며 테이블 크기가 폭발적으로 증가했습니다. ### 장애 전파 및 복구 과정 * 팽창된 시스템 테이블로 인해 쿼리 분석 단계에서 과도한 메모리가 사용되었고, 결국 Primary DB 인스턴스가 OOM(Out of Memory) 상태에 빠져 모든 서비스를 중단시켰습니다. * Figma 팀은 즉각적인 가용성 확보를 위해 DB 인스턴스를 더 높은 메모리 사양으로 수직 확장(Scale-up)했습니다. * 이후 VACUUM의 정상 작동을 가로막던 오래된 트랜잭션들을 강제로 종료하고, 시스템 테이블에 대한 `REINDEX`를 수행하여 비대해진 인덱스를 정리함으로써 성능을 정상화했습니다. ### 재발 방지를 위한 권장 사항 데이터베이스의 안정성을 유지하기 위해서는 사용자 데이터뿐만 아니라 시스템 메타데이터의 상태를 정기적으로 점검해야 합니다. 특히 장기 실행 트랜잭션은 VACUUM 효율을 떨어뜨려 DB 전체에 치명적인 영향을 줄 수 있으므로, 특정 시간(예: 5분) 이상 지속되는 트랜잭션을 자동으로 종료하는 설정(`idle_in_transaction_session_timeout`)을 도입하고 시스템 테이블 크기에 대한 모니터링 알람을 구축하는 것이 권장됩니다.

실패는 피할 수 없습니다: (새 탭에서 열림)

2023년 3월, 데이터독(Datadog)은 인프라의 약 50~60%가 중단되는 대규모 장애를 겪으며 시스템의 일부가 마비될 때 플랫폼 전체가 완전히 다운된 것처럼 보이는 '정방형 파형(Square-wave)' 장애 패턴을 확인했습니다. 이를 계기로 데이터독은 모든 장애 상황을 완벽히 방지하는 것은 불가능하다는 점을 인정하고, 장애 발생 시에도 시스템이 점진적으로 기능을 유지하는 '우아한 성능 저하(Graceful Degradation)'를 최우선 가치로 삼게 되었습니다. 데이터 유실 방지, 실시간 데이터 우선 처리, 부분적인 결과 제공을 핵심 원칙으로 설정하여 인프라 전반의 회복 탄력성을 재설계하는 대대적인 변화를 추진하고 있습니다. **"결함 없음" 설계의 한계와 Square-wave 장애** - 과거 데이터독은 데이터의 '정확성'을 보장하기 위해 100% 완벽한 데이터가 수집될 때까지 쿼리 결과를 반환하지 않도록 시스템을 최적화했습니다. - 이러한 설계는 일부 노드가 다운되었을 때 시스템 전체가 응답을 멈추게 하여, 사용자에게는 플랫폼이 완전히 중단된 것처럼 보이는 이진적(Binary) 장애를 초래했습니다. - 고전적인 근본 원인 분석(RCA)을 통해 특정 트리거를 제거할 수는 있지만, 소프트웨어 업데이트, 인증서 만료 등 무한한 장애 원인을 모두 예방하는 것은 불가능하다는 결론에 도달했습니다. **우아한 성능 저하를 위한 새로운 우선순위** - 시스템 구성 요소가 완벽하게 작동해야만 가치를 제공하는 '결함 방지(Never-fail)' 아키텍처에서 '더 잘 실패(Fail better)'하는 구조로 전환했습니다. - 데이터 유실 방지: 처리가 늦어지더라도 고객의 데이터가 영구적으로 사라지지 않도록 보장합니다. - 실시간성 우선: 가용 자원이 부족할 때 오래된 데이터보다 실시간 데이터를 우선적으로 처리하여 현재 상태를 파악할 수 있게 합니다. - 부분 결과 제공: 모든 데이터가 준비되지 않았더라도 정확도가 확인된 범위 내에서 부분적인 데이터를 즉시 시각화합니다. **데이터 유실 방지를 위한 영구적 수집 저장소(Persistent Intake Storage)** - 장애 당시 메모리나 로컬 디스크에만 머물던 미복제 데이터가 노드 유실과 함께 사라졌던 문제를 해결하기 위해 파이프라인 초기 단계에 디스크 기반 영구 저장소를 도입했습니다. - 수집(Intake) 직후 데이터를 복제된 저장소에 즉시 기록함으로써, 후속 처리 시스템이 정체되거나 노드가 유실되더라도 데이터 손실 없이 재처리가 가능하도록 설계했습니다. - 이를 통해 네트워크 지연이나 하위 시스템의 과부하 상황에서도 데이터 수집 단계에서의 안정성을 확보했습니다. 모든 장애를 차단하려는 시도보다는, 장애 상황에서도 시스템이 어떻게 부분적으로나마 작동할 수 있을지를 설계 단계부터 고민해야 합니다. 대규모 분산 시스템을 운영한다면 데이터의 완전성(Completeness)과 가용성(Availability) 사이의 균형을 재검토하고, 최악의 순간에도 사용자에게 최소한의 가시성을 제공할 수 있는 복구 탄력성을 구축하는 것이 권장됩니다.