2023년 3월 Datadog의 드문 대규모 장애에서 드러난 한계를 통해 무엇을 배웠고, 우아한 성능 저하(graceful degradation)를 목표로 데이터 보존, 라이브 데이터 우선순위화, 아키텍처 병목 제거, 복구 스케일링 및 제어면 강화, 대규모 카오스 테스트 등을 어떻게 적용했는지 공유합니다.

Laura de Vesine

Rob Thomas

Maciej Kowalewski
2023년 3월, Datadog은 드물지만 광범위한 사건을 겪었습니다. 그 결과 인프라의 큰 부분이 부분적으로만 동작했지만, 고객 관점에서는 플랫폼이 완전히 다운된 것처럼 보였습니다. 이러한 사각파형 실패 패턴—정상 동작하다가 즉시 다운—은 시스템의 성능 저하 방식과 고객을 지원하는 능력의 중대한 한계를 드러냈습니다. 그 이후 우리는 제품과 인프라 전반에서 실패에 대한 접근을 재고했습니다.
이 글에서는 해당 사건에서 무엇을 배웠는지, 어떻게 대응했는지, 그리고 대규모 환경에서 우아한 성능 저하를 구현하려면 무엇이 필요한지 설명합니다.
2023년 3월 사건을 다시 살펴보면, 직접적인 원인은 감독되지 않은 글로벌 업데이트였고, 재시작 상호작용으로 인해 프로덕션의 Kubernetes 노드 약 **50–60%**에 대한 연결이 제거되었습니다. 그 정도 수준의 플랫폼 손실은 어떤 시스템에도 중대한 영향을 주지만, Datadog 플랫폼에는 사용자 지향 기능의 거의 전면적인 상실로 이어졌습니다. 웹 인터페이스는 대부분 빠르게 자체 복구했지만, 로그, 메트릭, 알림, 트레이스, 그리고 고객 운영에 필수적인 다양한 다른 제품과 기능은 완전히 사용할 수 없었습니다. 페이지는 로드되었지만 데이터가 표시되지 않았습니다.
전통적인 인시던트 분석은 보통 시스템을 불안정하게 만든 촉발 사건(precipitating event)—흔히 근본 원인(root cause)—을 추적하고 그 원인을 시정하는 데 초점을 둡니다. 이번 경우, 우리는 보안 패치를 위한 글로벌 업데이트 메커니즘을 쉽게 촉발 사건으로 지목할 수 있었습니다. 또한 이 메커니즘이 레거시 시스템이라는 점도 확인했습니다. 최초 구현 이후 우리는 전체 플릿에 대해 “정상적이며” 정기적으로 수행되는 업데이트 메커니즘을 통해 중요 CVE를 포함한 모든 노드에 변경을 적용할 수 있는 라이프사이클 자동화를 구축해왔습니다. 우리는 인시던트의 트리거로 확인되는 즉시 플릿 전체 CVE 풀을 비활성화했으며, 보안이나 신뢰성의 손실은 없었습니다.
이러한 인과 분석의 어려움은 시스템을 불안정하게 만들어 중대한 장애로 이어질 수 있는 사건이 무수히 많다는 점입니다. 이번에는 자동 업데이트가 트리거였지만, 업계 전반을 보면 인증서 만료, 서머타임 버그, 윤일 처리, 시스템 전반의 연쇄적 오버로드, 구성 푸시 등 모든 것이 중대한 인시던트의 원인이 되어 왔습니다. 진정으로 더 탄력적이고 신뢰할 수 있는 Datadog을 만들기 위해서는 촉발 사건 자체를 고치는 것만으로는 충분하지 않다는 사실이 분명해졌습니다. 또한 인시던트를 야기하는 현실의 모든 교란을 사전에 방지하는 것도 불가능합니다.
촉발 사건에만 집중하는 대신, 우리는 보다 실용적인 엔지니어링 질문으로 전환했습니다. 왜 이번 인시던트는 이렇게까지 심각했는가?
시스템에 가해진 영향은 컸지만, 전면적인 것은 아니었습니다. 피크 시점에도 Kubernetes 노드의 약 **40–50%**는 여전히 실행 중이었고 연결되어 있었습니다. 그러나 사용자 관점에서 실패는 이진적이었습니다. 플랫폼이 완전히 다운된 것처럼 보였습니다.
우리는 역사적으로 데이터 정확성을 보장하는 방향으로 서비스 설계를 최적화했는데, 종종 거의 맞는 데이터를 보여주는 것보다 완전 중지를 우선시하는 방식이었습니다. 정상 운영 조건에서는 올바른 선택입니다. 예를 들어, aws.ec2.host_ok 같은 메트릭을 사용한다고 가정해 봅시다. 이 메트릭은 호스트 ID로 태그되며 호스트가 가동 중이면 값 1을 기록합니다. 그리고 실행 중인 호스트 수의 합이 임계값 아래로 떨어지면 모니터가 발동합니다. 이 메트릭 처리에 작은 지연이 생기면 실제로는 50%만 가동 중인 것처럼 보일 수 있어 잘못된 알림이 발생할 수 있습니다. 이를 피하기 위해 우리 시스템은 메트릭의 태그가 완전히 처리될 때까지 쿼리 값을 보고하지 않았으며, 부분 가시성보다 정확성을 보장했습니다.
이러한 정확성 편향은 시스템이 완전히 가동 중인 정상 상황에서는 잘 작동합니다. 하지만 대규모 장애 동안에는 일부 데이터가 누락되었기 때문에 어떤 데이터도 보고할 수 없는 사각파형 실패 패턴을 일으킵니다. 여기에 여러 요인이 더해지며 문제가 커질 수 있습니다. 순서대로 처리하는 큐잉 시스템은 단일 장애 지점 뒤에서 결과가 막혀 복구 시 실시간 데이터가 즉시 표시되지 않게 만들 수 있고, 재시도 로직이 다운스트림 시스템을 과부하할 수 있으며, 특정 메트릭을 특정 노드에서만 처리하도록 강제하는 시스템 설계도 문제를 키웁니다.
전반적으로 우리는 시스템 설계에서 바꿔야 할 패턴을 확인했습니다. 우리는 실패를 완전히 방지하거나—모든 것을 멈추는 방식으로—대응하는 가정하에 구축해왔으며, 극단적인 조건에서도 고객에게 가치를 계속 제공하기 위한 우아한 성능 저하 방법을 찾지 않았던 것입니다.
신뢰성은 항상 Datadog의 우선순위였습니다. 고객의 비즈니스가 우리에게 의존한다는 사실을 잘 알고 있으며, 그 책임을 매우 진지하게 받아들입니다. 하지만 신뢰성을 우선시하다 보니 구성 요소와 서비스가 고객 사용 사례를 지원하기 위해 완전히 기능해야 하는 절대 실패하지 않는(never-fail) 아키텍처를 구축하게 되었습니다. 우리는 처음부터 해당 구성 요소가 다운되지 않도록 중복을 통해 신뢰성을 확보하는 방식으로 설계했습니다.
이번 사건은 사고방식의 전환을 가져왔습니다. 시스템의 모든 수준에서 실패를 예방하려는 노력만으로는 안 된다는 점, 그리고 아무리 영웅적인 조치를 취하더라도 모든 실패를 막을 수 없다는 점을 받아들여야 했습니다. 실패를 예방하는 데 투자할 뿐만 아니라, 실패가 불가피하게 발생할 때 더 나은 방식으로 실패하는 데에도 투자해야 했습니다.
더 나은 실패란 시스템 일부가 실패하더라도 고객의 우선순위를 계속 충족시키는 것을 의미합니다. 대부분의 제품에서 이는 다음을 뜻합니다.
새로운 사고방식을 바탕으로, 우리는 불가피한 실패 상황에서도 우아한 성능 저하가 가능하도록 제품별로 변화를 시작했습니다. 실행 측면에서는 전사 프로그램으로 접근했고, 다수의 개별 제품 팀이 기여했습니다. 우아한 성능 저하는 널리 적용 가능한 원칙이지만, 특정 제품에 어떻게 구현할지는 해당 제품의 고객 사용 사례와 내부 아키텍처에 크게 좌우됩니다. 그럼에도 목표를 향해 나아가며 팀 간에 공통 주제가 도출되었습니다.
원래 장애 동안, 우리는 제한적이지만 0이 아닌 양의 고객 데이터를 복구 불가능한 방식으로 잃었습니다. 앞서 밝힌 우선순위를 고려하면, 이러한 데이터 손실의 원인을 해결하는 것이 중요했습니다. 분석 결과 주요 요인 중 하나는 일부 처리 파이프라인의 가장 초기에 디스크 기반 영속성이 부족했다는 점이었습니다.
이 격차는 두 가지 방식으로 데이터 손실을 유발했습니다. 첫째, 데이터가 메모리나 로컬 디스크에만 있는 경우 노드를 잃으면 아직 복제되지 않은 데이터가 유실되었습니다. 파이프라인은 일반적으로 처리 초기에 복제 데이터 저장소에 기록하지만, 일부는 수신 확인을 한 후에야 기록했습니다. 이 방식은 수집 단계에서 짧은 지연으로 응답할 수 있게 해주었습니다. 그 결과 일부 데이터는 로컬 노드에만 존재했고 에이전트 재시도 대상도 아니었습니다. 해당 노드를 잃으면 데이터도 함께 사라졌습니다.
둘째, 수집 이후의 복제 데이터 저장소가 대규모 노드 손실 이후에는 쓰기를 수용하지 못하는 경우가 종종 있었습니다. 이는 온라인 상태를 유지하던 수집 노드조차 인시던트가 지속되는 동안 메모리와 로컬 디스크 버퍼가 넘치면서 데이터를 잃을 수 있음을 의미했습니다.
이를 절대 실패하지 않는 접근으로 해결하려면 복제 데이터 저장소가 어떤 일이 있어도 데이터를 수용하도록 유지해야 합니다. 하지만 이는 불가능한 기대입니다. 대규모 실패 모드는 단지 노드 손실에만 국한되지 않기 때문입니다. 대신 우리는 이러한 데이터 저장소가 언젠가 실패한다는 가정하에, 어떤 일이 벌어지더라도 수집 단계에서 데이터를 영속화할 수 있도록 훨씬 더 강건한 디스크 기반 지속 스토리지를 구축했습니다. 이를 통해 어떤 종류의 실패가 발생하더라도 데이터를 재생(replay)할 수 있습니다.
이번 장애가 라이브 모니터링 데이터를 제공하지 못하게 만든 이유를 살펴보니 몇 가지 공통점이 있었습니다. 첫째, 많은 시스템이 수신 순서대로 데이터를 가리지 않고 처리하도록 구축되어 있었습니다. 이 접근은 시스템이 절대 실패하지 않는다는 가정하에 만들 때는 합리적입니다. 추론이 단순하고 강한 정확성 보장을 제공합니다.
우리는 이후 일부 서비스가 처리 적체를 앞질러 건너뛰어(skip forward) 복구 중 가능한 한 빨리 라이브 데이터로 따라잡을 수 있도록 변경했습니다. 또한 모든 텔레메트리 데이터의 중요도가 동일하지 않다는 점—예를 들어 일부는 높은 긴급도의 모니터를 구동—을 인식하고, 보다 중요한 데이터를 우선 처리하도록 내부 QoS 메커니즘을 구축(그리고 계속 구축)했습니다.
둘째, 자동 복구 동작—예: 적체 처리, 재시도 등—이 실제로는 복구를 늦추는 경우가 많다는 점을 발견했습니다. 이에 영향을 받은 시스템의 재시도 로직을 업데이트했습니다. 재시도는 이제 제한된 다운스트림 실패 범위에서 지속적 실패를 피하기 위해 다른 백엔드로 보내고, 과부하를 줄이기 위한 강력한 백오프를 사용하며, 단일 “문제” 패킷 때문에 처리가 멈추지 않도록 더 빨리 데드 레터 큐로 폴백하도록 했습니다. 또한 적체 복구에 대한 스로틀링을 도입하여, 오래된 데이터가 실시간 데이터 처리보다 우선되지 않도록 했습니다.
마지막으로, 희소한 컴퓨팅 리소스가 라이브 고객 데이터에 가장 중요한 서비스에 항상 할당되지는 않았다는 점을 확인했습니다. 이를 해결하기 위해 인프라와 컴퓨트 레벨에서의 우선순위화를 도입했습니다. Kubernetes 워크로드에 PriorityClass 메커니즘을 추가하고, 더 빠르고 반응성 높은 오토스케일러를 구현했으며, 전사적으로 잡 우선순위를 점검했습니다.
일부 경우 사각파형 실패의 원인은 서비스 아키텍처, 누적된 기술 부채, 과거의 캐싱 결정과 밀접하게 연관되어 있었습니다. 예를 들어, 우리의 메트릭 처리 파이프라인은 원래 대규모 공유 Cassandra 클러스터를 내구성 캐시로 사용해 태그를 중복 제거하도록 설계되었습니다.
시간이 지나면서 파이프라인의 서비스들이 태그 조회에 로컬 캐시를 더 많이 의존하게 되면서 이러한 공유 캐시의 필요성은 줄어들었습니다. 언젠가는 이 공유 캐시 클러스터를 폐기하려는 계획이 있었지만, 사건 당시에는 여전히 메트릭 처리를 위한 중요 경로에 있었습니다. 불행히도 대형 Cassandra 클러스터는 많은 노드를 잃은 후 재빌드가 느립니다. 이는 고객 서비스 복구에 상당한 지연을 야기했습니다.
사건 이후 우리는 이 캐시 경로를 제거하는 데 우선순위를 두어 실시간 메트릭 쿼리를 제공하기 위한 비로컬 핵심 의존성의 수를 줄였습니다. 또 다른 사례로, 백엔드 장애 동안 복제본이나 로컬 캐시로 폴백해 약간 오래된 데이터를 제공할 수 있음에도 아직 그렇게 설정되어 있지 않은 서비스들을 확인했습니다.
우리는 또한 복구가 복구해야 할 시스템 규모의 압도적인 크기, 복구 도구의 순환 의존성, 전반적으로 느린 서비스 시작 시간 때문에 지연되는 사례들을 관찰했습니다.
대규모 복구를 해결하기 위해, 우리는 추가적인 수평 샤딩을 도입하고 공유 운명과 데이터 지역성을 개선했습니다. 예를 들어, 인증서 발급을 위한 사이트 전체 단일 Vault 인스턴스에서 클러스터 로컬 Vault 인스턴스로 전환하고 추가 폴백 옵션을 마련했습니다. 이러한 인스턴스를 운영하고 동기화하는 일은 운영상 더 복잡하지만, 스케일 한계에 가까운 단일 병목 서비스를 제거함으로써—심지어 대규모 재해 시나리오에서도—복구를 신속히 시작할 수 있는 능력을 크게 향상시켰습니다.
내부 도구(도구를 복구하는 도구)의 경우, 빌드 인프라, 데이터베이스 복구 도구, Kafka 배포 자동화 같은 시스템 전반의 순환 의존성과 공유 운명 위험을 분석했습니다. 순환 의존성이 시스템 복구를 막을 수 있는 곳마다 수동 비상(break-glass) 메커니즘을 추가했습니다.
느린 서비스 시작을 살펴보니 두 가지 공통 원인이 있었습니다. 첫째는 컴퓨팅 리소스의 희소성으로, Kubernetes 우선순위 메커니즘을 통해 해결했습니다. 둘째는 서비스가 시작 시 메모리에 크고 처리 집약적인 캐시를 로드하는 것이었습니다. 우리는 이러한 캐시의 룩백 윈도를 줄이고, 시작 시 처리를 요구하지 않는 데이터 형식으로 조정했습니다.
우리가 수행한 변경은 시스템이 인간의 개입 없이도 자체적으로 장애에 우아하게 적응하도록 설계되었습니다. 어떤 경우에는 실패 모드가 사용자에게 가장 이치에 맞는 방식으로 자연스럽게 성능 저하를 유도하도록 시스템을 설계했습니다. 다른 경우에는 자동화된 서킷 브레이킹이나 페일오버 같은 방식으로 실패를 감지하고 대응하는 데 의존했습니다. 이러한 메커니즘은 사람의 개입 없이 트리거되어야 하지만, 오탐이나 누락이 있을 경우를 대비해 운영자가 그 동작을 재정의할 수 있도록 했습니다.
이 목적을 위해 구축된 제어 플레인 역시 실패 시 우아하게 성능 저하되도록 설계되었습니다. 우리는 레이어드된 비상 절차를 설계해, 운영자 경험이 다소 저하되거나 전문가가 반수동으로 변경을 전파해야 하더라도 시스템 동작을 계속 통제할 수 있도록 했습니다. 시스템의 모든 요소—내부 제어 플레인조차도—실패 상황에서도 가능한 한 잘 작동하도록 설계되어야 합니다.
이러한 솔루션을 구축하고 배포하는 과정에서, 우리는 카오스 테스트를 사용해 개선 사항을 검증했습니다. 테스트 계획에는 서비스가 성능 저하 조건에서 어떻게 동작해야 하는지에 대한 명시적 가설을 포함했고, 특정 유발 메커니즘이 아니라 (예: 모든 트래픽을 처리할 충분한 용량이 없음 같은) 성능 저하 조건 자체에 초점을 맞췄습니다.
이러한 시나리오는 지속적이고 완전 자동화된 카오스 테스트를 가능하게 해—중대한 실패 시나리오에서도—우리 시스템이 고객에게 서비스를 제공하는 데 훨씬 더 강해졌음을 확인해 줍니다.
불가피한 실패를 중심에 두고 시스템을 설계하면서도—가능한 한 실패를 예방—하는 일은 많은 설계에서 큰 전환이었습니다. 우아한 성능 저하를 가능하게 하는 변경은 종종 근본적인 설계 가정을 재고해야 하므로, 우리는 신중하고 점진적인 접근을 취했습니다. 미래의 문제를 막으려다 새로운 문제를 만들고 싶지는 않기 때문입니다!
우아한 성능 저하를 설계하는 구체적인 방법은 개별 서비스, 시스템, 제품에 따라 항상 달라지겠지만, 우리가 확인한 유용한 패턴과 기법은 다음과 같습니다.
항상 최종 사용자의 중요도에서 출발하라. 엔드 투 엔드 경험을 통해 실제 사용자 니즈를 우선시하는 것이 모든 우아한 성능 저하의 핵심입니다.
데이터를 일찍 영속화하라. 데이터 내구성이 중요하다면, 처리 파이프라인에서 가능한 한 이른 시점에 데이터를 영속화하세요.
여러 서비스를 가로지르는 전역 제어 시스템을 피하라. 종종 새로운 복잡한 실패 모드가 됩니다.
복잡한 제어 플레인을 만들지 않고도 데이터와 쿼리의 우선순위화가 빠른 복구의 핵심이다. 순서 외 처리, 적체의 디프라이어리타이제이션 등 전략을 포함합니다.
재시도와 캐싱을 면밀히 검토하라. 유용하지만 날이 서 있고, 제대로 검토하지 않으면 중대한 인시던트를 훨씬 악화시킬 수 있는 자충수 같은 함정이 있습니다.
기술 부채 의존성을 줄여라. 추가된 복잡성은 시한폭탄이 될 수 있습니다.
비상(break-glass) 도구에 투자하라. 똑똑한 엔지니어가 순간적으로 순환 의존성을 깨뜨릴 수는 있지만, 도구화가 더 나은 전략입니다.
시스템만 고치지 말고 도구도 고쳐라. 시스템을 운영하는 도구만큼, 도구를 고치는 도구도 중요합니다.
이러한 변경을 시스템에 반영하면서, 우리는 인시던트의 지속 시간과 방해 정도가 전반적으로 더 짧고 작아졌음을 이미 확인하고 있습니다. 특히 라이브 모니터링 데이터와 알림은 시스템 복구의 일부로 더 빠르게 회복됩니다. 인시던트가 드물고 예측 불가능하다는 특성 때문에 집계 지표만으로는 완전한 그림을 제공하기 어렵지만, 지난 2년간 고무적인 추세를 확인하고 있습니다. 특히:
고객 모니터에 영향을 미치는 중대한 인시던트 수가 30% 감소했습니다.
로그 제품에서 고객 영향 완화까지의 중앙값 시간은 10% 단축되었고, 95백분위에서는 거의 50% 단축되었습니다.
메트릭 제품의 인시던트는 피해 반경이 훨씬 제한적입니다—_대부분_의 메트릭 인시던트는 우리가 처리하는 메트릭의 일부, 보통 10% 미만에만 영향을 미칩니다.
모든 실패를 막을 수 있는 시스템은 없습니다. 그러나 실패가 불가피하게 발생할 때 더 나은 결과를 얻도록 설계할 수는 있습니다. 2023년 3월의 사건은 인시던트의 실패와 사용자 경험이 항상 일치하지 않으며, 빠른 복구는 단순히 시스템을 복원하는 것이 아니라—올바른 시스템을 올바른 순서로—복원하는 일이라는 점을 상기시켜 주었습니다.
우아한 성능 저하를 우선시함으로써, Datadog은 더욱 탄력적이고 고객 중심적인 인프라를 구축하고 있습니다.
이런 문제를 대규모로 해결하는 데 관심이 있으신가요? 채용 중입니다!