1. 대규모 서비스들의 동시 다발적 다운!

2025년 11월 18일, 전 세계 인터넷 트래픽의 상당 부분을 처리하는 클라우드플레어(Cloudflare)에서 치명적인 장애가 발생했다. League of Legends, X(구 트위터), ChatGPT, Discord 등 글로벌 서비스들이 일제히 502 Bad Gateway를 뿜어내며 멈춰 섰다.

초기에는 대규모 디도스(DDoS) 공격이 의심되었으나, 분석 결과 내부 시스템의 설정 오류가 원인임이 밝혀졌다. 이번 장애는 “데이터베이스의 사소한 권한 변경이 어떻게 나비효과를 일으켜, 안전하다고 믿었던 Rust 소프트웨어를 무너뜨렸는지” 보여주는 교과서적인 사례다.


2. Root Cause

장애는 [데이터베이스] [설정 생성기] [엣지 프록시] 로 이어지는 자동화 파이프라인에서 발생했다.

2.1. Trigger: DB 권한 변경과 ‘유령 데이터’

클라우드플레어는 봇(Bot) 탐지를 위해 트래픽 로그를 분석하는 ClickHouse 데이터베이스를 운영한다. 이 DB에는 봇 탐지 규칙(Feature)들이 저장되어 있으며, 이를 주기적으로 조회하여 전 세계 서버에 배포할 설정 파일(Config)을 생성한다.

사건의 발단은 엔지니어의 데이터베이스 권한(Grant) 변경 작업이었다.

  • 작업 의도: 보안 강화를 위한 내부 접근 제어 목록(ACL) 및 권한 체계 개선.
  • 발생 현상: 특정 계정의 권한이 변경되면서, 해당 계정이 실행하는 쿼리가 볼 수 있는 **데이터의 범위(View Scope)**가 의도치 않게 확장되었다.
    • Before: default 데이터베이스(사용자용 뷰)만 조회 가능.
    • After: default 뿐만 아니라 내부 복제본인 r0 (Replica/Shard) 테이블까지 접근 가능.

⛔️ The Bad Query

문제가 된 것은 설정 파일을 생성하는 SQL 쿼리문이었다. 쿼리에는 데이터 출처를 명확히 제한하는 조건(WHERE database = 'default')이 누락되어 있었다. 권한이 확장되자 쿼리는 원본 데이터와 복제 데이터를 모두 읽어들였고, 결과적으로 모든 행(Row)이 정확히 2배로 중복되었다.

Feature IDRule (Before)Rule (After - 11/18)
ft_101Block User-Agent XBlock User-Agent X
ft_101(없음)Block User-Agent X (중복)

Insight

단순히 쿼리 결과가 늘어난 것이 아니라, 물리적으로 동일한 설정 값(Feature)이 중복되어 리스트에 포함된 것이 치명적이었다.

2.2. The Crash: Rust의 unwrap()과 안전장치의 부재

중복 데이터로 인해 비대해진 설정 파일은 전 세계 엣지(Edge) 서버로 배포되었다. 트래픽을 처리하는 프록시 소프트웨어는 메모리 안전성이 뛰어난 Rust 언어로 작성되어 있었으나, 아이러니하게도 코드 한 줄이 시스템 전체를 다운시켰다.

  • 하드코딩된 한계: 소프트웨어는 봇 탐지 기능을 최대 200개까지만 로드하도록 설계되어 있었다. 평소 60여 개였던 기능이 중복으로 인해 한계를 초과하거나 파일 형식이 꼬였다.

  • 치명적인 코드 패턴 (unwrap): 개발자는 내부 시스템에서 생성된 설정 파일을 과도하게 신뢰했다. 파싱(Parsing) 실패 시 안전하게 넘어가거나(Graceful Degradation) 이전 설정을 유지하는 대신, unwrap() 함수를 사용했다.

// 문제의 코드 로직 (재구성)
let config = parse_bot_config(file_data).unwrap();

Why unwrap() failed?

Rust의 unwrap()은 *“결과가 무조건 성공(Ok)일 것이라 확신한다. 만약 실패(Err)라면, 에러를 처리할 필요 없이 프로그램을 즉시 종료(Panic)하라”*는 의미다. 불량 설정 파일을 읽은 프록시 프로세스는 예외 처리 없이 **즉시 사망(Crash)**했고, 관리 시스템(Kubernetes 등)이 이를 다시 살리면(Restart) 또다시 불량 파일을 읽고 죽는 **무한 부팅 루프(Boot Loop)**에 빠졌다.

2.3. 장애 발생 메커니즘

graph TD
    subgraph "Central Control : 본사"
        A["👨‍💻 엔지니어"] -->|"#1 권한 변경 (GRANT)"| B[("ClickHouse DB")]
        B -->|"#2 쿼리 범위 확장"| C["설정 생성기"]
        C -->|"#3 중복 데이터 포함"| D["📄 설정 파일 (Bad Config)"]
    end

    subgraph "Edge Network : 전 세계"
        D -->|"#4 배포 (Propagation)"| E["서버 1"]
        D --> F["서버 2"]
        D --> G["서버 N..."]
        
        E -->|"#5 파일 파싱 시도"| H{"Result(Ok, Err)"}
        H -- Err --> I["#6 unwrap() 실행"]
        I -->|"#7 Panic! (종료)"| J["💀 서비스 중단"]
        J -->|"#8 자동 재부팅"| E
    end

    style I fill:#ff6b6b,stroke:#333,stroke-width:2px,color:white
    style J fill:#c0392b,stroke:#333,stroke-width:2px,color:white

