Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

QdrantContainer

A single-node Qdrant vector database container, queried over its REST interface (port 6333). The gRPC port (6334) is exposed too, but this module’s helpers don’t wrap it — HTTP-first, matching the house convention for HTTP-first modules (see ClickHouse’s module).

Default image: floats to qdrant/qdrant:latest — Qdrant, unlike Elasticsearch, publishes a floating tag, so new() tracks upstream through it rather than a version pinned to this crate’s own release cycle. Guest ports: 6333 (REST — what the helpers use), 6334 (gRPC, exposed but not wrapped) Expected repository: qdrant/qdrant

MethodOnEffect
QdrantContainer::new()builderFloating default image (qdrant/qdrant:latest).
QdrantContainer::with_image(image)builderCaller-chosen image, kept verbatim.
.start()builder → Result<QdrantGuard>Checks the image’s repository, then boots the container.
.rest_url()guardThe REST interface’s base URI.
.stop()guardStops and removes the container, releases its ports.

Compatibility checking

with_image takes impl Into<ImageName> and keeps the image verbatim. start() then checks that image’s repository (registry host, tag, and digest stripped) against qdrant/qdrant before any backend is resolved or any sandbox is created, which keeps the constructors infallible like every other module’s:

use rightsize::ImageName;
use rightsize_modules::QdrantContainer;

// Checked on start, common case.
let guard = QdrantContainer::with_image("qdrant/qdrant:v1.18.3").start().await?;

// A verified drop-in replacement from another registry — the escape hatch.
let guard = QdrantContainer::with_image(
    ImageName::parse("mycorp/qdrant-hardened:v1.18.3")
        .as_compatible_substitute_for("qdrant/qdrant"),
)
.start()
.await?;

A mismatch returns RightsizeError::IncompatibleImage — naming the supplied repository, the expected one, and the override above — rather than letting an unrelated image run all the way to a wait-strategy timeout. new() goes through this same check against its own floating reference, so it can never fail in practice.

Readiness — HTTP 200 on /readyz, answered on the first poll

Verified against a real qdrant/qdrant:v1.18.3 (arm64; note the image’s own tags carry a v prefix, unlike new()’s :latest) boot: GET /readyz returned 200 on the very first poll after boot — no restart/double-boot race to account for here, so this module keeps the default 120s startup timeout rather than widening it.

No memory limit — verified directly

This module sets no memory limit. Verified against a real boot with the limit removed entirely: the full create-collection/upsert/search round trip below completed in a guest reporting ~480 MB total.

Complete example

use rightsize_modules::QdrantContainer;

#[tokio::test]
async fn create_upsert_search_round_trips_over_http() -> Result<(), Box<dyn std::error::Error>> {
    let guard = QdrantContainer::new().start().await?;

    let agent = ureq::Agent::new_with_defaults();
    let rest_url = guard.rest_url();

    agent
        .put(format!("{rest_url}/collections/smoke"))
        .header("Content-Type", "application/json")
        .send(r#"{"vectors":{"size":4,"distance":"Dot"}}"#)?;

    agent
        .put(format!("{rest_url}/collections/smoke/points?wait=true"))
        .header("Content-Type", "application/json")
        .send(r#"{"points":[{"id":1,"vector":[1.0,0.0,0.0,0.0],"payload":{"city":"test"}}]}"#)?;

    let mut search = agent
        .post(format!("{rest_url}/collections/smoke/points/search"))
        .header("Content-Type", "application/json")
        .send(r#"{"vector":[1.0,0.0,0.0,0.0],"limit":1}"#)?;
    let body = search.body_mut().read_to_string()?;
    assert!(body.contains("\"score\""));

    guard.stop().await?;
    Ok(())
}

Verified end to end: created a collection (vector size 4, Dot distance), upserted a point, and a search against it returned a score.

Backend notes

No memory-limit override is needed on either backend — see above.