
pgvector vs ClickHouse, 1,000만 건을 넣었더니 승부가 났다
요약
pgvector의 HNSW 인덱스가 메모리 한계로 인해 대규모 데이터 처리 시 성능 저하를 겪는 문제를 분석하고, 대안으로 ClickHouse를 활용한 사례를 소개합니다. 대량의 벡터 데이터를 다룰 때 발생하는 RAM 부족 문제와 OLAP 데이터베이스인 ClickHouse의 벡터 검색 기능 활용 방안을 다룹니다.
핵심 포인트
- pgvector의 HNSW 인덱스는 메모리 용량에 따라 쿼리 속도가 급격히 저하됨
- 1,000만 건 규모의 벡터 데이터 처리 시 pgvector는 디스크 I/O 폴백 발생 가능성 높음
- ClickHouse는 OLAP DB이지만 최근 벡터 검색 기능이 GA 단계로 발전함
- Anthropic과 OpenAI는 대규모 옵저버빌리티를 위해 ClickHouse를 채택함
「pgvector로 충분하잖아. Postgres에 그대로 넣을 수 있고, 설정도 5분이면 끝나니까.」
반년 전의 나는 그렇게 생각했다. 실제로 소규모 사내 RAG 시스템을 pgvector로 운영하고 있었고, 나름대로 잘 작동하고 있었다.
막다른 길에 다다른 것은 문서 수가 수백만 건을 넘어섰을 무렵이었다.
HNSW 인덱스가 메모리에 다 담기지 않게 된 것이다. RAG의 응답 시간(Response Time)이 초 단위로 늘어났고, 사내에서 클레임이 들어왔다. 스케일 업(Scale-up)을 하려 해도, pgvector의 HNSW 인덱스는 분산할 수 없다. 수직 확장(Vertical Scale)의 한계에 부딪혔다.
그래서 갈아탄 것이 ClickHouse다.
이 기사는 pgvector에서 ClickHouse로의 이행 기록과, Anthropic이나 OpenAI가 「왜 페타바이트 규모에서 ClickHouse를 선택했는지」를 실감할 수 있는 이야기다.
미리 말해두자면, ClickHouse는 원래 벡터 DB가 아니다. OLAP 데이터베이스다. 대량의 구조화된 데이터를 초고속으로 집계하기 위해 만들어졌다.
그것이 왜 AI/RAG 문맥에서 거론되기 시작했느냐 하면, 이유는 두 가지가 있다.
ClickHouse의 벡터 검색 기능의 변천
| 버전 | 사건 |
|---|---|
| 22.9 | 실험적 벡터 인덱스 첫 등장 |
| ... | |
| 25.8 | GA. 바이너리 양자화(Binary Quantization)·인덱스 전용 읽기 대응 |
2025년 8월(v25.8)까지 ClickHouse의 벡터 검색은 실험적 기능이었다. 지금은 프로덕션에 투입할 수 있는 상태가 되었다. 또한 25.8에서는 상관 서브쿼리(Correlated Subquery)에도 대응하여, PostgreSQL로부터의 이행 비용도 대폭 낮아졌다.
Anthropic: AI 시대의 옵저버빌리티(Observability)를 스케일링하기 위해 ClickHouse 채택 -
OpenAI: 페타바이트 규모의 옵저버빌리티를 위해 ClickHouse 선택
두 회사 모두 벡터 DB를 위해 사용하는 것이 아니라, 「방대한 로그·메트릭스를 실시간으로 집계·분석하기」 위해 ClickHouse를 선택하고 있다. 이 점이 본질이다.
이 부분은 나의 실측이 아니라, 공개되어 있는 데이터를 솔직하게 나열한다.
1536차원의 벡터(OpenAI text-embedding-3-small)를 사용했을 경우의 실측 데이터가 GitHub의 Issue나 기술 기사에 남아 있다.
| 건수 | HNSW 인덱스 크기 | 필요 RAM (빌드 시) | 쿼리 속도 |
|---|---|---|---|
| 100만 건 | ~1.5 GB | 서브 밀리초 (RAM 내) | |
| 500만 건 | 8~16 GB | 지연 증가 (RAM 초과 시작) | |
| 1000만 건 | 80~120 GB | 그 이상 | 수 초~타임아웃 |
pgvector의 GitHub Issue #843에 따르면: 16GB RAM 머신에서 500만 건의 HNSW 인덱스 구축을 시도했을 때 OOM(Out of Memory)이 발생했다. 또한 실제 PostgreSQL 로그에는 다음과 같이 기록되었다.
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 5,908,085 tuples
DETAIL: Building will take significantly more time.
HINT: Increase maintenance_work_mem to speed up builds.
HNSW 인덱스가 RAM을 초과하면 어떤 일이 벌어지는가. DEV Community의 검증 기사에 따르면, 서브 밀리초였던 쿼리가 수 초 단위가 된다. 디스크 I/O로 폴백(Fallback)하기 때문이다. 튜닝으로 해결될 문제가 아니라, 용량의 문제다.
ClickBench 벤치마크(1억 행의 Web Analytics 데이터, 43개 쿼리) 결과: 동일 하드웨어에서, ClickHouse는 PostgreSQL보다 10~100배 빠르다.
또한 개인의 실측 기사(fiveonefour.com)에서는, 1000만 행의 데이터에 대해 첫 번째 쿼리가 4,191ms 걸렸으나, 스키마 설계를 올바르게 수정한 것만으로 75ms까지 개선되었다(50배 이상).
단, ClickHouse가 빠른 것은 OLAP(집계·분석) 쿼리의 이야기다. 포인트 룩업(Point Lookup, 기본 키로 1건 조회)은 PostgreSQL이 더 빠르다. 용도를 잘못 선택하지 말 것.
| 항목 | pgvector | ClickHouse |
|---|---|---|
| 종류 | PostgreSQL 확장 | OLAP 데이터베이스 |
| ... | ||
| ClickHouse는 스키마 설계가 핵심이다. 처음에 잘못 설계하면 나중에 막다른 길에 다다르게 된다. |
CREATE TABLE documents
(
id UUID DEFAULT generateUUIDv4(),
...
ORDER BY
에는 벡터 컬럼을 넣어서는 안 된다. 파티션 키(Partition Key)나 날짜, 카테고리 등 메타데이터를 넣는다. 이것이 ClickHouse 벡터 검색 설계의 기본이다.
import clickhouse_connect
import openai
from typing import List
...
ClickHouse의 HNSW 인덱스가 유효하게 작동하는 것은 ORDER BY <거리 함수>(...) LIMIT N
형태뿐이다. WHERE cosineDistance(...) < 0.3
과 같이 작성하면 전체 스캔(Full Scan)이 발생하여 인덱스를 사용하지 못한다.
벡터 검색과 집계를 결합해야 하는 경우에는 서브쿼리(Subquery)를 사용하여 2단계로 나눈다.
def search_similar(query: str, top_k: int = 5, category: str = None) -> List[dict]:
query_vec = embed([query])[0]
vec_str = "[" + ", ".join(map(str, query_vec)) + "]"
...
벡터 검색 결과로 카테고리 집계도 하고 싶은 경우 (2단계 버전)
-- Step1: HNSW로 후보 ID 필터링
WITH (
SELECT groupArray(id)
...
import google.generativeai as genai
genai.configure(api_key="YOUR_KEY")
model = genai.GenerativeModel("gemini-1.5-flash")
...
v25.8에서 Annoy와 uSearch 인덱스가 삭제되었다. 기존 테이블에 이러한 인덱스가 남아 있으면 업그레이드 자체가 차단된다.
Error: Table uses removed vector index type 'annoy'.
Drop the index before upgrading.
v25.8로 업그레이드하기 전에 반드시 확인해야 한다. 다운그레이드는 지원되지 않으므로, 운영 환경에 적용하기 전에 포크(Fork)한 서비스에서 동작 검증을 권장한다.
-- 기존의 annoy/usearch 인덱스 확인
SELECT table, name, type
FROM system.data_skipping_indices
...
1M건 × 1536차원 (text-embedding-3-small)에서 HNSW 인덱스를 생성하려고 할 때, 공식 문서의 계산식에 따른 필요 RAM은 다음과 같다.
mv (벡터 본체) = 1,000,000 × 1536 × 2 (BFloat16) = 3,072 MB
mg (그래프) = 1,000,000 × 64 × 4 × 2 = 512 MB
합계 ≈ 3.5 GB
인덱스만으로 상시 3.5 GB를 소비한다. 다른 쿼리용 메모리가 압박되어 쿼리가 실패할 수 있다. 양자화 (Quantization, b1: 바이너리)를 통해 메모리를 약 4분의 1로 줄일 수 있지만, 정확도와의 트레이드오프(Trade-off)가 존재한다.
-- b1 (바이너리 양자화)로 메모리 대폭 절감
INDEX embedding_idx embedding
TYPE vector_similarity('hnsw', 'cosineDistance', 1536, 'b1', 64, 512)
대량의 데이터를 한꺼번에 넣었더니 INSERT가 비정상적으로 느려졌다. 원인은 머지(Merge) 시에 HNSW 인덱스가 매번 재구축되기 때문이다.
-- 대량 삽입 전에 Merge 중단
SYSTEM STOP MERGES documents;
-- 모든 데이터 투입
...
| 케이스 | 권장 |
|---|---|
| 문서 수가 100만 건 이하 | pgvector (과도한 스펙 불필요) |
| ... |
pgvector에서 ClickHouse로의 이전을 검토하는 기준은 **"벡터 검색을 다른 분석 쿼리와 결합하고 싶을 때" 또는 "데이터가 수백만 건을 넘어설 때"**이다.
Anthropic과 OpenAI가 ClickHouse를 선택한 것은 벡터 검색만을 위해서가 아니다.
**"방대한 이벤트 로그 × 메타데이터 × 벡터 검색을 하나의 DB에서 폭발적인 속도로 처리할 수 있다"**는 특성이 AI 시스템의 옵저버빌리티 (Observability) 및 데이터 기반에 완벽하게 부합했기 때문이다.
개인 개발이나 중소규모라면 처음에는 pgvector로 충분하다. 다만, 스케일링의 벽에 부딪혔을 때, 혹은 벡터 검색을 다른 분석 (Analytics)과 결합하고 싶을 때는 ClickHouse로의 이전을 진지하게 검토할 가치가 있다.
v25.8에서 벡터 검색이 GA (General Availability)가 된 지금, "ClickHouse는 OLAP 전용"이라는 인식은 이제 구식이다.
- ClickHouse 벡터 검색 문서
- Vector Search with ClickHouse - Part 1
- pgvector GitHub Issue #843: 5M건에서 OOM (Out of Memory)
- pgvector GitHub Issue #844: HNSW 그래프가 메모리에 올라가지 않게 된 로그
- Scaling pgvector: Memory, Quantization, and Index Build Strategies
- PostgreSQL vs ClickHouse: What I Learned From My First Database Benchmark
- Anthropic이 ClickHouse를 사용하여 AI 시대의 옵저버빌리티 (Observability)를 스케일링하는 방법
- 왜 OpenAI는 페타바이트 규모의 옵저버빌리티 (Observability)에 ClickHouse를 선택했는가
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기