✎ Edit content·DAY 088 · POST 4 OF 5 · Code Example

Qdrant in 8 Slides

Vector Databases · 11 slides
DAY 088 · POST 4 OF 5
(REMINDER)
DAY 088
Qdrant by Example
@saurav_dnj_24github.com/SauravDnj · linkedin.com/in/sauravdnj
1 / 11

Theme

Palette

Download

4K — sharpest, slowest
🎬 Video options
Preparing preview…
Live preview · loops the “none” effect
All rendering runs in your browser. No server, no cost, no upload. MP4/WebM = full motion + effects · GIF = lightweight loop · PNG/PDF = static for the Instagram & LinkedIn carousel.

Caption (tap to copy)

📖 Deep dive (full written explanation)

The slides stay clean and scannable. Here's the in-depth explanation behind each one — great for the blog version, show notes, or studying the topic properly.
Slide 1 · Qdrant by Example

This cover sets expectations for the code-heavy post: the goal is to go from an empty machine to a working filtered semantic search in a handful of commands, because watching that happen end to end is what makes HNSW and payloads stop feeling abstract. Reading the theory is necessary, but the wiring only clicks once you've seen data flow through it.

Everything in this post is copy-pasteable and uses the official qdrant-client, the library you'll actually import in production. The explicit instruction to type it rather than skim is deliberate: re-keying the code is how the API surface lodges in memory.

Slide 2 · 0. Run Qdrant in Docker

Running Qdrant locally is genuinely one command, which is part of its appeal. The docker run exposes two ports: 6333 for the REST/HTTP API and the web dashboard, and 6334 for gRPC, which you'll prefer for high-throughput workloads. The volume mount maps a host directory to /qdrant/storage so your data survives container restarts — omit it and everything vanishes when the container stops.

The pip install qdrant-client line gets you the Python client used throughout the rest of the post. With these two commands you have a real vector database running on your laptop, identical to what runs in Qdrant Cloud — that single-engine promise from post one, made concrete.

Slide 3 · 1. Connect + create collection

This step connects to the running instance and creates a collection. The QdrantClient points at the REST port. recreate_collection is used deliberately for a demo: it drops any existing collection of the same name and makes a fresh one, so the example is idempotent and you can rerun it cleanly. In production you'd use create_collection and handle the already-exists case rather than blowing data away.

The two parameters that matter are inside VectorParams: size and distance. The size here is a tiny 4 purely so the demo vectors are readable; in reality it must equal your embedding model's output dimension. The distance is cosine, the right default for most text embeddings. Both are fixed for the life of the collection.

Slide 4 · 2. Upsert points with payloads

Upserting is how points enter Qdrant, and 'upsert' means insert-or-update: reuse an id and you overwrite that point. Each PointStruct bundles the three nouns from post one — an id, a vector matching the collection's size, and a payload of arbitrary JSON. Here the payloads carry a topic string and a year integer, the fields the later filter will use.

Note the ids are integers. Qdrant accepts unsigned integers or UUIDs, not arbitrary strings, which catches people migrating from key-value stores. The two points are deliberately far apart in vector space so the upcoming similarity search has an obvious winner, making the output easy to verify.

Slide 5 · 3. Plain similarity search

This is the plain similarity search, the core operation everything else decorates. query_points takes a query vector and a limit, and returns points ranked by closeness under the collection's metric. with_payload=True asks Qdrant to return the stored JSON alongside each hit, which you almost always want so you can act on the metadata.

The query vector is close to point 1's vector and far from point 2's, so point 1 comes back first with a cosine score near 1.0, as the comment shows. The .points accessor unwraps the response object. Reading the score is a useful habit: it tells you not just the ranking but how confident the match is, which matters when you set relevance thresholds.

Slide 6 · 4. Add a payload filter

This step adds the constraint that turns similarity search into production search. The query vector is unchanged, but a query_filter now restricts results to points where topic equals 'ai' AND year is at least 2025. The must list expresses AND semantics; MatchValue handles exact equality and Range handles numeric bounds.

Crucially, Qdrant applies this filter during the search, as post three explained, not by fetching similar points and discarding afterwards. That's why filtered queries stay fast — provided the filtered fields are indexed, which the mistakes post hammers on. This snippet is, in miniature, exactly what a tenant-scoped or permission-scoped RAG retrieval looks like.

Slide 7 · The five steps

This pipeline diagram compresses the whole walkthrough into four beats: docker run brings Qdrant up, create makes the collection, upsert loads points, and query — with or without a filter — retrieves them. It's the shape of essentially every Qdrant integration, regardless of language or scale.

Keeping this loop in mind helps when you move from this toy demo to a real service. The create step happens once at setup, upsert runs continuously as data arrives, and query runs on every user request. Recognising which step is one-time versus per-request guides where you put each call in your application.

Slide 8 · 5. Batch upserts at scale

Real systems don't upsert two points at a time, so this shows the batch form. models.Batch takes parallel lists — ids, vectors, and payloads — that must line up by index, so the third vector and third payload belong to the third id. This is far more efficient than a loop of single upserts because it's one request and one indexing pass.

wait=True makes the call block until the write is actually applied, so a query immediately afterwards sees the new data. That matters for correctness in tests and synchronous flows; for bulk ingestion where throughput trumps immediacy you'd set it False and let indexing catch up. Choosing between them deliberately is the difference between a flaky pipeline and a reliable one.

Slide 9 · Gotchas that bite

This slide collects the gotchas that turn a smooth demo into a frustrating debugging session. The query vector must match the collection size exactly or the request is rejected. Ids must be unsigned integers or UUIDs, not free-form strings. Filtering on a payload field with no index forces a scan and tanks latency at scale. wait=True is required when a read depends on a just-written value. And gRPC on port 6334 outperforms REST for heavy loads.

Each of these is cheap to honour up front and expensive to discover in production. Several reappear in the mistakes post precisely because they're so common — seeing them here, attached to working code, makes them stick.

Slide 10 · What you just built

This recap confirms what the five steps actually delivered: a live local Qdrant, a correctly configured collection, points carrying vectors and payloads, a scored similarity search, and that same search narrowed by a filter. That's a complete, if miniature, retrieval system — the exact skeleton of a RAG or semantic-search backend.

The value of stating it plainly is that you can now map each step to its place in a larger app and swap the toy 4-dim vectors for real embeddings without changing the structure. The code scales; only the numbers and volume change.

Slide 11 · Save this. Follow for Day 89.

The CTA pivots from building to breaking. You can now make Qdrant work; the final post catalogues the ways people make it work badly — wrong metric, unindexed filters, RAM blowups, stale reads, and tenant mixing. Save this post as your reference implementation, and read the next one before you ship so those mistakes never reach production.

🎨 AI image prompt (matches this theme + palette)

Paste into Midjourney, DALL·E, Ideogram, etc. to generate an on-brand image, then upload it on the Edit content page. The prompt updates automatically with the selected theme + palette.