일관성 보장
일관성 보장 (Consistency Guarantees)
여러분, 분산 시스템에서 일관성(consistency)은 언제나 다루기 까다로운 주제예요. 기본적으로 Qdrant는 가용성(availability)과 최대 검색 처리량에 초점을 맞춰요. 이는 대부분의 사용 사례에서 선호되는 trade-off예요. 정상 운영 중에는 클러스터의 어느 피어(peer)에서든 데이터를 검색하고 수정할 수 있어요. 읽기는 지연과 가용성을 최적화하기 위해 부분 fan-out 전략을 쓰고, 쓰기는 모든 활성 샤드 레플리카에서 병렬로 실행돼요.
이 말은 한 point에 대한 동시 업데이트가 불일치 상태를 만들 수 있다는 뜻이에요. 예를 들어 샤드당 3개의 레플리카가 있는 컬렉션에서 두 클라이언트가 동시에 같은 point를 업데이트한다고 해볼게요. 어떤 레플리카에서는 그 point가 한 클라이언트의 업데이트를 반영하고, 다른 레플리카에서는 다른 클라이언트의 업데이트를 반영할 수 있어요.
경우에 따라서는 하드웨어 불안정, 동일 문서에 대한 대량 동시 업데이트 등이 발생할 때 추가적인 보장이 필요해요.
Qdrant는 일관성 보장을 제어하기 위한 몇 가지 옵션을 제공해요:
write_consistency_factor— 클라이언트에게 응답하기 전에 쓰기 연산을 승인(acknowledge)해야 하는 레플리카 수를 정의해요. 이 값을 늘리면 쓰기 연산이 클러스터의 네트워크 파티션에 강건해지지만, 쓰기 연산을 수행하려면 더 많은 활성 레플리카가 필요해져요.- 읽기
consistency매개변수 — 검색·조회 연산과 함께 사용해 모든 레플리카에서 얻은 결과가 동일함을 보장할 수 있어요. 이 옵션을 쓰면 Qdrant가 여러 레플리카에서 읽기 연산을 수행하고 선택한 전략에 따라 결과를 해석해요. 동일 문서의 동시 업데이트 시 데이터 불일치를 피하는 데 유용해요. 업데이트 연산이 빈번하고 레플리카 수가 적을 때 선호돼요. - 쓰기
ordering매개변수 — 업데이트·삭제 연산과 함께 사용해 모든 레플리카에서 그 연산이 같은 순서로 실행됨을 보장할 수 있어요. 이 옵션을 쓰면 Qdrant가 연산을 샤드의 리더 레플리카로 라우팅하고, 클라이언트에게 응답하기 전에 응답을 기다려요. 동일 문서의 동시 업데이트 시 데이터 불일치를 피하는 데 유용해요. 읽기 연산이 업데이트보다 빈번하고 검색 성능이 중요할 때 선호돼요.
쓰기 일관성 계수 (Write Consistency Factor)
write_consistency_factor는 클라이언트에게 응답하기 전에 쓰기 연산을 승인해야 하는 레플리카 수를 나타내요. 기본값은 1이에요. 컬렉션 생성 시 또는 컬렉션 매개변수 업데이트 시 설정할 수 있어요.
이 값은 1부터 각 샤드의 레플리카 수까지의 범위를 가질 수 있어요.
PUT /collections/{collection_name}
{
"vectors": {
"size": 300,
"distance": "Cosine"
},
"shard_number": 6,
"replication_factor": 2,
"write_consistency_factor": 2
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
shard_number=6,
replication_factor=2,
write_consistency_factor=2,
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
vectors: { size: 300, distance: "Cosine" },
shard_number: 6,
replication_factor: 2,
write_consistency_factor: 2,
});
활성 레플리카 수가 write_consistency_factor보다 적으면 쓰기 연산은 실패해요. 이 경우 클라이언트는 일관된 상태에 도달하도록 연산을 다시 보내야 해요.
write_consistency_factor를 낮은 값으로 설정하면 응답하지 않는 노드가 있어도 쓰기를 수용할 수 있어요. 응답하지 않는 노드는 죽은 것으로 표시되고, 데이터 일관성을 보장하기 위해 다시 사용 가능해지면 자동으로 복구돼요.
write_consistency_factor 설정은 재시작, 업그레이드, 장애로 일부 노드가 오프라인이 될 때 클러스터의 동작을 조정하는 데 중요해요.
기본적으로 클러스터는 각 샤드의 레플리카가 하나 이상 온라인인 동안 업데이트를 계속 수용해요. 하지만 이 동작은 오프라인 레플리카가 복구되면 클러스터의 나머지와 추가 동기화가 필요함을 의미해요. 어떤 경우에는 이 동기화가 리소스 집약적이고 바람직하지 않을 수 있어요.
write_consistency_factor를 복제 계수(replication factor)와 같게 설정하면, 복제되지 않은 업데이트가 거부되도록 클러스터 동작이 바뀌어 추가 동기화를 방지해요.
업데이트가 write_consistency_factor에 따라 충분한 레플리카에 적용되면 성공 상태를 반환해요. 업데이트를 적용하지 못한 레플리카는 일시적으로 비활성화되고, 데이터 일관성을 유지하기 위해 자동 복구돼요. 충분한 레플리카에 적용하지 못했다면 오류를 반환하고 부분 적용될 수 있어요. 데이터 일관성을 보장하려면 사용자가 연산을 다시 제출해야 해요.
비동기 업데이트와 오류·재시도를 처리할 수 있는 주입 파이프라인의 경우 이 전략이 더 선호될 수 있어요.
읽기 일관성 (Read Consistency)
읽기 consistency는 대부분의 읽기 요청에서 지정할 수 있고, 반환된 결과가 클러스터 노드 간 일관되도록 보장해요.
all— 모든 노드를 조회하고, 그 모두에 존재하는 point를 반환해요.majority— 모든 노드를 조회하고, 그 대부분에 존재하는 point를 반환해요.quorum— 무작위로 선택된 과반수 노드를 조회하고, 그 모두에 존재하는 point를 반환해요.1/2/3등 — 지정된 수의 무작위 선택 노드를 조회하고, 그 모두에 존재하는 point를 반환해요.- 기본
consistency는1이에요.
POST /collections/{collection_name}/points/query?consistency=majority
{
"query": [0.2, 0.1, 0.9, 0.7],
"filter": {
"must": [
{ "key": "city", "match": { "value": "London" } }
]
},
"params": { "hnsw_ef": 128, "exact": false },
"limit": 3
}
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
query_filter=models.Filter(
must=[
models.FieldCondition(
key="city",
match=models.MatchValue(value="London"),
)
]
),
search_params=models.SearchParams(hnsw_ef=128, exact=False),
limit=3,
consistency="majority",
)
client.query("{collection_name}", {
query: [0.2, 0.1, 0.9, 0.7],
filter: { must: [{ key: "city", match: { value: "London" } }] },
params: { hnsw_ef: 128, exact: false },
limit: 3,
consistency: "majority",
});
쓰기 순서 (Write Ordering)
동일 point에 대한 동시 업데이트 순서가 문제가 될 수 있어요. 쓰기 ordering을 사용하면 모든 레플리카에서 업데이트·삭제 연산이 같은 순서로 실행됨을 보장할 수 있어요. 이 옵션을 쓰면 Qdrant가 연산을 샤드의 리더 레플리카로 라우팅하고, 클라이언트에게 응답하기 전에 응답을 기다려요.
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(id=1, vector=[0.9, 0.1, 0.1], payload={"color": "red"}),
models.PointStruct(id=2, vector=[0.1, 0.9, 0.1], payload={"color": "green"}),
models.PointStruct(id=3, vector=[0.1, 0.1, 0.9], payload={"color": "blue"}),
],
ordering=models.WriteOrdering(type=models.WriteOrderingType.STRONG),
)
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],
],
},
ordering: "strong",
});
ordering 옵션을 쓰면 모든 레플리카에서 업데이트·삭제 연산이 같은 순서로 실행됨을 보장할 수 있고, 쓰기가 리더를 통해 직렬화되므로 지연이 증가할 수 있어요. 특히 같은 문서에 대한 동시 업데이트가 잦고 쓰기 연산의 순서가 중요할 때 유용해요.