글을 작성하게 된 계기
ClickHouse 세미나에서 새로운 분을 알게 되었는데, 왜 칼럼 지향 데이터베이스가 빠른가요? 라는 질문을 받았습니다. 이를 답하면서 석연치 않은 부분이 있었는데, 이를 정리해보기 위해 글을 작성하게 되었습니다. 확실히 알고 있다고 착각하고 있었는데, 막상 말로 표현하려고 하니까 생각보다 답답하더라고요. 😆
1. Row 기반 데이터베이스에서 특정 칼럼을 빨리 읽지 못하는 이유
Row 기반 저장 방식에서는 하나의 행(Row)에 포함된 여러 칼럼이 하나의 묶음으로 저장됩니다. 이 Row들은 디스크의 16KB 페이지 내부에 행 단위 로 채워지며, 저장 방식 자체가 Row 전체를 불러와야만 칼럼을 확인할 수 있는 구조 로 되어 있습니다.
1
Row1: [id] [name] [price] [date] [state] [기타칼럼...]
MySQL의 InnoDB 스토리지 엔진을 사용할 때, 데이터를 페이지(Page) 단위로 저장합니다. 하나의 페이지 안에는 여러 Row가 순서대로 배치되며, 각 Row는 여러 칼럼이 하나의 덩어리로 함께 저장됩니다. Row1이라는 데이터 묶음 안에는 id, name, price와 같은 모든 칼럼 값이 한 번에 기록되어 있으며, 어떤 위치를 읽더라도 해당 Row 전체가 통째로 포함되어 있는 형태입니다. 이 특성 때문에, 특정 칼럼만 필요하더라도 Row 전체를 먼저 읽어 온 뒤 그 내부에서 원하는 칼럼을 찾아야 합니다.
1
2
3
4
5
6
7
8
#################### DISK PAGE (16KB) ####################
Row1: [id] [name] [price] [date] [state] [기타칼럼...]
Row2: [id] [name] [price] [date] [state] [기타칼럼...]
Row3: [id] [name] [price] [date] [state] [기타칼럼...]
Row4: [id] [name] [price] [date] [state] [기타칼럼...]
###########################################################
즉, 칼럼 단위로 분리된 접근이 불가능하며, 언제나 Row 단위로 읽고 파싱하는 과정이 선행됩니다. 예를 들어, price 칼럼만 조회하더라도, 스토리지 엔진은 반드시 하나의 Row 전체를 읽어 온 뒤 그 내부에서 필요한 칼럼 값을 찾아야 합니다. 이 Row가 디스크에 있을 수도 있고, 버퍼 풀이나 OS 캐시에 올라와 있을 수도 있지만, 어떤 저장 위치에 있든지 간에 Row 전체를 읽고 그 내부를 파싱해야 한다는 구조 자체는 변하지 않습니다. Row의 수가 많아질수록 읽어야 하는 Row가 늘어나고, 각 Row 내부에서 모든 칼럼을 순차적으로 비교·파싱하게 되므로 조회 속도는 점점 느려질 수밖에 없습니다.
1
2
3
4
5
6
7
8
9
10
11
12
#################### DISK PAGE (16KB) ####################
# 먼저 한 행을 읽고 그 과정에서 내부 칼럼 값을 비교.
Row1: [id] → [name] → [price] → [date] → [state] → [기타...]
↓
Row2: [id] → [name] → [price] → [date] → [state] → [기타...]
↓
Row3: [id] → [name] → [price] → [date] → [state] → [기타...]
↓
Row4: [id] → [name] → [price] → [date] → [state] → [기타...]
###########################################################
즉, price라는 칼럼을 읽을 때, 다음과 같은 과정이 발생하죠.
1) 디스크에서 페이지(16KB) 읽기 2) 페이지 안의 모든 Row 스캔 3) Row1 파싱: id, name, price, date, state… 4) Row2 파싱: id, name, price, date, state… 5) Row3 파싱: id, name, price, date, state… 6) 필요한 price만 메모리로 추출
2. Column 기반 검색은 왜 빠를까?
칼럼 지향(Column-oriented) 데이터베이스에서는 각 칼럼이 서로 다른 칼럼과 함께 저장되는 것이 아니라, 칼럼 단위로 완전히 분리된 파일에 연속적인 배열 형태로 저장됩니다. 이 구조 덕분에 특정 칼럼을 조회할 때 스토리지 엔진은 해당 칼럼 파일만 순차적으로 읽는 작업만 수행하면 됩니다.
칼럼 파일에는 동일한 타입의 값들이 연속적으로 저장되어 있기 때문에, 디스크 접근이 최소화되며 CPU는 데이터를 블록 단위로 메모리에 적재한 뒤 벡터화(SIMD) 방식으로 매우 빠르게 연산을 진행할 수 있습니다. 즉, 필요하지 않은 칼럼을 읽거나 파싱할 필요가 없고, 필요한 데이터만 선택적으로 읽기 때문에 대규모 분석 쿼리에서 뛰어난 성능을 발휘합니다.
1
2
3
4
5
6
7
8
9
##################### ClickHouse Column Files #####################
./id.bin → [1][2][3][4][5][6] ...
./name.bin → [A][B][C][D][E][F] ...
./price.bin → [100][200][150][300][250][400] ...
./date.bin → [2025-01-01][2025-01-02] ...
./state.bin → ['OK']['OK']['FAIL']['OK'] ...
####################################################################
칼럼 지향 데이터베이스에서 특정 칼럼을 조회할 때, 필요한 칼럼이 저장된 파일만 순차적으로 읽고, 그 값을 연속된 배열 형태로 메모리에 적재한 뒤 벡터화된 방식으로 한 번에 처리합니다. 이 과정에서는 Row 전체를 읽거나 여러 칼럼을 파싱할 필요가 전혀 없습니다. 다음과 같이요.
1
2
3
4
5
6
############## COLUMN STORAGE ##############
column_price.bin
[100] → [200] → [150] → [300] → [250] → [400]
############################################
칼럼이 각각 독립된 파일로 분리되어 저장되어 있으며, 동일한 타입의 값들이 연속적으로 배치되어 있기 때문에 필요한 칼럼만 최소한의 I/O로 조회할 수 있습니다. 바로 이러한 구조적 차이로 칼럼 기반 데이터베이스가 대규모 분석 쿼리에서 좋은 성능을 발휘합니다. 칼럼 값들이 메모리와 디스크에서 연속된 배열 형태 로 저장되기 때문에, 데이터베이스는 불필요한 위치로 건너뛸 필요 없이 단일 순차 읽기 만으로 대량 데이터를 빠르게 가져올 수 있으니까요.
1
2
3
4
5
6
7
8
9
################### Column-Oriented Storage ###################
# 각 칼럼은 독립된 파일로 저장됨
./columnA.bin → [a1][a2][a3][a4][a5] ...
./columnB.bin → [b1][b2][b3][b4][b5] ...
./columnC.bin → [c1][c2][c3][c4][c5] ...
./columnD.bin → [d1][d2][d3][d4][d5] ...
#################################################################
3. 정리
칼럼 지향 데이터베이스가 왜 검색이 빠른지 대략적인 원리를 살펴봤는데요, ClickHouse가 빠른 이유는 압축, 자료구조, 블록 단위 skip, 파티션 prune 등 훨씬 다양합니다. 시간이 나면 해당 이유도 정리해보죠. 물론 … 전사 배치 개발 해야해서 언제가 될지는 모르겠지만. 🤔