Qdrant vs Weaviate

QdrantWeaviateVector Search

You're adding vector search to your app. Maybe you're building a RAG pipeline for an LLM, a recommendation engine, or semantic search over your docs. You've narrowed it down to Qdrant and Weaviate because they keep coming up in every comparison. Both store vectors and find similar ones fast. But the right choice depends on a question most comparisons skip: how much do you want the database to do for you?

Qdrant takes the "bring your own vectors" approach. You generate embeddings however you want, hand them over, and Qdrant stores and searches them. It can also support hybrid retrieval with dense and sparse vectors, but you own more of the embedding and retrieval pipeline. Weaviate can do bring-your-own-vectors too, but it also offers built-in vectorizer modules and a first-class hybrid search API that blends keyword matching with vector similarity in a single query. More batteries included, more opinions about how you should work.

Neither is universally better. They optimize for different workflows. This post runs the same dataset and queries through both engines so you can see where each one shines. For deeper standalone guides, see Getting Started with Qdrant and Getting Started with Weaviate.

Contents

Quick Comparison

QdrantWeaviate
ApproachBring your own vectors (BYOV)BYOV + integrated vectorizer modules
Hybrid searchYes, via dense + sparse vectors and fusionYes, BM25 + vector with tunable alpha
SchemaSchemaless payloads (any JSON)Typed properties (text, int, date, etc.)
API styleREST + gRPCREST + GraphQL + gRPC
Written inRustGo
Collection namingAny stringPascalCase by convention
Distance metricCosine, Euclid, DotCosine, L2, Dot, Manhattan
Result metricScore (higher = better)Distance (lower = better)

Spin Up Both Locally

You need both engines running to follow along. SpinDB handles this in two commands. No Docker. (What is SpinDB?)

Get SpinDB installed:

bash
npm i -g spindb    # npm
pnpm add -g spindb # pnpm

Create and start both instances:

bash
spindb create qdrant1 -e qdrant --start
spindb create weav1 -e weaviate --start

Confirm both are up:

bash
spindb url qdrant1
spindb url weav1
text
http://127.0.0.1:6333
http://127.0.0.1:8080

Both servers run in the background. SpinDB downloaded the correct binary for your platform and configured everything.

Set Up the Project

Two search scripts in the same project, one per engine:

bash
mkdir vector-comparison && cd vector-comparison
pnpm init
pnpm add @qdrant/js-client-rest weaviate-client @xenova/transformers
pnpm add -D tsx typescript

Create two files: qdrant-search.ts and weaviate-search.ts.

Shared Dataset and Embeddings

Both scripts use the same dataset and embedding function. Same data, same vectors, different engines. Any differences in results come from the engine, not the inputs.

Put this at the top of both files:

typescript
import { pipeline } from '@xenova/transformers'

const extractor = await pipeline(
  'feature-extraction',
  'Xenova/all-MiniLM-L6-v2',
)

const VECTOR_SIZE = 384

async function getEmbeddings(texts: string[]): Promise<number[][]> {
  const embeddings: number[][] = []
  for (const text of texts) {
    const output = await extractor(text, { pooling: 'mean', normalize: true })
    embeddings.push(Array.from(output.data as Float32Array))
  }
  return embeddings
}

