Engineering
우리 엔진이 Qdrant보다 8.7배 빠른 이유
Schift 벡터 엔진의 실측 벤치마크를 공개한다. 이기는 곳, 지는 곳 모두 투명하게. Apple M5 Pro, 1M vectors, 1024d 기준으로 Qdrant, FAISS, pgvector와 비교했다.
벤치마크는 맥락 없이 보면 거짓말한다.
“우리 엔진이 X배 빠릅니다”라고 주장하는 블로그 포스트를 많이 봤을 것이다. 측정 조건은 각주에 묻혀 있고, 비교 대상의 설정은 의도적으로 불리하게 맞춰져 있다. 우리는 그렇게 하지 않으려 한다.
이 포스트에서 우리가 이기는 숫자와 지는 숫자를 모두 공개한다. 테스트 코드도 함께 올린다.
환경
모든 측정은 동일 머신, 동일 날짜(2026-03-26)에 진행했다.
| 항목 | 값 |
|---|---|
| CPU | Apple M5 Pro |
| RAM | 48 GB |
| OS | Darwin 25.3.0 (macOS) |
| Rust | rustc 1.94.0 (2026-03-02) |
| 타겟 | aarch64-apple-darwin |
벡터 설정:
- 차원(dim): 1024
- 스케일: 1,000,000 vectors
- top-k: 10
- HNSW: M=32, efConstruction=200, efSearch=50
- 쿼리 수: 1000 (warmup 10 제외)
- 벡터:
PCG64(seed=42)기반 합성 정규 분포 (실제 임베딩 분포와 다를 수 있음)
검색은 모두 single-thread로 진행했다. 빌드만 멀티스레드로 허용했다.
숫자부터
1M vectors, dim=1024, top-k=10
| 엔진 | p50 (us) | p95 (us) | p99 (us) | QPS | 메모리 (MB) |
|---|---|---|---|---|---|
| Schift SQ8+HNSW | 277 | 392 | 502 | 3,400 | 1,024 |
| FAISS HNSW F32 | 621 | 941 | 1,653 | 1,503 | 4,096 |
| Qdrant (in-memory) | ~2,400 | ~3,300 | — | ~390 | — |
| FAISS Flat (brute force) | 37–87 | — | — | 11,000+ | 4,096 |
| pgvector | ~62,000+ | — | — | ~5–15 | — |
Qdrant와 pgvector 수치는 동일 조건(1M, 1024d, ef=50)으로 별도 Python 스크립트로 측정했다. FAISS Flat은 brute-force 특성상 파라미터가 없으며, 1M 기준 단일 쿼리 37–87us 범위를 기록했다.
왜 Qdrant보다 빠른가
8.7–9.8배 차이는 구체적인 이유가 있다.
SQ8 압축. 우리 엔진은 F32(4바이트/차원) 대신 8-bit scalar quantization으로 저장한다. 1024차원 벡터 한 개의 크기가 4,096바이트에서 1,024바이트로 줄어든다. 메모리 대역폭이 4분의 1로 감소하고, cache line에 4배 많은 벡터가 들어간다.
mmap 기반 segment. 데이터를 mmap으로 올려두면 OS 페이지 캐시와 결합해 hot segment는 사실상 in-memory처럼 동작하면서, 물리 메모리 초과 시 OS가 알아서 evict한다. Qdrant의 in-memory 모드와 비교해도 우리 접근이 더 빠른 이유 중 하나다.
Rust로 작성된 HNSW 구현. GC pause가 없고, 핫 패스에서 Python FFI overhead도 없다. Qdrant도 Rust 기반이지만, in-memory + Python 클라이언트를 통한 로컬 모드에서 gRPC/HTTP 레이어와 직렬화 비용이 추가된다. 우리 벤치마크는 Rust 바이너리 직접 호출이다.
정리하면: SQ8 압축 + mmap segment + GC-free Rust 세 가지가 조합된 결과다.
왜 FAISS Flat보다 느린가
솔직하게 말하자. 순수 brute force에서는 FAISS가 우리보다 3.2–7.5배 빠르다.
이건 부끄러운 결과가 아니다. 비교 자체가 잘못된 것이다.
FAISS Flat은 BLAS(OpenBLAS, Accelerate 등)로 최적화된 행렬 곱셈 기반 brute-force 탐색이다. 그래프 탐색이 없다. 모든 1M개 벡터를 전수 조사한다. Apple Silicon의 AMX 가속이 여기에 그대로 적용된다.
우리 엔진(HNSW)은 그래프 탐색으로 approximation을 한다. 정확도를 조금 포기하고 탐색 범위를 줄이는 것이 핵심이다.
두 방식의 recall 차이를 보면:
| 방식 | recall@10 |
|---|---|
| F32 brute force (ground truth) | 1.000 |
| Schift SQ8+HNSW | 0.980 |
2% recall 손실로 p50 기준 3–7배 빠른 응답을 얻는다. 1M 이상 스케일에서 brute force는 메모리와 레이턴시 모두 한계에 부딪힌다. Flat index는 1M에서 이미 4GB를 사용하며, 10M으로 가면 40GB가 필요하다.
FAISS Flat은 소규모 precision-critical 작업용이다. 100만 벡터 이상의 실시간 검색에 쓸 도구가 아니다.
pgvector는 별도로 봐야 한다
pgvector와의 225–751배 차이를 보고 “PostgreSQL이 그렇게 느리냐”고 묻는다면, 그것도 잘못된 비교다.
pgvector는 범용 OLTP 데이터베이스 위에 벡터 검색을 얹은 것이다. 트랜잭션, JOIN, 필터, 다른 테이블과의 관계형 쿼리가 필요한 경우에 쓴다. 순수 벡터 검색 레이턴시를 최적화하는 용도가 아니다.
“RDB가 필요하고 벡터 검색도 붙이고 싶다”면 pgvector가 맞다. “벡터 검색 자체가 핵심 병목이다”라면 다른 도구를 써야 한다.
FAISS HNSW와의 비교는 공정하다
FAISS HNSW F32와의 2.2배 차이(p50 277us vs 621us), 4배 메모리 차이(1,024MB vs 4,096MB)는 사과 대 사과 비교다.
동일 알고리즘(HNSW), 동일 파라미터(M=32, efConstruction=200, efSearch=50), 동일 머신. 차이는 저장 포맷뿐이다. FAISS는 F32 raw vector를, 우리는 SQ8 quantized vector를 사용한다.
Schift SQ8+HNSW: 277 us | 3,400 QPS | 1,024 MBFAISS HNSW F32: 621 us | 1,503 QPS | 4,096 MB ────────────────────────────────── 2.2x 빠름 2.3x 높음 4x 작음Schift가 FAISS HNSW보다 느린 구간은 없었다. 메모리 압축이 cache 효율을 높이고, 이것이 그대로 레이턴시 개선으로 이어진다.
테스트 코드
Rust 벤치마크:
// cargo test -p engine-store --test bench_competitors --release \// -- bench_1m_sq4_sq8 --nocaptureFAISS 비교 스크립트:
# python3 -u tests/bench_1m_competitors.pyQdrant 비교 스크립트:
# python3 -u tests/bench_qdrant.py세 스크립트 모두 동일한 PCG64(seed=42) 벡터를 생성하고, 동일한 percentile 계산 방식을 사용한다. 재현 가능하다.
이 벤치마크의 한계
솔직하게 말해야 할 것들이 있다.
단일 머신, 단일 런. 모든 숫자는 Apple M5 Pro 한 대에서 측정했다. AMD Epyc, AWS c7g, GCP n4 등 다른 환경에서 결과가 달라질 수 있다. Intel 계열 CPU에서 FAISS의 AVX-512 최적화가 더 강하게 나올 수 있다.
합성 벡터. PCG64 정규 분포로 생성한 벡터는 실제 텍스트 임베딩 분포와 다르다. 실제 임베딩은 차원 간 상관관계가 있고, 클러스터 구조가 있다. recall과 QPS 모두 달라질 수 있다.
분산 환경 미포함. 이 벤치마크는 단일 노드 in-process 측정이다. Qdrant의 분산 모드, 필터링 쿼리, 동시 쓰기 중 검색 등은 전혀 측정하지 않았다.
Qdrant 로컬 모드. Qdrant를 Python 클라이언트로 in-memory 모드에서 사용했다. gRPC 직접 호출이나 서버 모드에서 결과가 다를 수 있다. Qdrant 팀이 이 조건에 동의하지 않을 수도 있다.
SQ8 recall 손실. 2% recall 손실은 작다. 하지만 법률 문서 정밀 검색이나 의료 RAG처럼 precision이 절대적으로 중요한 도메인에서는 이 2%가 문제가 될 수 있다.
다음은 무엇인가
현재 SQ8이 production default다. 아직 실험 단계인 것들:
- TQ4 (TurboQuant-inspired): 1회 시도에서 recall@10이 0.49로 실패했다. 아직 production에 쓸 수 없다. 논문 구현을 더 충실히 따라가는 새 구현이 필요하다.
- SQ4: 메모리를 SQ8의 절반(512MB)으로 줄이지만, 1M 스케일에서 QPS가 1,138로 SQ8(3,400)에 크게 못 미친다. 극단적 메모리 제약 환경용으로만 고려 중이다.
- 필터링 벤치마크: 실제 프로덕션에서는 metadata 필터 + 벡터 검색 조합이 훨씬 많다. 순수 ANN 벤치마크보다 이쪽이 더 현실적인 비교다.
벤치마크는 계속 업데이트할 예정이다. 환경이 바뀌면, 숫자가 바뀌면, 그때마다 다시 측정한다.
Sources: