분석·검색: ClickHouse·DuckDB·Elasticsearch
많은 행을 훑어 집계하는 컬럼형 분석 엔진과, 글에서 낱말을 찾는 역색인 검색 엔진이다.
목차 (9)
OLTP와 OLAP: 행으로 둘까, 열로 둘까
쇼핑몰의 주문 처리는 "주문 하나를 넣고, 주문 하나를 꺼낸다"를 초당 수천 번 한다. 이런 일을 OLTP(온라인 트랜잭션 처리)라 하고, 행 하나의 모든 열을 한곳에 두는 행 저장이 맞는다. 반대로 "지난 1년 지역별 매출 합계"는 수억 행을 훑지만 열은 두세 개만 본다. 이런 일이 OLAP(온라인 분석 처리)이고, 열마다 값을 따로 모아 두는 열 저장(columnar)이 맞는다.
열 저장의 이득은 읽는 양만이 아니다. 한 열에는 같은 종류의 값이 이어져 있어 압축이 아주 잘 된다. 날짜처럼 정렬된 값은 차이만, 지역처럼 종류가 적은 값은 사전 번호만 적으면 된다(델타·사전·런 길이 인코딩). 대가는 행 하나를 넣고 고치는 일이 비싸다는 것이다. 한 행이 열 개수만큼의 파일에 흩어져 있기 때문이다. 그래서 분석 엔진은 행을 하나씩이 아니라 수천·수만 행씩 묶어 넣기를 기대하고, 고치기와 지우기는 드물게 일어난다고 가정한다.
벡터화 실행
전통적인 실행기는 연산자마다 "다음 행을 줘"(next) 함수를 행 하나에 한 번씩 부른다(Volcano 모델). 행이 수십억 개면 함수 호출과 분기, 행마다 다른 자료형을 확인하는 비용이 실제 계산보다 커진다. MonetDB/X100 논문(Boncz·Zukowski·Nes, CIDR 2005)이 제안한 벡터화 실행은 한 번에 한 열의 값 수천 개 묶음(벡터)을 넘긴다.
묶음 크기는 CPU 캐시에 들어갈 만큼 작고 호출 비용이 묻힐 만큼 크게 고른다. DuckDB는 기본 2048개, ClickHouse는 수만 행짜리 블록을 쓴다. 연산자 안은 "배열의 i번째 값끼리 더한다" 같은 단순한 반복문이 되어 컴파일러가 SIMD로 바꾸기 좋다. 쿼리마다 기계어를 만들어 내는 컴파일 방식도 있지만, 오늘날 열 저장 분석 엔진 대부분은 벡터화를 기본으로 한다.
ClickHouse: MergeTree와 희소 인덱스
ClickHouse는 Yandex가 웹 분석 서비스를 위해 만들어 2016년에 Apache 2.0으로 공개한 열 저장 분석 데이터베이스다. 설계는 VLDB 2024 논문과 구조 개요 문서에 정리되어 있다. 중심은 MergeTree 테이블 엔진이다.
INSERT 묶음 하나가 곧 part 하나다. 행을 정렬 키(ORDER BY) 순서로 정렬하고 열마다 따로 압축해 새 디렉터리로 쓴다. 기존 part는 건드리지 않는다.
테이블을 만들 때 정하는 ORDER BY가 곧 정렬 키이자 기본 키다. 관계형의 기본 키와 달리 유일성을 강제하지 않고, 데이터를 그 순서로 정렬해 두는 역할만 한다. 희소 기본 인덱스 가이드의 설명대로 인덱스는 행마다가 아니라 granule(기본 8192행)마다 키 하나만 갖는다. 수십억 행도 인덱스가 메모리에 들어가는 대신, 한 행을 콕 집어 찾지는 못하고 granule 단위로 읽는다. 그래서 정렬 키의 첫 열은 가장 자주 거르는 조건이어야 하고, 값의 종류가 적은 열을 앞에 두면 정렬된 덩어리가 길어져 압축과 건너뛰기가 모두 좋아진다.
병합(merge)은 단순한 정리를 넘어 기능이 된다. ReplacingMergeTree는 병합할 때 같은 키의 옛 행을 버리고, SummingMergeTree·AggregatingMergeTree는 같은 키의 행을 미리 합쳐 둔다. 다만 병합은 언제 끝날지 모르므로 쿼리 결과가 늘 합쳐진 상태라고 가정하면 안 된다. 복제는 ReplicatedMergeTree가 맡는다. 서버마다 새 part를 만들었다는 기록을 ClickHouse Keeper(Raft 기반 조정 서비스)에 남기면, 다른 복제본이 그 part를 받아 온다.
DuckDB: 앱 안에서 도는 분석 엔진
DuckDB는 분석용 SQLite를 목표로 한다. 공식 소개대로 서버 없이 앱 프로세스 안에서 돌고(in-process), 열 저장과 벡터화 실행을 쓰며, MIT 라이선스다. Python·R·Java·브라우저(WebAssembly)에서 라이브러리처럼 불러 쓴다. 처음 설계는 SIGMOD 2019 데모 논문에 있다.
자기 DB 파일(테이블을 행 묶음 단위로 나눠 열마다 압축해 두는 단일 파일)도 있지만, Parquet·CSV·JSON 파일을 적재하지 않고 바로 쿼리하는 쓰임이 많다. 노트북에서 수 GB짜리 Parquet를 판다스보다 빠르게 집계하거나, 데이터 파이프라인의 한 단계로 끼워 넣는 식이다. 트랜잭션은 MVCC로 지원하지만 쓰는 프로세스는 하나라는 점에서 SQLite와 닮았다. 여러 사람이 동시에 쓰는 공용 분석 서버가 필요하면 ClickHouse 같은 서버형을, 한 사람·한 프로세스의 분석이면 DuckDB를 고른다.
검색 엔진: 역색인과 분석기
"검색 엔진 빠른"으로 글을 찾는 일은 관계형의 LIKE '%검색%'으로는 안 된다. 모든 행을 훑어야 하고, "engines"와 "engine"을 같은 낱말로 보지도, 얼마나 잘 맞는지 순위를 매기지도 못한다. Elasticsearch와 OpenSearch는 이 일을 Apache Lucene 라이브러리에 맡긴다. Lucene의 중심은 역색인(inverted index)이다. 문서 → 낱말이 아니라 낱말 → 문서 목록으로 뒤집어 저장한다.
글을 낱말로 바꾸는 것이 분석기(analyzer)다. 글자 필터, 토크나이저(낱말로 쪼개기), 토큰 필터(소문자화, 불용어 제거, 어간 추출, 동의어)를 차례로 거친다. 언어마다 알맞은 분석기가 다르고, 한국어는 조사가 붙어 띄어쓰기만으로 쪼개면 "검색엔진이"와 "검색엔진을"이 다른 낱말이 되므로 형태소 분석기(Elasticsearch의 nori 등)를 쓴다. 정확히 같은 값으로만 거를 필드(상태 코드, 태그)는 분석하지 않는 keyword 필드로 둔다.
세그먼트, refresh, merge
Lucene 인덱스는 불변 세그먼트의 묶음이다. 세그먼트 하나가 그 자체로 작은 역색인이고, 검색은 모든 세그먼트에 묻고 결과를 합친다. 정렬된 사전과 압축된 포스팅을 고치지 않고 새로 쓰기만 하므로, 구조가 LSM 트리와 닮았다.
문서를 색인하면 메모리의 인덱싱 버퍼에 들어가고, 같은 요청이 translog에도 적힌다(기본은 요청마다 fsync). 응답은 여기서 나간다. 아직 검색에는 안 잡힌다.
새 문서가 검색되기까지 걸리는 시간은 refresh 주기다. near-real-time 검색 문서대로 기본은 1초마다지만, 최근 30초 동안 검색이 없었던 인덱스는 건너뛴다. 대량 적재 중에는 refresh를 늦추거나 끄면 작은 세그먼트가 덜 생겨 훨씬 빨라진다. 커밋 사이의 내구성은 translog가 지킨다. 기본 설정은 요청마다 translog를 fsync하고 나서 응답한다.
샤드, 레플리카, BM25
Elasticsearch의 인덱스는 샤드로 나뉜다. 샤드 하나가 Lucene 인덱스 하나이고, 문서는 _id(또는 routing 값)의 해시로 primary 샤드 하나에 들어간다. primary 수는 문서가 어느 샤드에 갈지를 정하므로 만든 뒤 바꾸려면 다시 색인(또는 split·shrink)해야 하고, 레플리카 수는 언제든 바꾼다. 쓰기는 primary가 받아 레플리카로 전달하고, 검색은 primary와 레플리카 어느 쪽이든 받는다.
순위는 점수가 정한다. 기본 유사도는 BM25다. 문서에 검색 낱말이 자주 나올수록(TF) 점수가 오르지만 일정 횟수 뒤로는 거의 늘지 않고, 흔한 낱말보다 드문 낱말이 맞을 때(IDF) 더 크게 오르며, 긴 문서는 우연히 낱말이 나올 가능성이 크므로 길이로 나눠 깎는다. 공식의 근거는 Robertson·Zaragoza의 정리 논문에 있다. IDF는 기본으로 샤드마다 따로 세므로, 문서가 적은 인덱스에서는 샤드에 따라 점수가 조금씩 달라질 수 있다. 의미가 가까운 글을 찾는 벡터 검색은 시계열·그래프·벡터 노트에서 다룬다.
Elasticsearch와 OpenSearch
Elasticsearch는 처음에 Apache 2.0이었다. 2021년 1월 Elastic은 7.11부터 SSPL과 Elastic License 2.0으로 바꾼다고 발표했고, 같은 해 4월 AWS가 마지막 Apache 판인 7.10.2에서 갈라진 OpenSearch를 발표했다.
2024년 8월 29일 Elastic은 AGPLv3를 세 번째 선택지로 더했다. 기존 SSPL·ELv2를 없앤 것이 아니라 하나를 더한 것이다. 같은 해 9월 AWS는 OpenSearch를 리눅스 재단 아래 OpenSearch Software Foundation으로 옮겼고, OpenSearch는 Apache 2.0을 유지한다. 두 제품은 API의 뿌리가 같지만 갈라진 뒤 생긴 기능과 클라이언트는 서로 호환되지 않을 수 있다. (2026년 10월 기준)
고르는 법
| 기준 | ClickHouse | DuckDB | Elasticsearch / OpenSearch |
|---|---|---|---|
| 형태 | 분산 서버 | 앱에 넣는 라이브러리 | 분산 서버 |
| 저장 | 열 저장 part, 희소 인덱스 | 열 저장 단일 파일, 외부 파일 직접 읽기 | Lucene 세그먼트(역색인 + 열 값) |
| 잘하는 질문 | 수십억 행 집계, 실시간 대시보드, 로그·이벤트 분석 | 한 사람의 데이터 분석, 파이프라인 안의 변환 | 전문 검색과 순위, 자동 완성, 로그 검색 |
| 못하는 것 | 잦은 단건 수정·삭제, 트랜잭션 | 여러 프로세스의 동시 쓰기 | 정확한 트랜잭션, 큰 조인, 주 저장소 역할 |
| 라이선스 | Apache 2.0 | MIT | AGPLv3·SSPL·ELv2 / Apache 2.0 |
먼저 질문이 "얼마나"(집계)인지 "어느 것"(검색과 순위)인지 가린다. 집계라면 데이터가 한 사람의 노트북에 들어오는지에 따라 DuckDB와 ClickHouse가 갈린다. 검색이라면 Elasticsearch·OpenSearch를 쓰되, 원본은 관계형이나 문서 데이터베이스에 두고 검색 엔진은 언제든 다시 색인할 수 있는 사본으로 다루는 것이 안전하다. 분석 엔진에 넣을 데이터를 OLTP 데이터베이스에서 옮겨 오는 일은 클라우드 데이터베이스 노트의 데이터 웨어하우스와 이어진다.
- ClickHouse Docs — ClickHouse
- MonetDB/X100: Hyper-Pipelining Query Execution — Boncz·Zukowski·Nes, CIDR 2005
- ClickHouse - Lightning Fast Analytics for Everyone — Schulze 외, VLDB 2024
- Architecture Overview — ClickHouse Docs
- MergeTree table engine — ClickHouse Docs
- A Practical Introduction to Primary Indexes — ClickHouse Docs
- Replicated table engines — ClickHouse Docs
- Why DuckDB — DuckDB
- DuckDB: an Embeddable Analytical Database — Raasveldt·Mühleisen, SIGMOD 2019
- Apache Lucene Core — Apache Software Foundation
- Text analysis — Elastic Docs
- Near real-time search — Elastic Docs
- Translog settings — Elastic Docs
- Clusters, nodes, and shards — Elastic Docs
- Similarity settings — Elastic Docs
- The Probabilistic Relevance Framework: BM25 and Beyond — Robertson·Zaragoza
- Upcoming licensing changes to Elasticsearch and Kibana — Elastic 블로그(2021.1)
- Introducing OpenSearch — AWS Open Source 블로그(2021.4)
- Elasticsearch is Open Source, Again — Elastic 블로그(2024.8)
- Linux Foundation Announces OpenSearch Software Foundation — Linux Foundation(2024.9)
- OpenSearch — OpenSearch Software Foundation