ClickHouse는 왜 이렇게 빠를까요?

ClickHouse는 왜 이렇게 빠를까요? (Why is ClickHouse so fast?)

데이터베이스 성능에는 데이터 지향성(orientation) 외에도 많은 다른 요소가 기여해요. 이 문서에서는 ClickHouse가 특히 다른 컬럼 지향 데이터베이스와 비교해 왜 이렇게 빠른지 자세히 설명합니다.

출처: 문서

본문

데이터베이스 성능에는 데이터 지향성 외에도 많은 다른 요소가 기여합니다.

다음에서는 특히 다른 컬럼 지향 데이터베이스와 비교해 무엇이 ClickHouse를 그렇게 빠르게 만드는지 더 자세히 설명하겠습니다.

아키텍처 관점에서 데이터베이스는 (최소한) 스토리지 계층과 쿼리 처리 계층으로 구성됩니다. 스토리지 계층이 테이블 데이터를 저장, 로드, 유지하는 역할을 담당하는 반면, 쿼리 처리 계층은 사용자 쿼리를 실행합니다. 다른 데이터베이스와 비교해 ClickHouse는 두 계층 모두에서 극도로 빠른 삽입과 Select 쿼리를 가능하게 하는 혁신을 제공합니다.

스토리지 계층: 동시 삽입은 서로 격리됩니다 (Storage layer: concurrent inserts are isolated from each other)

ClickHouse에서 각 테이블은 여러 "테이블 파츠(table parts)"로 구성됩니다. 사용자가 테이블에 데이터를 삽입할 때(INSERT 문) 파츠가 만들어집니다. 쿼리는 항상 쿼리가 시작될 때 존재하는 모든 테이블 파츠에 대해 실행됩니다.

너무 많은 파츠가 쌓이는 것을 피하기 위해 ClickHouse는 백그라운드에서 병합(merge) 작업을 실행하며, 여러 개의 작은 파츠를 지속적으로 하나의 더 큰 파츠로 결합합니다.

이 접근 방식은 여러 장점이 있습니다: 모든 데이터 처리를 백그라운드 파츠 병합으로 오프로드할 수 있어 데이터 쓰기를 가볍고 매우 효율적으로 유지합니다. 개별 삽입은 전역, 즉 테이블별 데이터 구조를 업데이트할 필요가 없다는 점에서 "로컬"입니다. 그 결과 여러 동시 삽입은 서로 간의 동기화나 기존 테이블 데이터와의 동기화가 필요하지 않으므로, 삽입이 거의 디스크 I/O 속도로 수행될 수 있습니다.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 On-Disk Format 섹션을 참고하세요.

스토리지 계층: 동시 삽입과 선택은 격리됩니다 (Storage layer: concurrent inserts and selects are isolated)

삽입은 SELECT 쿼리와 완전히 격리되며, 삽입된 데이터 파츠 병합은 동시 쿼리에 영향을 주지 않고 백그라운드에서 일어납니다.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 Storage Layer 섹션을 참고하세요.

스토리지 계층: 병합 시점 계산 (Storage layer: merge-time computation)

다른 데이터베이스와 달리 ClickHouse는 모든 추가 데이터 변환을 병합 백그라운드 프로세스 동안 수행해 데이터 쓰기를 가볍고 효율적으로 유지합니다. 그 예시는 다음과 같습니다:

  • Replacing 병합 — 입력 파츠에서 행의 가장 최근 버전만 유지하고 다른 모든 행 버전을 버립니다. Replacing 병합은 병합 시점의 정리 작업으로 생각할 수 있습니다.
  • Aggregating 병합 — 입력 파츠의 중간 집계 상태를 새 집계 상태로 결합합니다. 이해하기 어려워 보이지만, 실제로는 증분 집계를 구현한 것뿐입니다.
  • TTL (time-to-live) 병합 — 특정 시간 기반 규칙에 따라 행을 압축, 이동, 삭제합니다.

이러한 변환의 요점은 작업(계산)을 사용자 쿼리 실행 시점에서 병합 시점으로 옮기는 것입니다. 이는 두 가지 이유로 중요합니다:

한편으로, 사용자 쿼리는 "변환된" 데이터, 예를 들어 사전 집계된 데이터를 활용할 수 있다면 훨씬, 때로는 1000배 이상 빨라질 수 있습니다.

다른 한편으로, 병합 런타임의 대부분은 입력 파츠를 로드하고 출력 파츠를 저장하는 데 소비됩니다. 병합 중 데이터 변환을 위한 추가 노력은 보통 병합의 런타임에 큰 영향을 주지 않습니다. 이 모든 마법은 완전히 투명하며 쿼리 결과에는 영향을 주지 않습니다(성능 외에는).

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 Merge-time Data Transformation 섹션을 참고하세요.

