Vector Search Is Not One Thing: Server, Extension, or Embedded Files thumbnail
CompareJul 6, 2026Data infrastructure

Vector Search Is Not One Thing: Server, Extension, or Embedded Files

Vector search splits first by where it exists: Qdrant is a dedicated vector engine, pgvector is a PostgreSQL index access method, and LanceDB/Lance is an embedded file-backed stack. That operational home decides filtering, durability, consistency, and failure shape before benchmark tuning starts.

By ArcYou Editorial. Source read against the pinned repository states listed in Methodology.

Decision brief

What this source read clarifies

What it really is
A comparison analysis of vector search tools by existence model and index family
Best for
Teams that must decide operating boundaries and data ownership before search quality tuning
Avoid if
You only need API usage for one library
Core mechanism
Places server engines, Postgres access methods, and embedded columnar formats on the same axis.

Read at: qdrant-v1.18.2 @ v1.18.2 / pgvector-v0.8.4 @ v0.8.4 / lance-v8.0.0 @ v8.0.0 / +1 more

The first vector-search decision is not HNSW versus IVF. It is where the system exists. Qdrant is a dedicated vector engine, pgvector is a PostgreSQL index access method, and LanceDB/Lance is an embedded, file-backed stack. That choice decides who owns filtering, durability, consistency, and failure.

I read this map at Qdrant v1.18.2 (44ad62f8cd69642be5afa6441612525e24a0d063), pgvector v0.8.4 (1d458ad5d73731241e8b912cacc2079beb643d30), Lance v8.0.0 (15f2ff594a25b97f9bedd21a253b612ce14e39ec), and LanceDB v0.30.0 (a5288de8d14ff0dfbf42a69c5e2a557b06ebfb6b). The short version: do not ask “which vector database is fastest?” until you know whether you want to operate a search service, extend the database you already trust, or carry indexed vector data as files. To set expectations early: this is an ownership map, not a benchmark piece — recall and latency numbers are out of scope here.

Vector search existence models: Qdrant as a dedicated server, pgvector as a PostgreSQL extension, and LanceDB/Lance as embedded file-backed search

The first split is operational: server-owned search, Postgres-owned index access, or application-opened files.

The comparison in one table

Question Qdrant pgvector LanceDB / Lance
What does it exist as? A dedicated vector engine A PostgreSQL extension and index access method A LanceDB connection layer over Lance datasets and local/object-store paths
Who owns the vector search path? Qdrant’s segment and HNSW planner PostgreSQL’s index AM callback path plus pgvector operators Lance/LanceDB code opened by the application
Where does filtering meet ANN? Inside Qdrant’s filtered HNSW dispatch and optional payload-aware graph links As Postgres query execution around an order-by vector index scan Around file-backed datasets and IVF-family vector indexes
Who owns durability and visibility? The vector service PostgreSQL relation pages, WAL, heap TIDs, and MVCC snapshots Lance versioned manifests over fragments
Main operational bet Run a purpose-built vector service Keep vectors inside the database ledger you already operate Let vector data travel with files, jobs, notebooks, or object storage

That table is why benchmark charts can be misleading if they are the first thing you look at. A Qdrant filtered search, a pgvector ORDER BY embedding <-> query LIMIT k, and a Lance file-backed IVF index do not persist, filter, or deploy in the same place.

Qdrant: the vector engine owns search and filtering

Qdrant’s strongest claim in this comparison is not just “it has HNSW.” The interesting part is that the filtered-search branch makes payload filtering part of the search plan.