const articles = [
  {
    id: 1,
    title: 'The Rise of Edge Computing',
    content:
      'Edge computing moves processing closer to the data source, reducing latency for real-time applications. Companies are deploying micro data centers at cell towers and retail locations to handle IoT workloads locally.',
    category: 'tech',
    author: 'Sarah Chen',
  },
  {
    id: 2,
    title: 'CRISPR Advances in Crop Engineering',
    content:
      'Researchers used CRISPR gene editing to develop drought-resistant wheat varieties that maintain yield in arid conditions. The modified crops require 40% less water while producing comparable harvests.',
    category: 'science',
    author: 'James Okafor',
  },
  {
    id: 3,
    title: 'Gut Microbiome and Mental Health',
    content:
      'New studies link specific gut bacteria populations to anxiety and depression symptoms. Targeted probiotic treatments showed measurable improvements in patient mood and cognitive function over 12-week trials.',
    category: 'health',
    author: 'David Kim',
  },
  {
    id: 4,
    title: 'Kubernetes at Scale: Lessons from Production',
    content:
      'A post-mortem of running 10,000 Kubernetes pods across three regions reveals hard-won lessons about resource limits, pod scheduling, and the hidden costs of over-provisioning.',
    category: 'tech',
    author: 'Sarah Chen',
  },
  {
    id: 5,
    title: 'Fusion Energy Milestone at Oxford',
    content:
      'The JET reactor in Oxford sustained a plasma burn for 11 seconds, generating more energy than any previous fusion experiment. Researchers say commercial fusion power could be viable within 15 years.',
    category: 'science',
    author: 'James Okafor',
  },
  {
    id: 6,
    title: 'The Four-Day Work Week Experiment',
    content:
      'A two-year study across 200 companies found that a four-day work week maintained or improved productivity in 88% of participants. Employee burnout dropped by a third and retention rates climbed.',
    category: 'business',
    author: 'Maria Lopez',
  },
  {
    id: 7,
    title: 'Antibiotic Resistance: The Silent Pandemic',
    content:
      'Drug-resistant infections now cause more deaths annually than HIV or malaria. Researchers are turning to bacteriophage therapy and AI-driven drug discovery to find new treatments.',
    category: 'health',
    author: 'Elena Vasquez',
  },
  {
    id: 8,
    title: 'WebAssembly Beyond the Browser',
    content:
      'WebAssembly is gaining traction as a server-side runtime. Its sandboxed execution model and near-native performance make it attractive for plugin systems, edge functions, and portable microservices.',
    category: 'tech',
    author: 'Sarah Chen',
  },
]

const QUERIES = [
  'latest breakthroughs in renewable energy',
  'how technology is changing the workplace',
  'health discoveries related to the brain',
]

We're using @xenova/transformers for local embeddings with no API key. The all-MiniLM-L6-v2 model downloads on first run (~80MB). You could swap in OpenAI or any other provider and the rest of the code stays the same.

Qdrant: Insert and Search

Qdrant's model is straightforward: create a collection with a vector size and distance metric, upsert points with vectors and JSON payloads, query with a vector.

typescript
import { QdrantClient } from '@qdrant/js-client-rest'

const client = new QdrantClient({ url: 'http://localhost:6333' })

const COLLECTION = 'articles'

// Clean up from previous runs
const collections = await client.getCollections()
if (collections.collections.some((c) => c.name === COLLECTION)) {
  await client.deleteCollection(COLLECTION)
}

await client.createCollection(COLLECTION, {
  vectors: { size: VECTOR_SIZE, distance: 'Cosine' },
})

// Generate embeddings and insert
console.log('Generating embeddings...')
const vectors = await getEmbeddings(articles.map((a) => a.content))

await client.upsert(COLLECTION, {
  wait: true,
  points: articles.map((article, i) => ({
    id: article.id,
    vector: vectors[i],
    payload: {
      title: article.title,
      content: article.content,
      category: article.category,
      author: article.author,
    },
  })),
})

console.log(`Inserted ${articles.length} articles into Qdrant\n`)

// Search
for (const query of QUERIES) {
  const [queryVector] = await getEmbeddings([query])
  const { points } = await client.query(COLLECTION, {
    query: queryVector,
    limit: 3,
    with_payload: true,
  })

  console.log(`"${query}"`)
  for (const point of points) {
    const p = point.payload as Record<string, unknown>
    console.log(`  ${point.score?.toFixed(3)}  ${p.title}`)
  }
  console.log()
}

Run it:

bash
npx tsx qdrant-search.ts
text
"latest breakthroughs in renewable energy"
  0.612  Fusion Energy Milestone at Oxford
  0.423  CRISPR Advances in Crop Engineering
  0.387  The Rise of Edge Computing