스토리지 계층: 데이터 프루닝 (Storage layer: data pruning)

실제로 많은 쿼리는 반복적입니다. 즉, 주기적으로 변경 없이 또는 약간만 수정되어(예: 다른 파라미터 값) 실행됩니다. 동일하거나 유사한 쿼리를 반복 실행하면, 빈번한 쿼리가 더 빠르게 접근할 수 있도록 인덱스를 추가하거나 데이터를 재구성할 수 있습니다. 이 접근 방식은 "데이터 프루닝(data pruning)"이라고도 알려져 있으며 ClickHouse는 이를 위한 세 가지 기법을 제공합니다:

  1. Primary key 인덱스 — 테이블 데이터의 정렬 순서를 정의합니다. 잘 선택된 primary key는 전체 컬럼 스캔 대신 빠른 이진 검색으로 필터(위 쿼리의 WHERE 절 같은)를 평가할 수 있게 해 줍니다. 더 기술적으로 말하면, 스캔의 런타임이 데이터 크기에 대해 선형이 아니라 로그가 됩니다.
  2. 테이블 프로젝션(projections) — 테이블의 대안적이고 내부적인 버전으로, 같은 데이터를 저장하지만 다른 primary key로 정렬합니다. 프로젝션은 빈번한 필터 조건이 둘 이상일 때 유용할 수 있습니다.
  3. 스킵 인덱스(skipping indexes) — 컬럼에 추가 데이터 통계를 내장합니다. 예를 들어 컬럼의 최솟값과 최댓값, 고유 값 집합 등입니다. 스킵 인덱스는 primary key와 테이블 프로젝션과 직교하며, 컬럼의 데이터 분포에 따라 필터 평가를 크게 가속화할 수 있습니다.

세 기법 모두 전체 컬럼 읽기 동안 가능한 한 많은 행을 건너뛰는 것을 목표로 합니다. 데이터를 읽는 가장 빠른 방법은 전혀 읽지 않는 것이기 때문입니다.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 Data Pruning 섹션을 참고하세요.

스토리지 계층: 데이터 압축 (Storage layer: data compression)

그 외에도 ClickHouse의 스토리지 계층은 다양한 코덱을 사용해 원시 테이블 데이터를 추가로(그리고 선택적으로) 압축합니다.

같은 타입과 데이터 분포의 값이 함께 위치하므로 컬럼 저장소는 이러한 압축에 특히 적합합니다.

사용자는 다양한 일반 압축 알고리즘(예: ZSTD)이나 특수 코덱으로 컬럼이 압축되도록 지정할 수 있습니다. 예를 들어 부동소수점 값용 Gorilla와 FPC, 정수 값용 Delta와 GCD, 또는 암호화 코덱으로서의 AES 등이 있습니다.

데이터 압축은 데이터베이스 테이블의 저장 크기를 줄일 뿐만 아니라, 로컬 디스크와 네트워크 I/O가 종종 낮은 처리량에 의해 제한되므로 많은 경우 쿼리 성능도 개선합니다.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 On-Disk Format 섹션을 참고하세요.

최첨단 쿼리 처리 계층 (State-of-the-art query processing layer)

마지막으로 ClickHouse는 벡터화된 쿼리 처리 계층을 사용하여 최대 속도와 효율을 위해 모든 리소스를 활용하도록 쿼리 실행을 최대한 병렬화합니다.

"벡터화"는 쿼리 플랜 연산자가 단일 행 대신 일괄 처리된 중간 결과 행을 전달한다는 것을 의미합니다. 이는 CPU 캐시를 더 잘 활용하게 하고 연산자가 SIMD 명령을 적용해 여러 값을 한 번에 처리할 수 있게 해 줍니다. 실제로 많은 연산자는 SIMD 명령어 세트 세대마다 하나씩 여러 버전으로 제공됩니다. ClickHouse는 실행되는 하드웨어의 기능에 따라 가장 최근이고 가장 빠른 버전을 자동으로 선택합니다.

현대 시스템은 수십 개의 CPU 코어를 가집니다. 모든 코어를 활용하기 위해 ClickHouse는 쿼리 플랜을 여러 레인(lane)으로 펼치는데, 보통 코어당 하나입니다. 각 레인은 테이블 데이터의 서로 분리된 범위를 처리합니다. 그렇게 해서 데이터베이스 성능은 사용 가능한 코어 수에 따라 "수직"으로 확장됩니다.

