Files
qdrant/tests/openapi/test_memory_placement.py
Andrey VasnetsovandClaude Fable 5.1 95eb3c3f79 Support cached id tracker memory placement (#10598)
Thread a `Populate` through the disk-resident id tracker's open paths
(`DiskMappingReader`, `DiskIdTracker::open`, `ReadOnlyDiskIdTracker`,
`ReadOnlyIdTrackerEnum`) so a `cached` placement primes the page cache with the
mapping files on load instead of leaving them to page in on demand. The
populate is derived from the segment config's placement at load time, clamped
by low-memory mode, in both the writable segment open and the read-only one.
The update-only lookup path keeps its transfer-nothing policy, and the
build-time open stays cold: the built segment is reloaded anyway.

`cached` is no longer rejected by validation.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 13:09:29 +02:00

228 lines
7.7 KiB
Python

import pytest
from .helpers.collection_setup import drop_collection
from .helpers.helpers import request_with_validation
collection_name = "test_memory_placement"
@pytest.fixture(autouse=True)
def setup():
drop_collection(collection_name)
yield
drop_collection(collection_name)
def test_create_collection_with_memory_placement():
# Every component configured through the new `memory` parameter
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {
"size": 4,
"distance": "Dot",
"memory": "cached",
"hnsw_config": {"memory": "cold"},
"quantization_config": {
"scalar": {"type": "int8", "memory": "pinned"},
},
},
"sparse_vectors": {
"sparse": {"index": {"memory": "cold"}},
},
"payload": {"memory": "cached"},
},
)
assert response.ok, response.text
# The new parameters must be echoed back in the collection info
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
params = response.json()["result"]["config"]["params"]
assert params["vectors"]["memory"] == "cached"
assert params["vectors"]["hnsw_config"]["memory"] == "cold"
assert params["vectors"]["quantization_config"]["scalar"]["memory"] == "pinned"
assert params["sparse_vectors"]["sparse"]["index"]["memory"] == "cold"
assert params["payload"]["memory"] == "cached"
# Payload field index with the new parameter
response = request_with_validation(
api="/collections/{collection_name}/index",
method="PUT",
path_params={"collection_name": collection_name},
query_params={"wait": "true"},
body={
"field_name": "keyword_field",
"field_schema": {"type": "keyword", "memory": "cold"},
},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
schema = response.json()["result"]["payload_schema"]
assert schema["keyword_field"]["params"]["memory"] == "cold"
def test_create_collection_without_memory_placement_has_no_new_fields():
# A collection created without the new parameters must not expose them in
# the collection info, so clients of older SDKs see an unchanged response
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {"size": 4, "distance": "Dot", "on_disk": True},
},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
params = response.json()["result"]["config"]["params"]
assert "memory" not in params["vectors"]
assert "payload" not in params
assert "id_tracker" not in params
def test_pinned_dense_vector_storage_is_rejected():
# Dense vector storage has no heap variant: `pinned` must be rejected
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {"size": 4, "distance": "Dot", "memory": "pinned"},
},
)
assert response.status_code == 422, response.text
assert "pinned" in response.json()["status"]["error"]
def test_id_tracker_memory_placement():
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {"size": 4, "distance": "Dot"},
"id_tracker": {"memory": "cold"},
},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
assert response.json()["result"]["config"]["params"]["id_tracker"]["memory"] == "cold"
# The placement is mutable
response = request_with_validation(
api="/collections/{collection_name}",
method="PATCH",
path_params={"collection_name": collection_name},
body={"params": {"id_tracker": {"memory": "pinned"}}},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
assert response.json()["result"]["config"]["params"]["id_tracker"]["memory"] == "pinned"
response = request_with_validation(
api="/collections/{collection_name}",
method="PATCH",
path_params={"collection_name": collection_name},
body={"params": {"id_tracker": {"memory": "cached"}}},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
assert response.json()["result"]["config"]["params"]["id_tracker"]["memory"] == "cached"
def test_pinned_payload_storage_is_rejected():
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {"size": 4, "distance": "Dot"},
"payload": {"memory": "pinned"},
},
)
assert response.status_code == 422, response.text
assert "pinned" in response.json()["status"]["error"]
def test_update_collection_memory_placement():
response = request_with_validation(
api="/collections/{collection_name}",
method="PUT",
path_params={"collection_name": collection_name},
body={
"vectors": {"size": 4, "distance": "Dot"},
},
)
assert response.ok, response.text
# Vector storage placement and payload storage placement are mutable
response = request_with_validation(
api="/collections/{collection_name}",
method="PATCH",
path_params={"collection_name": collection_name},
body={
"vectors": {"": {"memory": "cold"}},
"params": {"payload": {"memory": "cached"}},
},
)
assert response.ok, response.text
response = request_with_validation(
api="/collections/{collection_name}",
method="GET",
path_params={"collection_name": collection_name},
)
assert response.ok, response.text
params = response.json()["result"]["config"]["params"]
assert params["vectors"]["memory"] == "cold"
assert params["payload"]["memory"] == "cached"
# `pinned` is rejected on update as well
response = request_with_validation(
api="/collections/{collection_name}",
method="PATCH",
path_params={"collection_name": collection_name},
body={
"vectors": {"": {"memory": "pinned"}},
},
)
assert response.status_code == 422, response.text
assert "pinned" in response.json()["status"]["error"]