본문으로 바로가기
본문으로 바로가기

ClickHouse는 왜 그렇게 빠를까요?

데이터 저장 방식 외에도 데이터베이스 성능에는 다양한 다른 요소들이 기여합니다. 다음으로, 특히 다른 컬럼 지향 데이터베이스와 비교했을 때 ClickHouse가 이렇게 빠른 이유를 더 자세히 설명합니다.

아키텍처 관점에서 보면 데이터베이스는 (최소한) 스토리지 계층과 쿼리 처리 계층으로 구성됩니다. 스토리지 계층은 테이블 데이터의 저장, 로드 및 유지 관리를 담당하고, 쿼리 처리 계층은 사용자 쿼리를 실행합니다. 다른 데이터베이스와 비교했을 때 ClickHouse는 두 계층 모두에서 매우 빠른 INSERT 및 SELECT 쿼리를 가능하게 하는 혁신을 제공합니다.

스토리지 계층: 동시 INSERT는 서로 격리됩니다

ClickHouse에서 각 테이블은 여러 「table parts」로 구성됩니다. 파트는 사용자가 테이블에 데이터를 삽입할 때마다(INSERT 구문) 생성됩니다. 쿼리는 항상, 쿼리가 시작되는 시점에 존재하는 모든 테이블 파트에 대해 실행됩니다.

너무 많은 파트가 누적되지 않도록, ClickHouse는 백그라운드에서 병합 작업을 실행하여 여러 개의 더 작은 파트를 지속적으로 하나의 더 큰 파트로 결합합니다.

이 접근 방식에는 여러 가지 장점이 있습니다. 모든 데이터 처리를 백그라운드 파트 병합으로 위임할 수 있어 데이터 쓰기를 가볍고 매우 효율적으로 유지할 수 있습니다. 개별 INSERT 작업은 전역, 즉 테이블 단위 데이터 구조를 업데이트할 필요가 없다는 의미에서 「로컬」하다고 할 수 있습니다. 그 결과, 여러 개의 동시 INSERT는 서로 간에도, 그리고 기존 테이블 데이터와도 동기화가 필요 없으며, 따라서 디스크 I/O 속도에 거의 근접한 속도로 INSERT를 수행할 수 있습니다.

🤿 VLDB 2024 논문의 웹 버전 중 On-Disk Format 섹션에서 이 내용을 더 깊이 살펴보십시오.

Storage layer: 동시 INSERT와 SELECT는 서로 격리됩니다

INSERT 작업은 SELECT 쿼리와 완전히 격리되며, 삽입된 데이터 파트의 병합은 동시 실행 중인 쿼리에 영향을 주지 않고 백그라운드에서 수행됩니다.

🤿 이 내용은 VLDB 2024 논문의 웹 버전에 있는 Storage Layer 섹션에서 더욱 자세히 확인할 수 있습니다.

Storage layer: merge-time computation

다른 데이터베이스와 달리 ClickHouse는 merge 백그라운드 프로세스 동안 부가적인 모든 데이터 변환 작업을 수행함으로써 데이터 쓰기를 가볍고 효율적으로 유지합니다. 예를 들면 다음과 같습니다.

  • 입력 파트에서 각 행의 가장 최신 버전만 유지하고 나머지 모든 행 버전을 버리는 replacing merge. replacing merge는 머지 시점에 이루어지는 정리 작업으로 볼 수 있습니다.

  • 입력 파트에 있는 중간 집계 상태를 결합해 새로운 집계 상태를 생성하는 aggregating merge. 이해하기 어려워 보일 수 있지만, 실제로는 증분 집계를 구현하는 것에 불과합니다.

  • 특정 시간 기반 규칙에 따라 행을 압축, 이동 또는 삭제하는 TTL (time-to-live) merge.

이러한 변환의 목적은 작업(연산)을 사용자 쿼리가 실행되는 시점에서 머지 시점으로 옮기는 데 있습니다. 이는 두 가지 이유에서 중요합니다.

한편으로, 사용자 쿼리가 「변환된」 데이터, 예를 들어 사전 집계된 데이터를 활용할 수 있는 경우, 쿼리 속도가 1000배 이상까지 상당히 빨라질 수 있습니다.

다른 한편으로, 머지 실행 시간의 대부분은 입력 파트를 로드하고 출력 파트를 저장하는 데 소비됩니다. 머지 중에 데이터를 변환하는 추가 작업은 일반적으로 머지 실행 시간에 큰 영향을 주지 않습니다. 이러한 모든 처리는 완전히 투명하며, (성능을 제외하면) 쿼리 결과에는 아무런 영향을 주지 않습니다.