단일 노드가 테이블 데이터를 담기에 너무 작아지면 추가 노드를 더해 클러스터를 구성할 수 있습니다. 테이블은 분할("샤딩")되어 노드들에 분산될 수 있습니다. ClickHouse는 테이블 데이터를 저장하는 모든 노드에서 쿼리를 실행하며, 그렇게 해서 사용 가능한 노드 수에 따라 "수평"으로 확장됩니다.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 Query Processing Layer 섹션을 참고하세요.

세심한 디테일 집중 (Meticulous attention to detail)

"ClickHouse는 괴짜 시스템입니다 - 여러분은 해시 테이블이 20개 버전이나 있습니다. 대부분의 시스템이 해시 테이블 하나를 가질 때 여러분은 이런 놀라운 것들을 다 가지고 있습니다… ClickHouse는 이렇게 특화된 구성요소들을 모두 갖추고 있기 때문에 놀라운 성능을 냅니다" Andy Pavlo, CMU 데이터베이스 교수

ClickHouse를 구별 짓게 하는 것은 저수준 최적화에 대한 세심한 집중입니다. 단순히 동작하는 데이터베이스를 만드는 것도 한 가지이지만, 다양한 쿼리 유형, 데이터 구조, 분포, 인덱스 구성에서 속도를 제공하도록 엔지니어링하는 것은 바로 그 "괴짜 시스템" 예술이 빛나는 곳입니다.

해시 테이블 (Hash Tables). 해시 테이블을 예로 들어 보겠습니다. 해시 테이블은 조인과 집계에 사용되는 핵심 데이터 구조입니다. 프로그래머로서 다음 설계 결정을 고려해야 합니다:

  • 선택할 해시 함수,
  • 충돌 해결: open addressing 또는 chaining,
  • 메모리 레이아웃: 키와 값을 위한 배열 하나, 아니면 별도의 배열들?
  • 채움 비율(fill factor): 언제, 어떻게 리사이즈할까? 리사이즈 중 값을 어떻게 이동할까?
  • 삭제: 해시 테이블이 항목 축출을 허용해야 할까?

타사 라이브러리가 제공하는 표준 해시 테이블은 기능적으로는 동작하겠지만 빠르지는 않을 것입니다. 훌륭한 성능은 세심한 벤치마킹과 실험을 요구합니다.

ClickHouse의 해시 테이블 구현은 쿼리와 데이터의 세부 사항에 따라 30개 이상의 사전 컴파일된 해시 테이블 변형 중 하나를 선택합니다.

알고리즘 (Algorithms). 알고리즘도 마찬가지입니다. 예를 들어 정렬에서 다음을 고려할 수 있습니다:

  • 무엇을 정렬할까: 숫자, 튜플, 문자열, 구조체?
  • 데이터가 RAM에 있나?
  • 정렬이 안정적(stable)이어야 하나?
  • 모든 데이터를 정렬해야 할까, 아니면 부분 정렬로 충분할까?

데이터 특성에 의존하는 알고리즘은 일반적인 대응 알고리즘보다 더 나은 성능을 내는 경우가 많습니다. 데이터 특성을 미리 알 수 없다면, 시스템은 다양한 구현을 시도하고 런타임에 가장 잘 동작하는 것을 선택할 수 있습니다. 예시는 ClickHouse에서 LZ4 압축 해제가 구현되는 방법에 관한 글을 참고하세요.

🤿 더 깊이 알아보고 싶다면 VLDB 2024 논문 웹 버전의 Holistic Performance Optimization 섹션을 참고하세요.

VLDB 2024 논문 (VLDB 2024 paper)

2024년 8월, 우리는 첫 연구 논문을 VLDB에 채택받아 출판했습니다.

VLDB는 매우 큰 데이터베이스에 관한 국제 학회이며, 데이터 관리 분야의 최고 학회 중 하나로 널리 인정받고 있습니다. 수백 개의 제출 중에서 VLDB는 일반적으로 약 20%의 채택률을 가집니다.

논문의 PDF 또는 웹 버전을 읽을 수 있으며, 웹 버전은 ClickHouse를 그렇게 빠르게 만드는 가장 흥미로운 아키텍처 및 시스템 설계 구성요소에 대해 간결하게 설명합니다.

우리 CTO이자 ClickHouse 창시자인 Alexey Milovidov가 논문을 발표했고(슬라이드 여기), 이어서 Q&A가 있었습니다(시간이 빨리 다 됐지만요!).

녹화된 발표는 여기에서 볼 수 있습니다:

더 알아보기 (Learn more)