Qdrant 1.18.0은 2026년 5월 11일 공개됐다. 이 릴리스의 핵심은 새로운 양자화 방식 하나가 추가됐다는 데 있지 않다. 저장 벡터를 더 작게 만드는 TurboQuant, 컬렉션별 메모리 사용처를 보여 주는 memory report, 메모리가 임계점을 넘으면 쓰기를 거부하는 strict mode, OOM 재시작 루프에서 디스크 중심으로 기동하는 low-memory mode가 한 버전에 함께 들어왔다.
이 조합은 벡터 데이터베이스의 용량 문제를 세 단계로 나눈다.
- 정상 상태에서는 양자화로 working set을 줄인다.
- 압력이 커지면 관측 결과와 strict mode로 OOM 전에 쓰기를 제한한다.
- 이미 기동조차 어려우면 persisted 설정을 바꾸지 않고 low-memory mode로 복구한다.
다만 1.18.0을 그대로 운영 기준선으로 삼아서는 안 된다. 1.18.1은 TurboQuant heap memory 과소 보고를 수정했고, 1.18.2는 optimizer·snapshot transfer 문제와 REST 인증 우회 및 악성 snapshot 처리 취약점을 고쳤다. 따라서 이 글은 1.18.2 이상에서 검증한다는 전제로 설명한다.
왜 4배 압축과 8배 압축 사이가 운영 문제인가
float32 임베딩은 차원 하나에 4 byte를 쓴다. 1,536차원 벡터라면 원본 좌표만 포인트당 6 KiB다. 여기에 HNSW graph, payload, payload index, ID tracker, allocator overhead, replica가 더해진다. 컬렉션이 커질수록 “벡터는 disk에 있다”와 “검색할 working set이 RAM에 들어온다”는 전혀 다른 말이 된다.
기존 Qdrant의 주요 선택지는 다음과 같았다.
- Scalar Quantization(SQ): 좌표를 int8로 줄여 원본 float32 대비 4배 압축한다. 비교적 안정적인 첫 선택이지만 더 큰 절감이 필요하면 한계가 빨리 온다.
- Binary Quantization(BQ): 좌표당 1~2 bit까지 줄일 수 있어 빠르고 작다. 대신 embedding 분포와 차원에 따라 recall 편차가 크다.
- Product Quantization(PQ): 더 강하게 압축할 수 있지만 codebook 학습과 검색 품질·속도 비용을 감수한다.
TurboQuant는 이 사이에 4, 2, 1.5, 1 bit operating point를 둔다. 특히 기본값인 4-bit는 좌표 저장량만 보면 float32 대비 8배 압축이므로, SQ보다 절반 크기의 압축 벡터로 비슷한 recall을 노리는 위치다.
이것은 “무조건 SQ를 대체한다”는 뜻이 아니다. Qdrant가 공개한 benchmark에서도 dataset과 embedding model에 따라 TQ4가 SQ보다 높거나 낮았다. 공개 수치는 upstream이 m=16, ef_construct=128 등 특정 조건에서 측정한 결과다. 자신의 filter 비율, hnsw_ef, rescore, storage 계층, warm-cache 상태를 넣지 않은 채 capacity plan에 복사하면 안 된다.
TurboQuant의 검색 경로: 회전하고, 짧게 저장하고, 필요하면 원본으로 재정렬한다
1. 직교 회전으로 좌표별 편차를 섞는다
TurboQuant 논문은 입력 벡터에 random orthogonal rotation을 적용한 뒤 좌표별 scalar quantizer를 사용한다. Qdrant 구현에서는 빠른 Hadamard rotation을 쓴다. 직교 변환은 dot product와 L2 norm을 보존하므로, 회전을 역으로 풀지 않고도 회전된 공간에서 점수를 계산할 수 있다.
회전의 목적은 정보를 없애는 것이 아니라 특정 좌표에 몰린 분산을 여러 좌표에 고르게 섞는 것이다. 그래야 dataset마다 별도 codebook을 학습하지 않고, 표준 분포에 맞춰 미리 계산한 작은 Lloyd-Max codebook을 재사용할 수 있다.
2. Qdrant는 논문의 MSE 경로를 그대로 끝내지 않는다
실제 embedding은 모든 방향이 균일한 이상적 분포가 아니다. 일부 방향에 정보가 몰린 anisotropic embedding에서는 고정 codebook과 좌표 분포가 어긋날 수 있다. Qdrant는 segment를 양자화하기 전에 표본을 한 번 훑어 좌표별 (shift, scale)을 추정한다.
- 표본은 reservoir sampling으로 고른다.
- 좌표별 quantile은 constant-memory P-Square 방식으로 추정한다.
- 계산한 calibration은 segment metadata에 저장한다.
- query에는 역 scale과 shift 보정을 미리 접어 넣으므로 vector 하나를 비교하는 hot loop의 형태는 유지한다.
또한 MSE quantization이 벡터 길이를 체계적으로 줄이는 bias를 보정하기 위해, 원본 길이와 양자화 복원 길이의 비율을 per-vector scalar로 저장해 scoring 시 다시 곱한다. Qdrant 설명에 따르면 이미 L2 length를 위해 확보한 4 byte를 이 비율 저장에 사용한다.
이 때문에 “4 bit × dimension”만으로 실제 segment 크기를 정확히 계산할 수 없다. bit-packed code 외에 calibration metadata, per-vector scalar, alignment와 index 구조가 남는다.
3. SIMD가 압축 코드에서 직접 점수를 계산한다
4-bit와 2-bit 경로는 작은 codebook lookup table과 정수 multiply-add를 결합한다. x86에서는 pshufb, maddubs, VNNI 계열 명령을, ARM에서는 dot-product 명령을 활용하는 구조다. 1-bit 경로는 query를 bit-plane으로 바꾸고 bitwise operation과 popcount를 사용한다.
하지만 SIMD 지원이 곧 workload 전체의 latency 개선을 보장하지는 않는다. filter가 매우 선택적이거나 원본 vector rescore가 remote/on-disk read를 많이 일으키면 quantized scoring보다 다른 단계가 지배적일 수 있다. CPU architecture별 kernel과 storage cache 상태를 분리해 측정해야 한다.
bit 수는 압축률·recall·속도를 동시에 바꾼다
| 설정 | 좌표 bit 수 | float32 대비 이론적 위치 | 운영 판단 |
|---|---|---|---|
bits4 | 4 | 8x | 기본값. SQ보다 작은 working set을 원할 때 첫 평가 후보 |
bits2 | 2 | 16x | BQ 2-bit보다 recall을 우선하고 일부 속도를 양보할 때 |
bits1_5 | 1.5 | 약 21.3x | 16x보다 더 줄이되 1-bit가 너무 공격적일 때 |
bits1 | 1 | 32x | 극단적 압축. 반드시 실제 recall과 tail latency 검증 |
공식 자료 사이에는 bits1_5 표기가 일치하지 않는다. Qdrant quantization 문서는 24x라고 쓰지만, 구현 설명 글은 약 21x라고 쓴다. 단순히 32 / 1.5를 계산하면 약 21.3x다. 여기에 metadata와 padding까지 있으므로 어느 표기도 물리 사용량을 대신하지 못한다. 실제 collection memory report와 disk 사용량을 측정해 capacity plan을 고정해야 한다.
거리 함수 경계도 중요하다.
Cosine,Dot,Euclid(L2)는 회전 불변량과 SIMD scoring을 활용하는 first-class 경로다.Manhattan(L1)은 API상 지원되지만 비교마다 전체 vector를 복원해야 하므로 현저히 느리다. L1 workload라면 SQ와 직접 비교해야 한다.- TurboQuant는 stored vector만 압축하고 query는 더 높은 정밀도로 처리하는 asymmetric scoring을 자동 사용한다.
기존 컬렉션에는 PATCH가 곧 재인덱싱 작업이다
새 컬렉션에 4-bit TurboQuant를 적용하는 최소 형태는 다음과 같다.
PUT /collections/documents_tq
{
"vectors": {
"size": 1536,
"distance": "Cosine",
"on_disk": true
},
"quantization_config": {
"turbo": {
"bits": "bits4",
"always_ram": true
}
}
}always_ram: true는 원본 vector의 on_disk 설정과 별개로 quantized vector를 RAM에 두겠다는 뜻이다. 원본은 disk에 남기고 작은 TQ code만 RAM에 상주시킨 뒤, 후보 추출은 RAM에서 하고 필요한 후보만 원본으로 rescore하는 구성이 가능하다.
기존 컬렉션에도 PATCH로 설정할 수 있다.
PATCH /collections/documents
{
"quantization_config": {
"turbo": {
"bits": "bits4",
"always_ram": true
}
}
}그러나 이것을 가벼운 metadata 변경으로 취급하면 안 된다. 기존 segment에 quantized representation을 만들어야 하므로 optimizer가 CPU, memory, disk I/O를 사용한다. 대형 production collection에서 즉시 patch하기보다 다음 순서가 안전하다.
- production embedding 분포를 대표하는 별도 collection을 만든다.
- SQ/BQ/float32와 동일한 point·filter 조건으로 TQ4를 비교한다.
- optimizer 완료 시간과 temporary disk headroom을 측정한다.
- replica 또는 저위험 shard에서 rollout한다.
green상태뿐 아니라 pending optimizer, ingestion latency, search tail을 확인한다.- 문제가 생겼을 때 quantization 제거보다 snapshot/alias 기반으로 이전 collection으로 돌아가는 절차를 먼저 검증한다.
Query마다 rescore와 oversampling을 명시해 정확도와 I/O 비용을 조정할 수 있다.
POST /collections/documents_tq/points/search
{
"vector": [0.01, -0.02, 0.03],
"limit": 20,
"params": {
"quantization": {
"ignore": false,
"rescore": true,
"oversampling": 2.0
}
}
}oversampling: 2.0은 최종 20개를 위해 quantized path에서 더 많은 후보를 가져와 원본 점수로 재정렬하는 예시일 뿐, 보편적 기본값이 아니다. 원본 vector가 on-disk이고 cache hit가 낮으면 recall은 오르는 대신 p99와 read I/O가 크게 늘 수 있다.
Memory report는 heap과 page cache를 분리해서 읽어야 한다
1.18부터 다음 endpoint로 collection별 memory breakdown을 조회할 수 있다.
curl http://localhost:6333/collections/documents_tq/memory응답은 dense/multi-dense vector별 storage·index·quantization, sparse vector, payload, payload index, ID tracker를 나눠 보여 준다. 각 component에는 네 가지 관점이 있다.
| 필드 | 의미 | 운영 해석 |
|---|---|---|
disk | 파일의 물리 크기 | snapshot, replica, disk headroom 계산의 출발점 |
ram | mmap으로 대체할 수 없는 heap resident memory | OOM 위험과 직접 연결되지만 allocator overhead 전체는 아님 |
cached | OS page cache에 현재 resident한 mmap page | reclaim 가능한 warm working set |
expected_cache | 최적 성능을 위해 cache되길 기대하는 파일 크기 | cached와의 차이로 cold/warm 정도 추정 |
여기서 ram + cached를 “Qdrant가 절대 해제하지 못하는 메모리”로 해석하면 안 된다. cached는 OS가 회수할 수 있는 page cache이고, ram은 그렇지 않은 heap 쪽이다. 반대로 ram만 보고 node RSS를 예측해서도 안 된다. 공식 문서는 third-party library와 allocator 사용량이 빠지므로 RAM 추정치가 보통 10~15% 낮게 보고될 수 있다고 명시한다. non-Unix에서는 cached가 0으로 나온다.
따라서 dashboard에는 최소 세 계층이 필요하다.
- Qdrant process RSS와 container/cgroup memory limit
- collection memory report의 component별
ram,cached,expected_cache - host의 page fault, reclaim, swap, disk latency
1.18.1이 TurboQuant heap memory under-reporting을 고쳤다는 점도 중요하다. 1.18.0에서 얻은 양자화 전후 report를 기준선으로 보존했다면 1.18.1 이상에서 다시 측정해야 한다.
Strict mode는 OOM을 고치는 기능이 아니라 쓰기 admission control이다
Collection strict mode에 max_resident_memory_percent를 설정하면 process RSS가 total system memory 또는 cgroup limit의 지정 비율을 넘었을 때 memory-consuming update를 거부한다.
PATCH /collections/documents_tq
{
"strict_mode_config": {
"enabled": true,
"max_resident_memory_percent": 85,
"search_max_batchsize": 64
}
}85와 64는 설명용 값이다. 실제 임계값은 base RSS, optimizer peak, kernel/page cache, 동시 query fan-out, orchestrator eviction 조건을 포함해 정해야 한다.
이 guardrail의 경계는 다음과 같다.
- upsert와 payload update처럼 memory를 늘리는 operation을 막는다.
- delete는 계속 허용해 memory를 줄일 탈출 경로를 남긴다.
- external request와 internal replication traffic에 같은 판정을 적용해 replica divergence를 피한다.
- 측정값에는 짧은 cache가 있고 allocator가 page를 OS에 돌려주는 데도 시간이 걸리므로, delete 직후 바로 write가 재개된다고 가정하면 안 된다.
- threshold 도달은 이미 위험 구간에 들어왔다는 신호다. 지속적으로 reject가 난다면 batching, ingest rate, segment/optimizer 설정 또는 node sizing을 고쳐야 한다.
search_max_batchsize는 한 요청이 너무 많은 query를 묶어 다른 workload를 밀어내지 못하게 한다. 두 설정을 함께 두면 write-side memory growth와 read-side batch amplification을 각각 제한할 수 있다.
Client는 strict-mode reject를 무한 재시도해서는 안 된다. exponential backoff와 retry budget을 두고, memory pressure alert와 연결해야 한다. 그렇지 않으면 admission control이 retry storm으로 바뀐다.
Low-memory mode는 OOM crash loop에서만 쓰는 기동 스위치다
이미 node가 재시작할 때마다 mmap prefault와 RAM-resident component loading으로 죽는다면 strict mode는 실행될 기회조차 없다. 1.18은 storage.low_memory_mode를 추가했다.
storage:
low_memory_mode: no_populate세 모드의 차이는 다음과 같다.
| 모드 | load 동작 | 예상 대가 |
|---|---|---|
disabled | persisted 설정 그대로 load | 정상 기본 경로 |
no_resident | quantization을 always_ram=false, payload index를 on_disk=true, payload를 mmap 방식으로 가능한 만큼 load | query page fault와 disk 의존도 증가 |
no_populate | no_resident에 더해 vector, HNSW, payload mmap prefault를 건너뜀 | 가장 작은 startup pressure, 가장 차가운 초기 query |
중요한 장점은 이 설정이 persisted segment configuration을 수정하지 않는다는 점이다. 같은 data directory도 기동 옵션에 따라 다르게 load되므로 OOM recovery 후 원래 설정으로 돌아갈 수 있다.
반대로 이를 상시 memory optimization 옵션처럼 남겨 두면 위험하다. startup은 성공해도 첫 traffic에서 major page fault와 disk queue가 급증할 수 있다. 권장 recovery runbook은 다음과 같다.
- crash-loop node를 traffic과 automatic transfer 대상에서 격리한다.
no_populate로 기동해 collection과 shard 상태를 확인한다./memory, process RSS, page fault, disk latency를 수집한다.- 필요하면 ingestion을 중지하고 snapshot 또는 shard 이동으로 headroom을 만든다.
no_resident로 한 단계 복귀해 warm-up query를 제한적으로 흘린다.- 정상 설정에서 재기동할 수 있는지 확인한 뒤 traffic을 재개한다.
Low-memory mode는 데이터 손상 복구가 아니다. snapshot restore, optimizer cancellation 잔재, shard transfer 상태가 원인이라면 별도 정합성 검증이 필요하다.
Named vector 변경은 embedding migration의 재수집 폭을 줄인다
1.18은 기존 collection schema에 named vector를 추가하거나 제거할 수 있게 했다. 과거에는 새 embedding model을 도입하려면 collection을 다시 만들고 모든 point를 재적재하는 방식이 일반적이었다. 이제 다음과 같은 in-place migration이 가능하다.
- 기존
embedding_v1옆에embedding_v2vector field를 추가한다. - backfill worker가 point별
embedding_v2를 채운다. - 두 vector의 recall, latency, memory report를 병행 비교한다.
- read traffic을 새 vector로 전환한다.
- rollback window와 snapshot을 보존한 뒤 이전 vector field를 제거한다.
이 기능은 편리하지만 storage peak를 없애지는 않는다. 전환 기간에는 두 원본 vector, 두 index, 두 quantized representation이 공존할 수 있다. memory report가 vector name별로 분해되는 이유를 migration gate에 활용해야 한다. 삭제 전에는 “애플리케이션이 새 vector를 읽는다”뿐 아니라 모든 point의 backfill completeness와 replica별 index 완료를 확인해야 한다.
평가 실험은 recall만이 아니라 복구 가능성까지 포함해야 한다
검색 품질 matrix
같은 production sample로 최소 다음 조합을 비교한다.
- float32 exact search: ground truth
- 기존 SQ 또는 BQ 설정
- TQ4, TQ2
- 각 TQ 설정에서 rescore on/off
- 대표 oversampling 값
- filter 없음, low-selectivity filter, high-selectivity filter
측정 항목은 recall@k, p50/p95/p99 latency, CPU/query, read bytes, page fault, cache warm-up 시간이다. 평균 latency만 보면 cold restart와 tail regression을 놓친다.
Build·rollout matrix
- 새 collection build와 기존 collection
PATCH의 optimizer 시간 - quantization build 중 ingestion latency와 search latency
- temporary disk 증가량과 snapshot 크기
- replica별 optimizer 완료 편차
- restart 직후
cached / expected_cache회복 속도
Failure injection
- cgroup memory limit을 낮춰 strict mode가 update를 거부하는지 확인한다.
- delete 후 write 재개까지 걸리는 시간을 잰다.
- client retry budget이 reject storm을 만들지 않는지 본다.
no_populate로 기동한 뒤 cold-query p99와 disk saturation을 확인한다.- snapshot restore와 alias rollback을 실제로 수행한다.
운영 도입 체크리스트
버전과 보안
- [ ] 1.18.2 이상을 평가 기준선으로 사용했는가?
- [ ] 1.18.1의 TurboQuant memory reporting fix 이후 기준선을 다시 측정했는가?
- [ ] 1.18.2의 REST auth whitelist bypass와 malicious snapshot fix를 포함했는가?
- [ ] self-hosted upgrade를 intermediate minor별 최신 patch로 진행하는 절차가 있는가?
TurboQuant
- [ ] production embedding과 filter 분포로 SQ/BQ/TQ를 비교했는가?
- [ ]
bits4부터 시작하고 더 낮은 bit 수는 recall 근거가 있을 때만 선택했는가? - [ ] upstream benchmark 수치를 자신의 capacity plan으로 취급하지 않았는가?
- [ ] rescore·oversampling에 따른 원본 vector read와 p99를 측정했는가?
- [ ] L1 distance workload를 SQ와 별도 비교했는가?
- [ ] 기존 collection patch 중 optimizer CPU·memory·disk headroom을 확보했는가?
Memory guardrail
- [ ]
/collections/{name}/memory의ram과cached를 구분해 dashboard에 표시했는가? - [ ] report의 10~15% 과소 추정 가능성을 node headroom에 반영했는가?
- [ ]
max_resident_memory_percent가 cgroup limit 기준으로 실제 동작하는지 확인했는가? - [ ] write reject를 처리하는 retry budget과 alert가 있는가?
- [ ] batch search 상한이 정상 bulk workload를 불필요하게 막지 않는가?
Recovery와 migration
- [ ]
no_resident와no_populate기동 절차를 runbook으로 검증했는가? - [ ] low-memory 기동 후 cold-cache page fault와 disk saturation을 측정했는가?
- [ ] named vector 이중 운영 기간의 peak storage와 RAM을 계산했는가?
- [ ] old vector 삭제 전에 backfill completeness, replica 상태, rollback snapshot을 확인했는가?
- [ ] snapshot/restore와 alias rollback rehearsal를 완료했는가?
정리
Qdrant 1.18의 중요한 변화는 TurboQuant만 떼어 보면 절반만 보인다. Random rotation과 segment calibration으로 4~1 bit code를 만들고 SIMD로 후보를 좁히는 새 검색 경로는 working set을 줄인다. Collection memory report는 그 결과가 vector, index, payload, page cache 중 어디에서 나타났는지 보여 준다. Strict mode는 RSS가 한계를 넘을 때 memory-growing update를 막고, low-memory mode는 이미 OOM crash loop에 빠진 node를 persisted 설정 변경 없이 기동할 방법을 제공한다.
도입 판단도 이 세 층을 함께 봐야 한다. TQ4가 vendor benchmark에서 SQ와 비슷한 recall을 보였다는 이유만으로 production collection을 즉시 patch해서는 안 된다. 실제 embedding으로 recall과 tail latency를 측정하고, optimizer build peak와 원본 rescore I/O를 확인하며, memory reject와 cold restart까지 실패 주입해야 한다.
가장 안전한 시작점은 작다. 1.18.2 이상에서 대표 sample collection을 만들고, TQ4와 기존 양자화를 비교한 뒤, /memory로 물리 사용량을 검증한다. 그다음 strict mode와 low-memory recovery를 runbook으로 연결하고 나서야 압축률이 운영 여유로 바뀐다.
References
- Qdrant v1.18.0 release notes — GitHub, 2026-05-11
- Qdrant 1.18 release overview — Qdrant, 2026-05-11
- TurboQuant in Qdrant — implementation and benchmark details, 2026-05-13
- Quantization concepts and TurboQuant configuration — Qdrant documentation
- Collection memory usage — Qdrant documentation
- Qdrant v1.18.1 release notes — TurboQuant memory reporting fix, 2026-05-22
- Qdrant v1.18.2 release notes — optimizer, snapshot, and security fixes, 2026-06-04
- Qdrant v1.18.2 tagged source: low-memory loading modes
- Qdrant v1.18.2 tagged source: strict-mode memory semantics
- TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate — arXiv:2504.19874