포인트
포인트 (Points)
포인트(point)는 Qdrant가 다루는 중심 엔티티예요. 포인트 하나는 벡터와 선택적인 페이로드로 구성된 레코드예요.
모양은 이렇게 생겼어요:
// 간단한 포인트 예시
{
"id" : 129 ,
"vector" : [ 0.1 , 0.2 , 0.3 , 0.4 ],
"payload" : { "color" : "red" },
}
하나의 컬렉션으로 묶인 포인트들 사이에서 벡터 유사도에 기반해 검색할 수 있어요. 이 과정의 자세한 내용은 검색과 필터링 섹션에 설명돼 있어요.
이 섹션은 포인트를 어떻게 만들고 관리하는지 설명해요.
포인트 수정 연산은 모두 비동기적이며 2단계로 진행돼요. 첫 단계에서 연산이 Write-ahead-log에 기록돼요. 이 시점 이후로는 기계 전원이 꺼져도 서비스가 데이터를 잃지 않아요.
포인트 ID (Point IDs)
Qdrant는 포인트 식별자로 64-bit unsigned integer와 UUID 둘 다 지원해요.
UUID 문자열 표현 예시:
- 단순형(simple):
936DA01F9ABD4d9d80C702AF85C822A8 - 하이픈형(hyphenated):
550e8400-e29b-41d4-a716-446655440000 - urn형:
urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4
즉, 모든 요청에서 숫자 ID 대신 UUID 문자열을 쓸 수 있어요. 예:
PUT /collections/{collection_name}/points
{
"points": [
{
"id": "5c56c793-69f3-4fbf-87e6-c4bf54c28c26",
"payload": {"color": "red"},
"vector": [0.9, 0.1, 0.1]
}
]
}
from qdrant_client import QdrantClient , models
client = QdrantClient ( url = "http://localhost:6333" )
client . upsert (
collection_name = " {collection_name} " ,
points = [ models . PointStruct (
id = "5c56c793-69f3-4fbf-87e6-c4bf54c28c26" ,
payload = { "color" : "red" , },
vector = [ 0.9 , 0.1 , 0.1 ],
), ],
)
import { QdrantClient } from "@qdrant/js-client-rest" ;
const client = new QdrantClient ({ host : "localhost" , port : 6333 });
client . upsert ( "{collection_name}" , {
points : [ {
id : "5c56c793-69f3-4fbf-87e6-c4bf54c28c26" ,
payload : { color : "red" , },
vector : [ 0.9 , 0.1 , 0.1 ],
}, ],
});
use qdrant_client :: qdrant :: { PointStruct , UpsertPointsBuilder };
use qdrant_client :: Qdrant ;
let client = Qdrant :: from_url ( "http://localhost:6334" ). build () ? ;
client . upsert_points (
UpsertPointsBuilder :: new ( "{collection_name}" , vec! [
PointStruct :: new ( "5c56c793-69f3-4fbf-87e6-c4bf54c28c26" , vec! [ 0.9 , 0.1 , 0.1 ], [("color" , "Red" . into ())]),
]) . wait ( true ),
) . await ? ;
숫자 ID를 쓰는 다음 예시도 모두 가능해요:
PUT /collections/{collection_name}/points
{
"points": [
{ "id": 1, "payload": {"color": "red"}, "vector": [0.9, 0.1, 0.1] }
]
}
client . upsert (
collection_name = " {collection_name} " ,
points = [ models . PointStruct ( id = 1 , payload = { "color" : "red" , }, vector = [ 0.9 , 0.1 , 0.1 ], ), ],
)
client . upsert ( "{collection_name}" , { points : [ { id : 1 , payload : { color : "red" , }, vector : [ 0.9 , 0.1 , 0.1 ], }, ], });
client . upsert_points (
UpsertPointsBuilder :: new ( "{collection_name}" , vec! [
PointStruct :: new ( 1 , vec! [ 0.9 , 0.1 , 0.1 ], [("color" , "Red" . into ())]),
]) . wait ( true ),
) . await ? ;
UUID와 숫자 ID 모두 사용 가능하다는 뜻이에요.
벡터 (Vectors)
Qdrant의 각 포인트는 하나 이상의 벡터를 가질 수 있어요. 벡터는 Qdrant 아키텍처의 중심 구성 요소예요. Qdrant는 서로 다른 종류의 데이터 탐색과 검색을 제공하기 위해 여러 유형의 벡터에 의존해요.
지원되는 벡터 유형 목록:
| Dense Vectors | 대부분의 임베딩 모델이 생성하는 일반 벡터. 고정 길이를 가짐. |
| Sparse Vectors | 고정 길이가 없고 비-영(non-zero) 요소가 몇 개뿐인 벡터. 정확한 토큰 매칭과 협업 필터링 추천에 유용. |
| MultiVectors | 고정 길이지만 높이(variable height)가 변하는 숫자 행렬. 보통 ColBERT 같은 late interaction 모델에서 얻음. |
하나의 포인트에 여러 유형의 벡터를 붙일 수 있어요. Qdrant에서는 이를 Named Vectors라고 불러요.
벡터 유형이 어떻게 저장되고 최적화되는지에 대한 더 자세한 내용은 vectors 섹션에서 다뤄요.
포인트 업로드 (Upload Points)
성능을 최적화하기 위해 Qdrant는 포인트를 배치(batch)로 로드하는 것을 지원해요. 즉 한 번의 API 호출로 여러 포인트를 서비스에 넣을 수 있어요. 배칭은 네트워크 연결을 만드는 오버헤드를 최소화해 줘요.
Qdrant API는 배치를 만드는 두 가지 방식을 지원해요: record-oriented(레코드 지향) 과 column-oriented(컬럼 지향). 내부적으로 이 둘은 동등하며 단지 편의를 위해 제공돼요.
배치로 포인트 만들기:
PUT /collections/{collection_name}/points
{
"batch": {
"ids": [1, 2, 3],
"payloads": [ {"color": "red"}, {"color": "green"}, {"color": "blue"} ],
"vectors": [ [0.9, 0.1, 0.1], [0.1, 0.9, 0.1], [0.1, 0.1, 0.9] ]
}
}
client . upsert (
collection_name = " {collection_name} " ,
points = models . Batch (
ids = [ 1 , 2 , 3 ],
payloads = [ { "color" : "red" }, { "color" : "green" }, { "color" : "blue" }, ],
vectors = [ [ 0.9 , 0.1 , 0.1 ], [ 0.1 , 0.9 , 0.1 ], [ 0.1 , 0.1 , 0.9 ], ],
),
)
client . upsert ( "{collection_name}" , {
batch : {
ids : [ 1 , 2 , 3 ],
payloads : [{ color : "red" }, { color : "green" }, { color : "blue" }],
vectors : [ [ 0.9 , 0.1 , 0.1 ], [ 0.1 , 0.9 , 0.1 ], [ 0.1 , 0.1 , 0.9 ], ],
},
});
또는 레코드 지향의 동등한 형태:
PUT /collections/{collection_name}/points
{
"points": [
{ "id": 1, "payload": {"color": "red"}, "vector": [0.9, 0.1, 0.1] },
{ "id": 2, "payload": {"color": "green"}, "vector": [0.1, 0.9, 0.1] },
{ "id": 3, "payload": {"color": "blue"}, "vector": [0.1, 0.1, 0.9] }
]
}
client . upsert (
collection_name = " {collection_name} " ,
points = [
models . PointStruct ( id = 1 , payload = { "color" : "red" , }, vector = [ 0.9 , 0.1 , 0.1 ], ),
models . PointStruct ( id = 2 , payload = { "color" : "green" , }, vector = [ 0.1 , 0.9 , 0.1 ], ),
models . PointStruct ( id = 3 , payload = { "color" : "blue" , }, vector = [ 0.1 , 0.1 , 0.9 ], ),
],
)
client . upsert ( "{collection_name}" , {
points : [
{ id : 1 , payload : { color : "red" }, vector : [ 0.9 , 0.1 , 0.1 ], },
{ id : 2 , payload : { color : "green" }, vector : [ 0.1 , 0.9 , 0.1 ], },
{ id : 3 , payload : { color : "blue" }, vector : [ 0.1 , 0.1 , 0.9 ], },
],
});
use qdrant_client :: qdrant :: { PointStruct , UpsertPointsBuilder };
client . upsert_points (
UpsertPointsBuilder :: new ( "{collection_name}" , vec! [
PointStruct :: new ( 1 , vec! [ 0.9 , 0.1 , 0.1 ], [("color" , "red" . into ())]),
PointStruct :: new ( 2 , vec! [ 0.1 , 0.9 , 0.1 ], [("color" , "green" . into ())]),
PointStruct :: new ( 3 , vec! [ 0.1 , 0.1 , 0.9 ], [("color" , "blue" . into ())]),
]) . wait ( true ),
) . await ? ;
조건부 업데이트 (Upsert)
동시성 문제를 방지하기 위해 Qdrant는 조건부 업데이트(conditional updates)를 지원해요. 전형적인 시나리오를 보자면:
- 클라이언트 A가 포인트 P를 읽음.
- 클라이언트 B가 포인트 P를 읽음.
- 클라이언트 A가 포인트 P를 수정해서 Qdrant에 다시 씀.
- 클라이언트 B가 (낡은 데이터에 기반해) 포인트 P를 수정해서 다시 쓰면서, 의도치 않게 클라이언트 A의 변경을 덮어씀.
이런 상황을 막기 위해 클라이언트 B는 조건부 업데이트를 사용할 수 있어요. 이를 위해 페이로드에 version 같은 추가 필드를 넣고, 업데이트할 때마다 값을 증가시키면 돼요.
클라이언트 A가 수정된 포인트 P를 다시 쓸 때 version 필드가 자기가 처음 읽었던 값과 같다는 조건을 걸면 돼요. 이후 클라이언트 B가 변경을 다시 쓰려고 하면 (A가 version을 증가시켰으므로) 조건이 실패하고, Qdrant가 업데이트를 거부해서 우발적인 덮어쓰기를 막아요.
version 대신 애플리케이션이 타임스탬프(시계 동기화 전제)나 데이터 모델에 맞는 다른 단조 증가값을 쓸 수도 있어요.
포인트 조회 (Retrieve Points)
ID로 포인트를 조회하는 메서드가 있어요.
REST API (스키마):
POST /collections/{collection_name}/points
{ "ids": [0, 3, 100] }
client . retrieve ( collection_name = " {collection_name} " , ids = [ 0 , 3 , 100 ], )
client . retrieve ( "{collection_name}" , { ids : [ 0 , 3 , 100 ], });
use qdrant_client :: qdrant :: GetPointsBuilder ;
client . get_points ( GetPointsBuilder :: new ( "{collection_name}" , vec! [ 0. into (), 30. into (), 100. into ()], )) . await ? ;
import static io.qdrant.client.PointIdFactory.id ;
import java.util.List ;
client . retrieveAsync ( "{collection_name}" , List . of ( id ( 0 ), id ( 30 ), id ( 100 )), false , false , null ) . get ();
using Qdrant.Client ;
var client = new QdrantClient ( "localhost" , 6334 );
await client . RetrieveAsync (
collectionName : "{collection_name}" ,
ids : [ 0 , 30 , 100 ],
withPayload : false ,
withVectors : false );
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client , err := qdrant . NewClient ( & qdrant . Config { Host : "localhost" , Port : 6334 , })
client . Get ( context . Background (), & qdrant . GetPoints {
CollectionName : "{collection_name}" ,
Ids : [] * qdrant . PointId { qdrant . NewIDNum ( 0 ), qdrant . NewIDNum ( 3 ), qdrant . NewIDNum ( 100 ), },
})
이 메서드에는 with_vectors와 with_payload라는 추가 파라미터가 있어요. 이 파라미터를 사용해 결과로 받고 싶은 포인트의 일부를 선택할 수 있어요. 불필요한 필드를 제외하면 전송되는 데이터 양이 줄어들어요.
단일 포인트도 API로 조회할 수 있어요:
REST API (스키마):
GET /collections/{collection_name}/points/{point_id}
포인트 스크롤 (Scroll Points)
가끔은 ID를 모른 채 저장된 모든 포인트를 가져와야 하거나, 필터에 해당하는 포인트들을 반복해서 순회해야 할 수 있어요.
REST API (스키마):
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{ "key": "color", "match": { "value": "red" } }
]
},
"limit": 1,
"with_payload": true,
"with_vector": false
}
client . scroll (
collection_name = " {collection_name} " ,
scroll_filter = models . Filter (
must = [ models . FieldCondition ( key = "color" , match = models . MatchValue ( value = "red" )), ]
),
limit = 1 ,
with_payload = True ,
with_vectors = False ,
)
scroll API는 포인트 ID를 모른 채 조건에 맞는 포인트들을 순차적으로 가져올 때 유용해요. 커서 방식으로 페이지네이션을 지원하므로, 대규모 데이터셋을 배치로 순회할 수 있어요.