3. 복구 과정: “열쇠를 차 안에 두고 문을 잠그다”

장애 발생 직후 클라우드플레어 팀은 즉시 롤백(Rollback)을 시도했으나, 상황은 예상보다 훨씬 복잡했다. 복구는 총 3단계의 복잡한 과정을 거쳐야 했다.

Phase 1: 통신 두절과 OOB (Out-of-Band)

중앙 관제 센터(Control Plane)에서 “이전 버전으로 되돌려라”는 명령을 내려야 했지만, 명령을 전달할 네트워크망(Data Plane) 자체가 죽어버렸다. 소방서에서 불을 끄러 가야 하는데 도로가 불타버린 상황이었다.

  • Action: 엔지니어들은 인터넷망이 아닌, 데이터센터 관리용 비상망인 OOB(Out-of-Band) 네트워크를 통해 전 세계 주요 거점 서버에 수동으로 접근했다.

  • Result: 문제가 된 ‘봇 관리(Bot Management)’ 기능을 코드 레벨에서 강제로 비활성화(Disable)하는 긴급 패치를 수행했다.

Phase 2: 썬더링 허드 (Thundering Herd)와의 싸움

기능을 끄자 서버들이 재부팅에 성공했다. 하지만 그 순간, 전 세계에서 접속을 대기하던 수십억 건의 트래픽이 댐이 무너지듯 쇄도했다. 이를 ‘썬더링 허드(성난 들소 떼)’ 현상이라 한다.

  • Crisis: 막 깨어난 서버들이 폭주하는 트래픽을 감당하지 못하고 다시 다운될(CPU 과부하) 위기에 처했다.

  • Action: 트래픽 스로틀링(Throttling) 전략을 사용하여, 수도꼭지를 조금씩 돌리듯 지역별로 순차적으로 트래픽을 허용했다.

Phase 3: 완전 정상화

  • 타임라인:

    • T+00:00: 장애 발생 및 인지

    • T+00:20: 디도스 공격 아님을 확인, 내부 원인 파악

    • T+00:45: OOB를 통한 봇 기능 비활성화 시작

    • T+01:15: 전 세계 트래픽 정상 처리 완료


4. Lessons Learned (교훈 및 결론)

이번 사고는 고가용성(High Availability) 시스템을 설계하는 엔지니어들에게 뼈아픈 교훈을 남겼다.

4.1. 기술적 교훈

  1. Defensive Programming (방어적 프로그래밍):

    • 내부 데이터라도 절대 신뢰해서는 안 된다.

    • Rust의 unwrap()이나 expect()는 프로덕션 코드, 특히 핵심 인프라 코드에서 사용을 엄격히 금지해야 한다. 대신 match 구문 등을 사용하여 오류 발생 시 기본값(Default)으로 동작하거나, 해당 기능만 끄고 서비스는 유지하는 Graceful Degradation을 구현해야 한다.
  2. Coupling (결합도) 관리:

    • 분석용 데이터베이스(ClickHouse)의 상태가 실시간 서비스(Production Config)에 직접적인 영향을 주는 강한 결합(Tight Coupling) 구조를 끊어내야 한다. 설정 파일 생성 시 중간에 검증(Validation) 레이어를 두어야 한다.

4.2. 운영적 교훈

  1. Canary Deployment (카나리아 배포):

    • 모든 설정 변경은 전 세계 동시 배포가 아닌, 일부 서버(1~2%)에 먼저 적용하여 문제가 없는지 확인하는 절차를 강제해야 한다.
  2. OOB 관리의 중요성:

    • 서비스 망이 완전히 붕괴되었을 때를 대비한 비상 통로(Backdoor)가 정상 작동한 덕분에 복구가 가능했다. 이는 평소 DR(재해 복구) 훈련이 얼마나 중요한지 보여준다.

5. 더 깊이 알아보기

  • System Design for Large Scale (대규모 트래픽 시스템 설계):
    • 수백만 개의 동시 접속을 처리하는 글로벌 로드 밸런싱(Global Load Balancing) 전략.
    • 장애가 전체 시스템으로 전파되는 것을 막기 위한 격벽(Bulkhead) 패턴속도 제한(Rate Limiting) 아키텍처 설계.
  • SRE (Site Reliability Engineering):
    • 구글이 제안한 시스템 운영 방법론. 완벽한 안정성 대신 SLO(서비스 수준 목표)Error Budget(에러 예산) 을 설정하여 혁신과 안정성의 균형을 맞추는 법.
    • 이번 클라우드플레어 사례처럼, 사고 후 사람을 문책하는 대신 시스템적 원인을 찾는 비난 없는 회고(Blameless Post-mortem) 문화.
  • Rust Error Handling Patterns:
    • Result<T, E> 타입과 ? 연산자, unwrap_or_else 등을 활용하여 ‘Panic’ 없이 우아하게 실패를 처리하는 기법.
  • Circuit Breaker Pattern:
    • 외부 서비스나 내부 기능에 장애가 감지되면 즉시 연결을 차단하여, 장애가 시스템 전체의 병목(Bottleneck)이 되는 것을 방지하는 설계 패턴.
  • Thundering Herd Problem & Jitter:
    • 대규모 분산 시스템이 재시작될 때 트래픽이 일시에 몰려 다시 다운되는 현상을 막기 위한 ‘지수 백오프(Exponential Backoff)‘와 ‘Random Jitter’ 알고리즘.