Our Billing Pipeline Was Suddenly Slow — A Hidden Bottleneck in ClickHouse (Cloudflare)

Author: Cloudflare | Source: Cloudflare Blog | Published: 2026-05-14


한 줄 요약

Cloudflare가 ClickHouse 파티셔닝 키를 (day)에서 (namespace, day)로 바꾼 뒤 billing 잡이 느려진 원인이 part 개수 증가로 인한 query planner의 mutex lock contention임을 flame graph로 진단하고, 3개 업스트림 패치로 해결.

핵심 주장/내용

  • per-namespace retention을 위해 파티셔닝 키를 (day)(namespace, day)로 변경. “쿼리당 읽는 part 수는 그대로니 성능 영향 없다”는 가정 — 틀렸음
  • I/O·메모리·스캔 행·읽은 part 모두 정상인데 billing 잡이 점점 느려짐. query duration vs total part count 플롯에서 명백한 상관 발견
  • flame graph 진단: 처음 CPU trace는 filterPartsByPartition에 45% → 작은 패치(5% 개선). “Real” trace(대기 스레드 포함)로 전환하니 진짜 원인 발견 — query duration 절반이 MergeTreeData mutex 대기(plan마다 exclusive lock → 전체 part 리스트 복사 → unlock → 필터). 수만 part × 수백 동시 쿼리가 single-file line
  • 3개 패치: ① shared lock(planner는 읽기만 하므로 std::shared_lock → contention 소멸) ② vector 복사 제거(shared copy 캐시, 수정 시만 재생성 → planner는 필터된 part만 복사) ③ binary search(part 리스트가 namespace로 정렬됨을 활용 → part 개수와 query duration 상관 끊음)

주요 수치 / 사실

  • 100PB+ 데이터, 수십 개 클러스터; Ready-Analytics 단일 테이블 2PiB+, 초당 수백만 행 ingest
  • 30,000 part/replica → 1년 후 160k part/replica로 증가했으나 패치로 query duration 안정
  • 패치 ①②는 ClickHouse PR 85535로 머지(v25.11+); 패치 ③ 배포 후 query duration 50% 감소
  • 교훈: “잘 계획된 변경도 잘못된 가정의 희생양이 될 수 있다”

관련 위키


Source: 원문 보기