🤿 이에 대한 자세한 내용은 VLDB 2024 논문의 웹 버전에 있는 Merge-time Data Transformation 섹션을 참고하십시오.

Storage layer: data pruning

실제 환경에서는 많은 쿼리가 반복적으로 실행되며, 일정 주기마다 변경 없이 실행되거나 (예: 서로 다른 파라미터 값처럼) 약간만 수정되어 실행됩니다. 동일하거나 유사한 쿼리를 반복해서 실행하면 인덱스를 추가하거나 데이터를 재구성하여 자주 실행되는 쿼리가 더 빠르게 접근할 수 있도록 할 수 있습니다. 이러한 접근 방식은 「data pruning(데이터 프루닝)」이라고도 하며, ClickHouse는 이를 위해 다음의 세 가지 기법을 제공합니다:

  1. 테이블 데이터의 정렬 순서를 정의하는 Primary key indexes. 잘 설계된 기본 키는 전체 컬럼 스캔 대신 빠른 이진 검색을 사용하여 필터(위 쿼리의 WHERE 절과 같은)를 평가할 수 있게 합니다. 좀 더 기술적으로 말하면, 스캔의 실행 시간은 데이터 크기에 대해 선형이 아니라 로그에 비례하게 됩니다.

  2. 동일한 데이터를 저장하지만 서로 다른 기본 키로 정렬된, 테이블의 대안적인 내부 버전인 Table projections. 프로젝션은 자주 사용되는 필터 조건이 둘 이상일 때 유용합니다.

  3. 컬럼에 최소/최대 컬럼 값, 고유 값 집합 등과 같은 추가 데이터 통계를 내장하는 Skipping indexes. 스키핑 인덱스는 기본 키 및 테이블 프로젝션과는 독립적이며, 컬럼 내 데이터 분포에 따라 필터 평가 속도를 크게 높일 수 있습니다.

이 세 가지 기법의 공통 목표는 전체 컬럼 읽기 동안 가능한 한 많은 행을 건너뛰는 것입니다. 데이터를 읽는 가장 빠른 방법은 애초에 읽지 않는 것이기 때문입니다.

🤿 이 주제에 대해서는 VLDB 2024 논문의 웹 버전에 있는 Data Pruning 섹션을 참고하여 더 깊이 살펴보십시오.

스토리지 계층: 데이터 압축

또한 ClickHouse의 스토리지 계층은 추가로(선택 사항으로) 서로 다른 코덱을 사용해 원시 테이블 데이터를 압축합니다.

컬럼 지향 스토리지는 동일한 타입과 데이터 분포를 가진 값들이 함께 배치되기 때문에 이러한 압축에 특히 잘 적합합니다.

사용자는 컬럼이 다양한 범용 압축 알고리즘(ZSTD 등)이나 특수 코덱을 사용하여 압축되도록 지정할 수 있습니다. 예를 들어 부동 소수점 값에는 Gorilla 및 FPC, 정수 값에는 Delta 및 GCD, 심지어 암호화 코덱으로 AES를 사용할 수 있습니다.

데이터 압축은 데이터베이스 테이블의 저장 공간을 줄일 뿐만 아니라, 많은 경우 로컬 디스크와 네트워크 I/O의 처리량이 낮아 병목이 되므로 쿼리 성능도 향상시킵니다.

🤿 이 주제에 대해서는 VLDB 2024 논문의 웹 버전에 있는 On-Disk Format 섹션에서 더 자세히 살펴보십시오.

최첨단 쿼리 처리 레이어

마지막으로, ClickHouse는 벡터화된 쿼리 처리 레이어를 사용하여 쿼리 실행을 최대한 병렬화함으로써 모든 리소스를 최고 속도와 효율로 활용합니다.

「벡터화(Vectorization)」란 쿼리 플랜 연산자가 개별 행이 아니라 중간 결과 행을 배치 단위로 전달하는 것을 의미합니다. 이를 통해 CPU 캐시 활용도가 향상되고, 연산자가 SIMD 명령어를 사용하여 여러 값을 한 번에 처리할 수 있습니다. 실제로 많은 연산자는 SIMD 명령어 집합 세대마다 하나씩, 여러 버전으로 제공됩니다. ClickHouse는 실행 중인 하드웨어의 기능을 기반으로 가장 최신이자 가장 빠른 버전을 자동으로 선택합니다.