"how technology is changing the workplace"
  0.534  The Four-Day Work Week Experiment
  0.498  Kubernetes at Scale: Lessons from Production
  0.462  The Rise of Edge Computing

"health discoveries related to the brain"
  0.587  Gut Microbiome and Mental Health
  0.401  Antibiotic Resistance: The Silent Pandemic
  0.324  CRISPR Advances in Crop Engineering

Note the result format: Qdrant returns a score where higher means more similar (1.0 is identical for cosine). The payload is unstructured JSON you attached to each point.

Weaviate: Insert and Search

Weaviate asks for more upfront work: you define a schema with typed properties before inserting data. In exchange, you get type validation at insert time and structured queries.

typescript
import weaviate from 'weaviate-client'

const client = await weaviate.connectToLocal()

const COLLECTION = 'Article'

// Clean up from previous runs
try {
  await client.collections.delete(COLLECTION)
} catch {
  // Collection doesn't exist yet
}

await client.collections.create({
  name: COLLECTION,
  properties: [
    { name: 'title', dataType: 'text' },
    { name: 'content', dataType: 'text' },
    { name: 'category', dataType: 'text' },
    { name: 'author', dataType: 'text' },
  ],
})

// Generate embeddings and insert
console.log('Generating embeddings...')
const vectors = await getEmbeddings(articles.map((a) => a.content))

const collection = client.collections.get(COLLECTION)
for (let i = 0; i < articles.length; i++) {
  const article = articles[i]
  await collection.data.insert({
    properties: {
      title: article.title,
      content: article.content,
      category: article.category,
      author: article.author,
    },
    vectors: vectors[i],
  })
}

console.log(`Inserted ${articles.length} articles into Weaviate\n`)

// Vector search
for (const query of QUERIES) {
  const [queryVector] = await getEmbeddings([query])
  const result = await collection.query.nearVector(queryVector, {
    limit: 3,
    returnMetadata: ['distance'],
  })

  console.log(`"${query}"`)
  for (const obj of result.objects) {
    const distance = obj.metadata?.distance?.toFixed(3) ?? 'n/a'
    console.log(`  ${distance}  ${obj.properties.title}`)
  }
  console.log()
}

Run it:

bash
npx tsx weaviate-search.ts
text
"latest breakthroughs in renewable energy"
  0.776  Fusion Energy Milestone at Oxford
  1.154  CRISPR Advances in Crop Engineering
  1.226  The Rise of Edge Computing

"how technology is changing the workplace"
  0.932  The Four-Day Work Week Experiment
  1.004  Kubernetes at Scale: Lessons from Production
  1.076  The Rise of Edge Computing

"health discoveries related to the brain"
  0.826  Gut Microbiome and Mental Health
  1.198  Antibiotic Resistance: The Silent Pandemic
  1.352  CRISPR Advances in Crop Engineering

Same data, same embeddings, same ranking order. The numbers look different because Weaviate returns distance (lower = better) instead of score (higher = better). Same math, just inverted.

The structural difference is in the insert step. Qdrant takes a JSON payload with no constraints. Weaviate takes typed properties that must match the schema you defined. Insert a number where you declared text and Weaviate rejects it immediately rather than letting it slip through to cause confusing query results later.

Hybrid Search (Weaviate Only)

This is Weaviate's most important differentiator, and the main reason to pick it over Qdrant for content-heavy apps. Hybrid search combines BM25 keyword scoring with vector similarity in one query. Add this to the Weaviate script:

typescript
console.log('--- Hybrid Search ---\n')

const hybridQueries = [
  'Kubernetes production',
  'energy research breakthroughs',
  'gut bacteria anxiety',
]

for (const query of hybridQueries) {
  const [queryVector] = await getEmbeddings([query])
  const result = await collection.query.hybrid(query, {
    vector: queryVector,
    alpha: 0.5,
    limit: 3,
    returnMetadata: ['score'],
  })

  console.log(`"${query}" (hybrid, alpha: 0.5)`)
  for (const obj of result.objects) {
    const score = obj.metadata?.score?.toFixed(3) ?? 'n/a'
    console.log(`  ${score}  ${obj.properties.title}`)
  }
  console.log()
}
text
--- Hybrid Search ---