In Qdrant v1.18.2, the filtered HNSW path first estimates payload-filter cardinality, adjusts that estimate against available vectors, and then dispatches. Small filtered sets go to plain search. Large filtered sets go to HNSW graph search. If the estimate is not decisive, Qdrant runs a sample cardinality check before choosing the path (vector_index_impl.rs#L114-L166).

That is a server-shaped design. The vector engine is not waiting for an outside SQL executor to hand it a final candidate set. It is deciding, inside the search path, whether the filter is small enough for exact scan or large enough for graph traversal.

Qdrant also has a build-time answer to the classic filtered-HNSW problem: a graph that is connected globally can become disconnected inside a filter slice. For eligible indexed payload fields, Qdrant can iterate payload blocks, build additional filtered subgraphs with a separate payload_m budget, and merge those links into the main graph builder (build.rs#L378-L523). The code has skip heuristics, so this is not “every payload value always gets extra links.” The important point is ownership: Qdrant can change the graph shape because Qdrant owns the graph.

Use this mental model: Qdrant is where you go when the vector engine should own both nearest-neighbor search and enough payload structure to make filtered search a first-class path.

pgvector: Postgres owns the database, pgvector adds the vector index

pgvector is almost the opposite bet. It does not ask you to move truth into a new vector service. It makes vector search exist inside Postgres.

At v0.8.4, pgvector registers HNSW as a PostgreSQL index access method. The SQL file declares hnswhandler(internal) and creates the hnsw access method, while the C handler returns an IndexAmRoutine, marks it as order-by-operator capable, and wires in HNSW build, insert, and scan hooks (vector.sql#L257-L260, hnsw.c#L266-L310). The operator classes bind vector distance operators like <->, <#>, and <=> to USING hnsw for order-by scans (vector.sql#L313-L326).

That gives you a very different contract from Qdrant. The HNSW graph is a PostgreSQL-managed index relation, not an independent storage engine. pgvector allocates HNSW pages with PostgreSQL buffers, stores heap TIDs in HNSW element tuples, WAL-logs built index pages when the relation needs WAL, uses generic WAL for normal insert changes, requires an MVCC snapshot for scans, and returns heap TIDs through the PostgreSQL index scan path (hnswutils.c#L184-L196, hnsw.h#L357-L367, hnswbuild.c#L1137-L1138, hnswinsert.c#L194-L195, hnswscan.c#L216-L325).

That is the real reason pgvector is attractive. Not because it magically turns Postgres into a dedicated vector service, but because your vectors can live under the same backup, WAL, snapshot, and relational query machinery as the rest of your application state.

The tradeoff follows from the same fact. pgvector participates in the Postgres planner and executor world. If you want the vector engine itself to own payload-aware graph construction and filtered-search dispatch, that is Qdrant’s shape. If you want vector search to stay inside your existing Postgres truth system, pgvector’s shape is the point.

LanceDB and Lance: the vector system travels with files

LanceDB/Lance is the third shape: vector search as an embedded, file-backed data stack rather than a resident server or a Postgres relation.

The LanceDB v0.30.0 Rust connection path makes the split visible. ConnectBuilder::execute routes URIs starting with db to the remote branch, routes manifest_enabled local-native connections to a namespace database branch, and routes the default non-remote, non-manifest branch to ListingDatabase::connect_with_options (connection.rs#L910-L931). That default branch opens an object store from the URI and creates the local directory only in the local-store case (listing.rs#L550-L560).

I am being careful with that wording. The source-backed claim is not “every non-remote LanceDB connection is ListingDatabase.” The verified branch is narrower: remote, manifest-enabled local namespace, and default ListingDatabase are separate paths.

Under that connection layer, Lance v8.0.0 stores dataset state as versioned manifests over fragments. The Manifest contains fragments and a version number, manifest paths live under _versions, and commits use a create-only rename path that maps an existing target to a commit conflict (table.proto#L36-L47, commit.rs#L93-L108, commit.rs#L1439-L1460).

Its vector index family is also telling. Lance’s vector index enum is IVF-first: IvfFlat, IvfSq, IvfPq, IvfHnswSq, IvfHnswPq, IvfHnswFlat, and IvfRq, with the legacy Vector displayed as IVF_PQ (lance-index/src/lib.rs#L137-L169). So the operational story and the index story point in the same direction: data and index state are meant to live with the dataset, not behind a separate vector service boundary.

Use this shape when vector search should move with local files, object-store data, notebooks, pipelines, edge jobs, or embedded application code. You are choosing file-backed versioned data as the center of gravity.

Why this matters more than HNSW vs. IVF at the start

HNSW versus IVF matters. Recall, latency, memory, build time, and update behavior all matter. But those are second-order questions until you know the operational home.

If search exists as a Qdrant service, then filtered search can be planned inside the vector engine and payload-aware links can be added to the graph for eligible indexed fields. Your operational failure domain is a service.

If search exists as a pgvector index, then the vector path joins Postgres’ storage, WAL, MVCC, and query machinery. Your operational failure domain is your database.

If search exists as Lance/LanceDB files opened by application code, then versioned manifests and file/object-store paths become the center. Your operational failure domain is your data layout, object store, and the jobs or apps opening it.

Those are not implementation details. They decide what “consistency” means, where a filter can be optimized, what backup system you trust, what a deploy looks like, and which team gets paged.

Where you fit

Choose Qdrant when you want a vector search service to own the hard vector-search mechanics. The evidence I care about is not a generic HNSW checkbox; it is the filtered-search dispatch and payload-aware graph-building machinery.

Choose pgvector when Postgres is already your source of truth and you want vector search inside that ledger. You get a real HNSW access method and vector operator classes, but the deeper win is that the index participates in PostgreSQL’s relation, WAL, heap TID, and MVCC path.

Choose LanceDB/Lance when vector search should travel with datasets rather than sit behind a resident service. The default local/object-store branch and Lance’s versioned manifest model put files at the center, with an IVF-first index family attached to that file-backed world.

Scope and honesty

This is not a benchmark piece. I did not measure recall, latency, memory, or cost here. I am mapping the source-visible ownership model, because it tells you what a fair benchmark would even have to compare.

I am also not flattening these projects into slogans. Qdrant still has exact/plain paths. pgvector still implements real HNSW. Lance includes IVF-HNSW variants. LanceDB has a remote branch. The point is not that each project has only one feature. The point is that each project has a different place where vector search exists.

Start there. Then benchmark the shape you actually want to operate.

Methodology

This post is read against the pinned repository states below. Directory names are convenience keys; the real pin is the commit SHA.

Repo keyVersionCommitUpstream
qdrant-v1.18.2v1.18.244ad62f8cd69github.com/qdrant/qdrant
pgvector-v0.8.4v0.8.41d458ad5d737github.com/pgvector/pgvector
lance-v8.0.0v8.0.015f2ff594a25github.com/lancedb/lance
lancedb-v0.30.0v0.30.0a5288de8d14fgithub.com/lancedb/lancedb

Related analysis

The decision axis is existence. Running a separate engine, living inside Postgres, or opening files from the application process changes consistency, deployment, and filtering strategy.