최신 시스템에는 수십 개의 CPU 코어가 있습니다. 모든 코어를 활용하기 위해 ClickHouse는 일반적으로 코어당 하나씩, 쿼리 플랜을 여러 개의 레인으로 펼칩니다. 각 레인은 테이블 데이터의 서로 겹치지 않는 범위를 처리합니다. 이렇게 하면 사용 가능한 코어 수에 따라 데이터베이스의 성능이 「수직적으로」 확장됩니다.

단일 노드에 테이블 데이터를 담기에 용량이 부족해지면, 노드를 추가하여 클러스터를 구성할 수 있습니다. 테이블을 세그먼트(shard) 단위로 분할해 노드 간에 분산할 수 있습니다. ClickHouse는 테이블 데이터를 저장하는 모든 노드에서 쿼리를 실행하여, 사용 가능한 노드 수에 따라 「수평적으로」 확장됩니다.

🤿 웹 버전에 있는 VLDB 2024 논문의 Query Processing Layer 섹션에서 이 내용을 더 깊이 있게 살펴보십시오.

세심한 디테일에 대한 집착

"ClickHouse는 괴짜 같은 시스템입니다 - 해시 테이블이 20개 버전이나 있어요. 대부분의 시스템은 해시 테이블을 하나만 갖고 있는데, ... ClickHouse는 이렇게 특화된 컴포넌트들을 많이 가지고 있기 때문에 놀라운 성능을 냅니다" Andy Pavlo, CMU 데이터베이스 교수

ClickHouse를 돋보이게 하는 것은 저수준 최적화에 대한 세심한 집착입니다. 단순히 동작하는 데이터베이스를 만드는 것과, 다양한 쿼리 유형, 데이터 구조, 분산 방식, 인덱스 구성 전반에서 속도를 내도록 설계하는 것은 전혀 다른 일입니다. 바로 이 지점에서 "freak system"다운 장인정신이 드러납니다.

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

  • 어떤 해시 함수를 선택할지,
  • 충돌 해결 방식: open addressing 또는 chaining 중 무엇을 사용할지,
  • 메모리 레이아웃: 키와 값을 하나의 배열에 둘지, 별도 배열로 분리할지,
  • 채움 비율(fill factor): 언제, 어떻게 크기를 조정할지? 크기를 조정할 때 값을 어떻게 이동시킬지?
  • 삭제: 해시 테이블이 항목 제거를 허용해야 하는지?

타사 라이브러리가 제공하는 표준 해시 테이블도 기능적으로는 동작하지만 빠르지는 않습니다. 뛰어난 성능을 내려면 세심한 벤치마킹과 실험이 필요합니다.

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

알고리즘. 알고리즘도 마찬가지입니다. 예를 들어 정렬을 생각해 보면 다음과 같은 점을 고려할 수 있습니다.

  • 무엇을 정렬하는가: 숫자, 튜플, 문자열, 구조체 중 무엇인가?
  • 데이터가 RAM에 있는가?
  • 정렬이 안정적(stable)이어야 하는가?
  • 전체 데이터를 정렬해야 하는가, 아니면 부분 정렬만으로 충분한가?

데이터 특성에 의존하는 알고리즘은 일반적인 알고리즘보다 더 좋은 성능을 내는 경우가 많습니다. 데이터 특성을 미리 알 수 없다면, 시스템이 여러 구현을 시도해 보고 런타임에 가장 잘 동작하는 것을 선택할 수 있습니다. 예시는 ClickHouse에서 LZ4 디컴프레션이 어떻게 구현되어 있는지에 대한 글을 참고하십시오.

🤿 자세한 내용은 VLDB 2024 논문의 웹 버전에 있는 Holistic Performance Optimization 섹션을 참조하십시오.

VLDB 2024 논문

2024년 8월, 첫 연구 논문이 VLDB에 채택되어 게재되었습니다. VLDB는 초대형 데이터베이스를 다루는 국제 학회로, 데이터 관리 분야에서 가장 권위 있는 학회 중 하나로 널리 인정받고 있습니다. 수백 편의 투고 가운데 VLDB의 일반적인 채택률은 약 20%입니다.

논문 PDF 또는 웹 버전을 통해 내용을 확인할 수 있으며, 이 논문은 ClickHouse가 매우 빠른 성능을 내도록 하는 가장 흥미로운 아키텍처 및 시스템 설계 요소를 간결하게 설명합니다.

ClickHouse의 창시자이자 CTO인 Alexey Milovidov가 논문을 발표했습니다(슬라이드는 여기에서 확인할 수 있으며, 이어서 Q&A 세션이 진행되었지만 시간이 금방 부족해졌습니다). 발표 녹화 영상은 아래에서 볼 수 있습니다: