Cloudflare, ClickHouse 쿼리 플래닝 병목 발견...뮤텍스 경합으로 청구 시스템 마비 위기
Cloudflare가 페타바이트 규모의 분석 플랫폼에서 청구 시스템 성능 저하를 겪으면서 ClickHouse의 숨겨진 병목을 발견했다. 파티셔닝 스키마 재설계 후 단일 뮤텍스를 둘러싼 쿼리 플래너의 심각한 lock contention이 원인이었다. 3가지 최적화 패치로 50% 성능 개선을 달성해 ClickHouse 25.11 버전에 기여했다.

Cloudflare, ClickHouse 쿼리 플래닝 병목 발견...뮤텍스 경합으로 청구 시스템 마비 위기
Cloudflare가 페타바이트 규모의 빅데이터 분석 플랫폼에서 겪은 성능 위기 사건을 공개했다. 2025년 3월 청구 시스템의 일일 집계 작업이 예정 시간을 놓치기 일보 직전, 원인은 오픈소스 분석 데이터베이스 ClickHouse의 쿼리 플래닝 단계에서 발생한 뮤텍스(mutex) 경합이었다.
Cloudflare는 기업 100개 이상을 위해 100페타바이트 이상의 데이터를 ClickHouse 수십 개 클러스터에 저장한다. 2022년 초, 이들은 "Ready-Analytics"라는 시스템을 구축해 각 팀이 단일 대규모 테이블로 데이터를 스트리밍할 수 있도록 통합했다. 네임스페이스(namespace), 인덱스ID, 타임스탬프를 기본 키로 하는 이 구조는 초당 수백만 행씩 수집했고, 2024년 12월 이미 2페타바이트를 넘었다.
고정 보관 정책의 함정
하지만 치명적인 제약이 있었다. 모든 팀이 31일 보관 정책을 강제받았다. 법규상 장기 보관이 필요한 팀과 며칠만 필요한 팀 모두 같은 정책의 희생양이었다. 유연한 보관을 원하는 팀들은 결국 복잡한 별도 인프라로 이탈했다.
Cloudflare는 2025년 1월, 파티셔닝 키를 (day)에서 (namespace, day)로 변경하기로 결정했다. 이렇게 하면 기존 파티션 삭제 메커니즘을 유지하면서 네임스페이스별 보관 정책을 지원할 수 있었다. 엔지니어 팀은 테이블의 데이터 부분(part) 수가 늘겠지만, "모든 쿼리가 특정 네임스페이스로 필터링되므로 읽은 부분 수는 변하지 않을 것"이라고 가정했다.
점진적 성능 악화, 원인 불명
두 달 뒤 3월, 청구팀이 보고했다. 일일 집계 작업이 점점 느려지고 있다는 것이었다. 이 작업이 지연되면 고객 청구가 나가지 않는다. 엔지니어들은 표준 진단을 시작했다. I/O는 정상, 메모리도 정상, 개별 쿼리가 읽은 데이터와 부분 수도 전과 같았다.
며칠이 지나고, 한 엔지니어가 쿼리 소요 시간을 클러스터의 전체 부분 수에 대해 그래프로 그렸다. 선형 상관관계가 명확했다. 부분이 증가할수록 쿼리가 느려졌다.
Cloudflare는 ClickHouse의 내장 추적 로그(trace_log)를 이용해 화염 그래프(flame graph)를 생성했다. CPU 기반 그래프에서 첫 단서가 포착됐다. 샘플링된 CPU 시간의 45%가 filterPartsByPartition 함수에서 소모되고 있었다. 쿼리 플래닝, 즉 ClickHouse가 어느 부분을 읽을지 결정하는 단계였다.
초기 패치로 함수 순서를 최적화해 5% 개선했지만, 이건 증상에 불과했다. 실제 원인은 더 깊었다.
뮤텍스 경합의 정체
"CPU" 추적에서 "Real" 추적으로 전환하자 전체 상황이 드러났다. CPU 추적은 활성 스레드만 샘플링하지만, Real 추적은 대기 중인 스레드도 포함한다. 충격적인 결과가 나왔다. 쿼리 소요 시간의 절반 이상이 단 하나의 뮤텍스(MergeTreeData)를 기다리는 데 소모되고 있었다.
이 뮤텍스는 테이블의 부분 목록을 보호한다. 쿼리 플래너가 하려는 일은 간단했다.
- 이 뮤텍스에 배타적 잠금(exclusive lock) 획득
- 테이블의 모든 부분 목록을 복사
- 잠금 해제
- 복사본을 필터링해 관련 부분만 남김
수만 개의 부분과 수백 개의 동시 쿼리가 있으면, 모두가 일렬로 줄을 서는 셈이었다. 뮤텍스 경합(lock contention)의 전형적 증상이었다.
3단계 최적화, 50% 성능 개선
Cloudflare는 세 가지 패치를 순차 배포했다.
첫 번째: 공유 잠금 도입. 쿼리 플래너는 부분 목록을 읽기만 한다. 수정하지 않는다. 배타적 잠금이 불필요했다. std::shared_lock으로 변경하니 모든 플래너가 동시에 들어올 수 있었다. 경합이 즉시 사라졌다.
두 번째: 벡터 복사 회피. Real 추적 그래프가 새로운 병목을 보여줬다. 수만 개 요소의 벡터 복사가 초당 수백 번 일어났다. 엔지니어들은 공유 복사본 캐시를 만들었다. 읽기 작업은 이 캐시에서 읽고, 쓰기 작업(예: 새 삽입)만 캐시를 재생성한다. 플래너는 필요한 부분만 복사한다. 또 다른 유의미한 개선을 얻었다.
세 번째: 이진 검색 도입 (2026년 3월 배포). 부분 목록이 파티셔닝 키로 정렬되어 있다는 점을 활용했다. 대다수 쿼리가 네임스페이스로 필터링되므로, 네임스페이스를 기반으로 이진 검색을 수행해 확인할 부분 수를 획기적으로 줄였다. 결과는 50% 성능 개선이었고, 부분 수와 쿼리 소요 시간의 상관관계가 깨졌다.
커뮤니티 기여, 근본적 설계 의문
Cloudflare는 처음 두 패치를 ClickHouse 공식 저장소에 기여했다(PR #85535). ClickHouse 버전 25.11부터 모든 사용자가 이 최적화를 받을 수 있다. 세 번째 패치는 아직 임시 솔루션으로, 범용 쿼리 조건(예: namespace in (5,10))에는 일반화되지 않아 더 포괄적 접근을 모색 중이다.
현재 클러스터는 레플리카당 160,000개 부분을 관리하지만 쿼리는 안정적이다. 하지만 경험은 깊은 의문을 남겼다. "과연 이 파티셔닝 스키마가 장기적으로 올바른 선택인가?" Cloudflare는 영구적 해법 대신 임시 방편으로 "불편한 휴전"을 맺은 상태다.
이 사건은 IT 인프라가 극단적 규모에서 보여주는 복잡성을 여실히 보여준다. 잘 설계된 변경도 숨은 가정을 간과하면 예상 밖의 재앙이 될 수 있다. Cloudflare 같은 초대형 서비스는 이런 병목을 먼저 경험하고, 오픈소스 커뮤니티가 그 해결책을 공유받는다. 분석 데이터베이스 성능이 까다로운 조직들에겐 이 3단계 최적화 사례가 소중한 참고가 될 것이다.
편집 안내 | 이 기사는 AI 기술을 활용하여 글로벌 뉴스 소스를 분석·종합한 후, AIB프레스 편집팀의 검수를 거쳐 발행되었습니다. 정확한 정보 전달을 위해 노력하고 있으며, 원문 출처를 함께 제공합니다.