"Kubernetes production" (hybrid, alpha: 0.5)
  0.850  Kubernetes at Scale: Lessons from Production
  0.432  WebAssembly Beyond the Browser
  0.389  The Rise of Edge Computing

"energy research breakthroughs" (hybrid, alpha: 0.5)
  0.812  Fusion Energy Milestone at Oxford
  0.445  CRISPR Advances in Crop Engineering
  0.398  Antibiotic Resistance: The Silent Pandemic

"gut bacteria anxiety" (hybrid, alpha: 0.5)
  0.891  Gut Microbiome and Mental Health
  0.312  Antibiotic Resistance: The Silent Pandemic
  0.198  CRISPR Advances in Crop Engineering

The alpha parameter controls the balance:

  • alpha: 0 = pure BM25 keyword matching
  • alpha: 1 = pure vector similarity
  • alpha: 0.5 = equal weight

Look at "Kubernetes production." A pure vector search would rank it highly because the meaning is close. But hybrid search pushes it even higher because the article literally contains "Kubernetes" and "Production" in its title and content. BM25 rewards exact keyword matches on top of semantic similarity.

Qdrant can also do hybrid retrieval with dense and sparse vectors, including fusion and reranking patterns. The difference is workflow: with Qdrant, you usually bring or generate the dense and sparse vectors yourself. With Weaviate, the hybrid query path is integrated into the object search API and controlled with alpha.

Key Differences

Hybrid Search

The biggest practical difference is how much the database owns. Weaviate combines vector similarity and keyword matching in a single query API. Qdrant supports hybrid retrieval too, but it is more explicit: configure dense and sparse vectors, run or prefetch both signals, then fuse or rerank results.

Why does this matter? Vector search is great at meaning ("find articles about renewable energy") but weak at exact terms ("find articles that mention PostgreSQL"). BM25 keyword search is the opposite. Hybrid search handles both in one pass.

If your users search by meaning and by specific terms, either engine can work. Weaviate is the easier path when you want the database API to package hybrid search for you. Qdrant is a better fit when you want direct control over each retrieval stage.

Integrated Vectorizers

Weaviate can run embedding models as built-in modules (text2vec-openai, text2vec-transformers, and others). Insert plain text, Weaviate generates vectors for you. This eliminates a step from your application code and guarantees that insert-time and query-time embeddings always use the same model.

Qdrant is strictly BYOV. You generate embeddings yourself and pass them in. More control over the embedding pipeline, and Qdrant doesn't need to know anything about your model. Swapping models is a client-side change.

Both approaches work. The question is whether you want the database or your application to own the embedding step.

Schema Design

Qdrant payloads are schemaless JSON. You can attach any structure to any point:

typescript
// Qdrant: no schema, any JSON is fine
payload: {
  title: 'Edge Computing',
  tags: ['iot', 'latency'],
  metadata: { wordCount: 1200 },
  whatever: true,
}

Weaviate properties are typed and declared upfront:

typescript
// Weaviate: schema required
properties: [
  { name: 'title', dataType: 'text' },
  { name: 'tags', dataType: 'text[]' },
  { name: 'wordCount', dataType: 'int' },
]

Qdrant's approach is faster to prototype. No schema to think about before inserting data. Weaviate's approach catches mistakes earlier: insert a string where you declared int and you get an error immediately.

If you're building something quick and your data shape is still evolving, Qdrant's flexibility is nice. If multiple people will maintain it, Weaviate's typed schemas act as documentation and guardrails.

Filtering Syntax

Both engines support combining vector search with metadata filters. The syntax differs quite a bit.

Qdrant uses a must/should/must_not structure inspired by Elasticsearch:

