저장
저장 (Storage)
여러분, Qdrant가 데이터를 어떻게 저장하는지 궁금하지 않으세요? 한 컬렉션 안의 모든 데이터는 **세그먼트(segment)**로 나뉘어 저장돼요. 각 세그먼트는 독립적인 벡터 저장소, payload 저장소, 그리고 인덱스를 가져요.
세그먼트에 저장된 데이터는 보통 서로 겹치지 않아요. 하지만 같은 point를 다른 세그먼트에 저장해도 문제가 되지 않는데, 검색에 중복 제거(deduplication) 메커니즘이 있기 때문이에요.
세그먼트는 벡터 저장소와 payload 저장소, 벡터·payload 인덱스, 그리고 내부 id와 외부 id 사이의 관계를 저장하는 id 매퍼(id mapper)로 구성돼요.
세그먼트는 사용하는 저장소·인덱스의 종류에 따라 appendable(추가 가능) 또는 non-appendable(추가 불가) 상태가 될 수 있어요. appendable 세그먼트에서는 데이터를 자유롭게 추가·삭제·조회할 수 있어요. non-appendable 세그먼트에서는 읽기와 삭제만 가능해요.
컬렉션 안의 세그먼트 설정은 서로 다르고 독립적일 수 있지만, 컬렉션에는 반드시 appendable 세그먼트가 하나 이상 있어야 해요.
벡터 저장소 (Vector storage)
Qdrant는 항상 벡터를 디스크의 메모리 매핑 파일(memory-mapped file)에 저장해요. 애플리케이션 요구사항에 따라 메모리 티어(memory tier)를 설정해서 더 빠른 접근을 위해 벡터를 RAM에도 로드할지 제어할 수 있어요:
cached— Qdrant가 시작 시 파일을 디스크 캐시에 미리 로드해서 첫 요청이 빨라요. 이게 기본값이며, 벡터를 담을 수 있을 만큼의 충분한 RAM이 필요해요.cold— Qdrant가 파일을 RAM에 미리 로드하지 않아요. 첫 요청은 디스크에서 읽으므로 더 느릴 수 있지만, 운영체제가 페이지에 접근할 때 그 페이지를 캐시해요.
Memmap 저장소 설정하기
벡터를 cold 티어로 옮기는 방법은 두 가지가 있어요:
1. 컬렉션 생성 API에서 벡터의 memory 옵션 설정하기:
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine",
"memory": "cold"
}
}
from qdrant_client import QdrantClient, models
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(
size=768,
distance=models.Distance.COSINE,
memory=models.Memory.COLD,
),
)
import { QdrantClient } from "@qdrant/js-client-rest";
client.createCollection("{collection_name}", {
vectors: { size: 768, distance: "Cosine", memory: "cold" },
});
이렇게 하면 모든 벡터가 즉시 cold 티어에 있는 컬렉션이 만들어져요. Qdrant 인스턴스가 빠른 디스크로 운영되고 큰 컬렉션을 다룰 때 이 방식이 권장돼요.
2. memmap_threshold 옵션 설정하기. 이 옵션은 세그먼트가 memmap 저장소로 전환되는 임계값을 설정해요. 두 가지 방법이 있어요:
- 설정 파일에서 전역으로 임계값을 설정할 수 있어요. 매개변수 이름은
memmap_threshold(이전에는memmap_threshold_kb)예요. - 각 컬렉션별로 생성 또는 업데이트 시 임계값을 설정할 수 있어요.
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine"
},
"optimizers_config": {
"indexing_threshold": 20000
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
optimizers_config=models.OptimizersConfigDiff(indexing_threshold=20000),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: { size: 768, distance: "Cosine" },
optimizers_config: { indexing_threshold: 20000 },
});
memmap 임계값 매개변수를 정하는 간단한 규칙이 있어요:
- 균형 잡힌 사용 시나리오라면 memmap 임계값을
indexing_threshold(기본값 10000)와 같게 설정하세요. 이 경우 옵티마이저가 불필요한 추가 실행을 하지 않고 모든 임계값을 한 번에 최적화해요. - 높은 쓰기 부하와 낮은 RAM이라면 memmap 임계값을
indexing_threshold보다 낮게(예: 5000) 설정하세요. 이 경우 옵티마이저가 먼저 세그먼트를 memmap 저장소로 변환하고 그 후에 인덱싱을 적용해요.
memmap_threshold는 명시적 메모리 티어가 설정되지 않은 벡터의 기본 배치만 결정해요. 벡터에 memory(또는 더 이상 쓰지 않는 on_disk)를 명시적으로 설정하면, 세그먼트 크기와 관계없이 그 설정이 항상 임계값보다 우선해요.
또한 벡터뿐 아니라 HNSW 인덱스용 메모리 티어도 설정할 수 있어요. 예를 들어 cold 티어로 옮기려면 컬렉션 생성이나 업데이트 시 hnsw_config.memory 매개변수를 cold로 설정하면 돼요.
PUT /collections/{collection_name}
{
"vectors": {
"size": 768,
"distance": "Cosine",
"memory": "cold"
},
"hnsw_config": {
"memory": "cold"
}
}
from qdrant_client import QdrantClient, models
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(
size=768,
distance=models.Distance.COSINE,
memory=models.Memory.COLD,
),
hnsw_config=models.HnswConfigDiff(memory=models.Memory.COLD),
)
import { QdrantClient } from "@qdrant/js-client-rest";
client.createCollection("{collection_name}", {
vectors: { size: 768, distance: "Cosine", memory: "cold" },
hnsw_config: { memory: "cold" },
});
Payload 저장소 (Payload storage)
Qdrant는 payload에 대해 두 가지 메모리 티어를 지원해요: cached와 cold(기본값). 티어와 관계없이 영속성에는 디스크와 Gridstore를 사용해요.
cached payload 저장소는 cached 벡터와 같은 방식으로 구성돼요. Qdrant가 시작 시 디스크 캐시에 미리 로드하므로 접근이 빨라요. 특히 payload에 큰 값(텍스트 요약이나 이미지 등)이 붙어 있다면, 모든 데이터를 RAM에 워밍 상태로 유지하려면 많은 공간이 필요할 수 있어요.
큰 payload 값의 경우에는 cold 티어가 더 나을 수 있어요. 이 티어는 사전 워밍 없이 payload를 Gridstore에 직접 읽고 씁니다. 그래서 저장에 상당한 RAM을 필요로 하지 않아요. 단점은 접근 지연(latency)이에요. payload 기반 조건으로 벡터를 조회해야 한다면 — 디스크에 저장된 값을 확인하는 것이 너무 오래 걸릴 수 있어요. 이런 시나리오에서는 필터링 조건에 쓰이는 각 필드에 payload 인덱스를 만들어 디스크 접근을 피하는 것을 권장해요. 필드 인덱스를 만들면 Qdrant는 payload 저장소 타입과 관계없이 인덱스된 필드의 모든 값을 RAM에 보존해요.
원하는 메모리 티어는 설정 파일 또는 컬렉션 생성 시 payload.memory 컬렉션 매개변수로 지정할 수 있어요.
버전 관리 (Versioning)
데이터 무결성을 보장하기 위해 Qdrant는 모든 데이터 변경을 2단계로 수행해요. 첫 단계에서 데이터는 **Write-Ahead-Log(WAL)**에 기록되는데, WAL이 모든 연산을 순서대로 정렬하고 연속 번호(sequential number)를 부여해요.
변경 사항이 WAL에 추가되면 전원 손실이 발생해도 데이터가 유실되지 않아요. 그런 다음 변경 사항이 세그먼트로 이동해요. 각 세그먼트는 자기에게 적용된 마지막 변경 버전과 각 개별 point의 버전을 저장해요. 새 변경의 연속 번호가 point의 현재 버전보다 작으면, 업데이터는 그 변경을 무시해요. 이 메커니즘 덕분에 Qdrant는 비정상 종료 후에도 WAL에서 저장소를 안전하고 효율적으로 복원할 수 있어요.