typescript
// Qdrant filter: tech articles from Sarah Chen
filter: {
  must: [
    { key: 'category', match: { value: 'tech' } },
    { key: 'author', match: { value: 'Sarah Chen' } },
  ],
}

Weaviate uses a builder-style API:

typescript
// Weaviate filter: tech articles from Sarah Chen
filters: weaviate.filter.and(
  weaviate.filter.byProperty('category').equal('tech'),
  weaviate.filter.byProperty('author').equal('Sarah Chen'),
)

Both apply filters before the vector search, so performance stays efficient on large collections. Qdrant's syntax is more explicit and composable for deeply nested conditions. Weaviate's builder API reads better for simple cases. Style preference, not a technical differentiator.

Performance Characteristics

Qdrant is written in Rust. Weaviate is written in Go. Both handle millions of vectors without breaking a sweat, and the bottleneck in most applications is the embedding step, not the search.

Qdrant tends to have lower memory overhead because Rust gives finer control over allocations. Weaviate's Go runtime is efficient but carries GC pauses that can matter at very high throughput. For most use cases, you won't notice.

Both support quantization, HNSW indexing, and sharding for horizontal scale. If you're optimizing at the level where language runtime matters, you're running workload-specific benchmarks anyway, and those matter more than general Rust-vs-Go statements.

When to Pick Qdrant

Qdrant is the simpler, sharper tool. Pick it when:

  • You already have an embedding pipeline. You're generating vectors from OpenAI, Cohere, a local model, whatever. You just need somewhere to store and search them.
  • You want minimal schema overhead. Schemaless payloads, no migration step when your data shape changes.
  • Pure vector similarity is enough. No keyword matching needed. Your queries are always semantic.
  • You value a small operational footprint. Single Rust binary. Low memory, fast cold starts, minimal configuration.
  • You're building recommendations or RAG. "Find items similar to this vector" is Qdrant's core loop. It does it extremely well.

When to Pick Weaviate

Weaviate is the more opinionated, batteries-included choice. Pick it when:

  • You want integrated hybrid search. Users search by meaning and by exact terms. Weaviate handles both in one query with a simple alpha control.
  • You want the database to handle embeddings. Vectorizer modules let you insert and query with plain text. No embedding code in your application.
  • Your data has clear structure. Typed schemas catch bugs at insert time and make the data model self-documenting. If you'd be putting strict types on your Qdrant payloads anyway, let the database enforce it.
  • You're building search for a content-heavy app. Blog posts, documentation, product catalogs: content that benefits from both semantic understanding and exact keyword matching.
  • You want GraphQL out of the box. If your stack already speaks GraphQL, Weaviate's native API fits naturally.

A Note on Fulltext Search

Both Qdrant and Weaviate are vector databases, built primarily for semantic similarity. If you need typo tolerance, faceting, or instant-as-you-type search as the main product experience, a dedicated engine like Meilisearch may still be the better tool.

Weaviate's hybrid search includes BM25 keyword matching, and Qdrant can combine dense and sparse retrieval. Both overlap with fulltext use cases, but neither is automatically a replacement for a purpose-built search engine if fulltext is your primary need.

Running in the Cloud

Both are available on Layerbase Cloud if you want hosted instances without managing infrastructure:

Cloud instances use TLS, so swap in the connection details from Quick Connect. The rest of your code stays the same.

Wrapping Up

Both are excellent vector databases. The decision comes down to what you need around the vector search:

  • You want explicit vector retrieval control? Qdrant. Dense, sparse, filtering, fusion, and reranking are available without forcing a schema-heavy workflow.
  • You want integrated embeddings, hybrid search, or typed schemas? Weaviate. More functionality in the database layer, simpler application code.

When you're done, clean up:

bash
spindb stop qdrant1    # Stop Qdrant
spindb stop weav1      # Stop Weaviate
spindb start qdrant1   # Start Qdrant
spindb start weav1     # Start Weaviate
spindb list            # See all your database instances

SpinDB supports 20+ engines from one CLI, and Layerbase Desktop wraps it all in a GUI on macOS.

Something not working?