| name | Vector Database Patterns |
| description | Comprehensive guide to vector databases including Pinecone, Qdrant, Weaviate, embedding strategies, and similarity search. |
Vector Database Patterns
Overview
Vector databases are specialized databases designed to store, index, and query high-dimensional vectors efficiently. They enable similarity search by finding vectors that are "closest" to a query vector using various distance metrics. This skill covers Pinecone, Qdrant, Weaviate, embedding strategies, similarity search, performance optimization, and production considerations.
Prerequisites
- Understanding of vectors and embeddings
- Knowledge of machine learning concepts
- Familiarity with Python or TypeScript
- Understanding of similarity metrics (cosine, Euclidean, dot product)
- Basic knowledge of database concepts
Key Concepts
Vector Database Fundamentals
- Vectors: Numerical representations of data (text, images, audio) in high-dimensional space
- Embeddings: Vectors generated by machine learning models that capture semantic meaning
- Distance Metrics: Measures of similarity between vectors (cosine, Euclidean, dot product)
- Indexing: Data structures that enable fast similarity search
- Metadata: Additional information associated with vectors for filtering
Vector Database Types
- Pinecone: Managed service, easy setup, good for production
- Qdrant: Open-source, self-hosted option, flexible
- Weaviate: Open-source, GraphQL API, good for multimodal
Use Cases
- Semantic search (finding similar documents, products, images)
- Recommendation systems
- Anomaly detection
- Natural language processing tasks
- Computer vision applications
- Personalization engines
- Knowledge retrieval for RAG (Retrieval-Augmented Generation)
Implementation Guide
Pinecone
Setup and Indexing
import pinecone
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key="your-api-key")
pc.create_index(
name="my-index",
dimension=1536,
metric="cosine",
spec=ServerlessSpec(
cloud="aws",
region="us-east-1"
)
)
index = pc.Index("my-index")
stats = index.describe_index_stats()
print(f"Total vectors: {stats['total_vector_count']}")
print(f"Dimension: {stats['dimension']}")
import { Pinecone } from '@pinecone-database/pinecone';
const pinecone = new Pinecone({
apiKey: 'your-api-key'
});
await pinecone.createIndex({
name: 'my-index',
dimension: 1536,
metric: 'cosine',
spec: {
serverless: {
cloud: 'aws',
region: 'us-east-1'
}
}
});
const index = pinecone.index('my-index');
const stats = await index.describeIndexStats();
console.log('Total vectors:', stats.totalVectorCount);
console.log('Dimension:', stats.dimension);
Upserting Vectors
index.upsert(
vectors=[
{
"id": "doc1",
"values": [0.1, 0.2, 0.3, ...],
"metadata": {
"title": "Document 1",
"category": "technology",
"date": "2024-01-01"
}
}
]
)
index.upsert(
vectors=[
{
"id": "doc1",
"values": vector1,
"metadata": {"title": "Document 1", "category": "tech"}
},
{
"id": "doc2",
"values": vector2,
"metadata": {"title": "Document 2", "category": "science"}
},
{
"id": "doc3",
"values": vector3,
"metadata": {"title": "Document 3", "category": "tech"}
}
],
namespace="documents"
)
from tqdm import tqdm
def upsert_in_batches(vectors, batch_size=):
i tqdm((, (vectors), batch_size)):
batch = vectors[i:i + batch_size]
index.upsert(vectors=batch)
await index.upsert([
{
id: 'doc1',
values: [0.1, 0.2, 0.3, ...],
metadata: {
title: 'Document 1',
category: 'technology',
date: '2024-01-01'
}
}
]);
await index.upsert([
{
id: 'doc1',
values: vector1,
metadata: { title: 'Document 1', category: 'tech' }
},
{
id: 'doc2',
values: vector2,
metadata: { title: 'Document 2', category: 'science' }
},
{
id: 'doc3',
values: vector3,
metadata: { title: 'Document 3', category: 'tech' }
}
]);
await index.upsert([
{
id: 'doc1',
values: vector1,
metadata: { title: 'Document 1' }
}
], );
Querying
results = index.query(
vector=query_vector,
top_k=10,
include_metadata=True,
include_values=False
)
for match in results['matches']:
print(f"ID: {match['id']}, Score: {match['score']}")
print(f"Metadata: {match['metadata']}")
results = index.query(
vector=query_vector,
top_k=10,
namespace="documents",
include_metadata=True
)
results = index.query(
vector=query_vector,
top_k=10,
filter={
"category": {"$eq": "technology"},
"date": {"$gte": "2024-01-01"}
},
include_metadata=True
)
results = index.query(
vector=query_vector,
top_k=10,
filter={
"$or": [
{"category": {"$eq": "technology"}},
{"category": {"$eq": "science"}}
],
"date": {"$gte": "2024-01-01"}
},
include_metadata=
)
const results = await index.query({
vector: queryVector,
topK: 10,
includeMetadata: true,
includeValues: false
});
results.matches.forEach(match => {
console.log(`ID: ${match.id}, Score: ${match.score}`);
console.log('Metadata:', match.metadata);
});
const results = await index.query({
vector: queryVector,
topK: 10,
namespace: 'documents',
includeMetadata: true
});
const results = await index.query({
vector: queryVector,
topK: 10,
filter: {
category: { $eq: 'technology' },
date: { $gte: '2024-01-01' }
},
includeMetadata: true
});
results = index.({
: queryVector,
: ,
: {
: [
{ : { : } },
{ : { : } }
],
: { : }
},
:
});
Deleting Vectors
index.delete(ids=["doc1"])
index.delete(ids=["doc1", "doc2", "doc3"])
index.delete(delete_all=True, namespace="documents")
index.delete(
filter={
"category": {"$eq": "old"},
"date": {"$lt": "2023-01-01"}
},
namespace="documents"
)
await index.deleteOne('doc1');
await index.deleteMany(['doc1', 'doc2', 'doc3']);
await index.deleteAll({ namespace: 'documents' });
await index.deleteMany({
filter: {
category: { $eq: 'old' },
date: { $lt: '2023-01-01' }
},
namespace: 'documents'
});
Qdrant
Collections and Points
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(
size=1536,
distance=Distance.COSINE
)
)
client.create_collection(
collection_name="multimodal",
vectors_config={
"text": VectorParams(size=1536, distance=Distance.COSINE),
"image": VectorParams(size=512, distance=Distance.EUCLID)
}
)
collections = client.get_collections()
for collection in collections.collections:
print(f"Collection: {collection.name}")
info = client.get_collection("documents")
print(f"Vectors count: {info.vectors_count}")
print(f"Points count: {info.points_count}")
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({
url: 'http://localhost:6333'
});
await client.createCollection('documents', {
vectors: {
size: 1536,
distance: 'Cosine'
}
});
await client.createCollection('multimodal', {
vectors: {
text: { size: 1536, distance: 'Cosine' },
image: { size: 512, distance: 'Euclid' }
}
});
const collections = await client.getCollections();
collections.collections.forEach(collection => {
console.log('Collection:', collection.name);
});
info = client.();
.(, info.);
.(, info.);
Inserting Points
client.upsert(
collection_name="documents",
points=[
PointStruct(
id=1,
vector=[0.1, 0.2, 0.3, ...],
payload={
"title": "Document 1",
"category": "technology",
"date": "2024-01-01"
}
)
]
)
client.upsert(
collection_name="documents",
points=[
PointStruct(id=1, vector=vector1, payload={"title": "Doc 1", "category": "tech"}),
PointStruct(id=2, vector=vector2, payload={"title": "Doc 2", "category": "science"}),
PointStruct(id=3, vector=vector3, payload={"title": "Doc 3", "category": "tech"}),
]
)
from qdrant_client.models import Batch
def insert_in_batches(points, batch_size=100):
for i in range(0, len(points), batch_size):
batch = points[i:i + batch_size]
client.upsert(
collection_name="documents",
points=Batch(
ids=[p. p batch],
vectors=[p.vector p batch],
payloads=[p.payload p batch]
)
)
await client.upsert('documents', {
points: [{
id: 1,
vector: [0.1, 0.2, 0.3, ...],
payload: {
title: 'Document 1',
category: 'technology',
date: '2024-01-01'
}
}]
});
await client.upsert('documents', {
points: [
{ id: 1, vector: vector1, payload: { title: 'Doc 1', category: 'tech' } },
{ id: 2, vector: vector2, payload: { title: 'Doc 2', category: 'science' } },
{ id: 3, vector: vector3, payload: { title: 'Doc 3', category: 'tech' } }
]
});
await client.upsert('multimodal', {
points: [{
id: 1,
: {
: textVector,
: imageVector
},
: {
: ,
:
}
}]
});
Querying
results = client.search(
collection_name="documents",
query_vector=query_vector,
limit=10,
with_payload=True
)
for result in results:
print(f"ID: {result.id}, Score: {result.score}")
print(f"Payload: {result.payload}")
results = client.search(
collection_name="documents",
query_vector=query_vector,
query_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="technology")
),
FieldCondition(
key="date",
range=Range(
gte="2024-01-01"
)
)
]
),
limit=10,
with_payload=True
)
results = client.search(
collection_name="multimodal",
query_vector=NamedVector(
name="text",
vector=query_vector
),
limit=10
)
from qdrant_client.models import SearchRequest
results = client.search_batch(
collection_name="documents",
requests=[
SearchRequest(
vector=NamedVector(name="text", vector=query_vector),
limit=10,
with_payload=True
),
SearchRequest(
vector=NamedVector(name="image", vector=image_query_vector),
limit=10,
with_payload=True
)
]
)
const results = await client.search('documents', {
vector: queryVector,
limit: 10,
withPayload: true
});
results.forEach(result => {
console.log(`ID: ${result.id}, Score: ${result.score}`);
console.log('Payload:', result.payload);
});
const results = await client.search('documents', {
vector: queryVector,
queryFilter: {
must: [
{
key: 'category',
match: { value: 'technology' }
},
{
key: 'date',
range: { gte: '2024-01-01' }
}
]
},
limit: 10,
withPayload: true
});
const results = await client.search('multimodal', {
vector: {
name: 'text',
: queryVector
},
:
});
results = client.(, [
{
: {
: ,
: queryVector
},
:
},
{
: {
: ,
: imageQueryVector
},
:
}
]);
Filtering
filter = Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="technology")
)
]
)
filter = Filter(
must=[
FieldCondition(
key="price",
range=Range(
gte=100,
lte=1000
)
)
]
)
filter = Filter(
should=[
FieldCondition(
key="category",
match=MatchValue(value="technology")
),
FieldCondition(
key="category",
match=MatchValue(value="science")
)
],
min_count=1
)
filter = Filter(
must=[
FieldCondition(
key="metadata.category",
match=MatchValue(value="technology")
)
]
)
filter = Filter(
must_not=[
FieldCondition(
key="deleted_at",
is_null=True
)
]
)
const filter = {
must: [
{
key: 'category',
match: { value: 'technology' }
}
]
};
const filter = {
must: [
{
key: 'price',
range: { gte: 100, lte: 1000 }
}
]
};
const filter = {
should: [
{
key: 'category',
match: { value: 'technology' }
},
{
key: 'category',
match: { value: 'science' }
}
],
minCount: 1
};
const filter = {
must: [
{
key: 'metadata.category',
match: { value: 'technology' }
}
]
};
Weaviate
Schema Setup
import weaviate
from weaviate import Client
client = Client("http://localhost:8080")
schema = {
"classes": [
{
"class": "Document",
"description": "A document",
"vectorizer": "text2vec-openai",
"properties": [
{
"name": "title",
"dataType": ["string"],
"description": "The title of document"
},
{
"name": "content",
"dataType": ["text"],
"description": "The content of document"
},
{
"name": "category",
"dataType": ["string"],
"description": "The category of document"
},
{
"name": "date",
"dataType": ["date"],
"description": "The date of document"
},
{
"name": "metadata",
"dataType": [],
:
}
]
}
]
}
client.schema.create(schema)
schema = client.schema.get()
(schema)
import weaviate, { WeaviateClient } from 'weaviate-ts-client';
const client: WeaviateClient = weaviate.client({
scheme: 'http',
host: 'localhost:8080',
});
const schema = {
classes: [
{
class: 'Document',
description: 'A document',
vectorizer: 'text2vec-openai',
properties: [
{
name: 'title',
dataType: ['string'],
description: 'The title of document'
},
{
name: 'content',
dataType: ['text'],
description: 'The content of document'
},
{
name: 'category',
dataType: ['string'],
description: 'The category of document'
},
{
name: 'date',
dataType: ['date'],
description: 'The date of document'
},
{
: ,
: [],
:
}
]
}
]
};
client.
.()
.(schema.[])
.();
retrievedSchema = client..().();
.(retrievedSchema);
Inserting Data
client.data_object.create(
class_name="Document",
data_object={
"title": "Document 1",
"content": "This is content of document 1",
"category": "technology",
"date": "2024-01-01T00:00:00Z",
"metadata": {
"author": "John Doe",
"tags": ["tech", "ai"]
}
}
)
objects = [
{
"title": "Document 1",
"content": "Content 1",
"category": "technology"
},
{
"title": "Document 2",
"content": "Content 2",
"category": "science"
}
]
for obj in objects:
client.data_object.create(
class_name="Document",
data_object=obj
)
client.data_object.create(
class_name="Document",
data_object={
"title": "Document 1",
"content": "Content 1"
},
vector=[0.1, 0.2, 0.3, ...]
)
from weaviate.batch import Batch
with Batch(client) as batch:
obj objects:
batch.add_data_object(
data_object=obj,
class_name=
)
await client.data
.creator()
.withClassName('Document')
.withProperties({
title: 'Document 1',
content: 'This is content of document 1',
category: 'technology',
date: '2024-01-01T00:00:00Z',
metadata: {
author: 'John Doe',
tags: ['tech', 'ai']
}
})
.do();
const objects = [
{
title: 'Document 1',
content: 'Content 1',
category: 'technology'
},
{
title: 'Document 2',
content: 'Content 2',
category: 'science'
}
];
for (const obj of objects) {
await client.data
.creator()
.withClassName('Document')
.withProperties(obj)
.do();
}
await client.data
.creator()
.()
.({
: ,
:
})
.([, , , ...])
.();
Querying
results = client.query.get(
class_name="Document",
properties=["title", "content", "category"]
).with_near_text({
"concepts": ["artificial intelligence"],
"distance": 0.7
}).with_limit(10).do()
for result in results["data"]["Get"]["Document"]:
print(f"Title: {result['title']}")
print(f"Distance: {result['_additional']['distance']}")
results = client.query.get(
class_name="Document",
properties=["title", "content"]
).with_hybrid(
query="artificial intelligence",
alpha=0.7,
vector=query_vector
).with_limit(10).do()
results = client.query.get(
class_name="Document",
properties=["title", "content", "category"]
).with_where({
"path": ["category"],
"operator": "Equal",
"valueString": "technology"
}).with_near_text({
"concepts": ["AI"]
}).with_limit(10).do()
results = client.query.get(
class_name=,
properties=[, ]
).with_where({
: ,
: [
{
: [],
: ,
:
},
{
: [],
: ,
:
}
]
}).with_near_text({
: []
}).do()
const results = await client.graphql
.get()
.withClassName('Document')
.withFields('title content category _additional { distance }')
.withNearText({
concepts: ['artificial intelligence'],
distance: 0.7
})
.withLimit(10)
.do();
console.log(results.data.Get.Document);
const results = await client.graphql
.get()
.withClassName('Document')
.withFields('title content _additional { distance }')
.withHybrid({
query: 'artificial intelligence',
alpha: 0.7,
vector: queryVector
})
.withLimit(10)
.do();
const results = await client.graphql
.get()
.withClassName('Document')
.()
.({
: [],
: ,
:
})
.({
: []
})
.()
.();
results = client.
.()
.()
.()
.({
: ,
: [
{
: [],
: ,
:
},
{
: [],
: ,
:
}
]
})
.({
: []
})
.();
Embedding Strategies
Text Embeddings
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
def get_embedding(text: str) -> list:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def get_embeddings(texts: list) -> list:
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
return [item.embedding for item in response.data]
def chunk_text(text: str, chunk_size: int = 1000, overlap: int = 200) -> list:
chunks = []
for i in range(0, len(text), chunk_size - overlap):
chunks.append(text[i:i + chunk_size])
return chunks
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: 'your-api-key'
});
async function getEmbedding(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: text
});
return response.data[0].embedding;
}
async function getEmbeddings(texts: string[]): Promise<number[][]> {
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: texts
});
return response.data.map(item => item.embedding);
}
function (): [] {
: [] = [];
( i = ; i < text.; i += chunkSize - overlap) {
chunks.(text.(i, i + chunkSize));
}
chunks;
}
Image Embeddings
from PIL import Image
import clip
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
def get_image_embedding(image_path: str) -> list:
image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
with torch.no_grad():
image_features = model.encode_image(image)
return image_features.cpu().numpy().tolist()[0]
def get_image_embeddings(image_paths: list) -> list:
images = torch.stack([preprocess(Image.open(path)) for path in image_paths]).to(device)
with torch.no_grad():
image_features = model.encode_image(images)
return image_features.cpu().numpy().tolist()
Multimodal Embeddings
def get_text_embedding(text: str) -> list:
text_tokens = clip.tokenize([text]).to(device)
with torch.no_grad():
text_features = model.encode_text(text_tokens)
return text_features.cpu().numpy().tolist()[0]
def get_image_embedding(image_path: str) -> list:
image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
with torch.no_grad():
image_features = model.encode_image(image)
return image_features.cpu().numpy().tolist()[0]
import numpy as np
def cosine_similarity(vec1: list, vec2: list) -> float:
v1 = np.array(vec1)
v2 = np.array(vec2)
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
Similarity Search
Cosine Similarity
import numpy as np
def cosine_similarity(vec1: list, vec2: list) -> float:
v1 = np.array(vec1)
v2 = np.array(vec2)
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
vector_a = [1, 2, 3]
vector_b = [2, 4, 6]
similarity = cosine_similarity(vector_a, vector_b)
print(f"Cosine similarity: {similarity}")
Euclidean Distance
import numpy as np
def euclidean_distance(vec1: list, vec2: list) -> float:
v1 = np.array(vec1)
v2 = np.array(vec2)
return np.linalg.norm(v1 - v2)
vector_a = [1, 2, 3]
vector_b = [2, 4, 6]
distance = euclidean_distance(vector_a, vector_b)
print(f"Euclidean distance: {distance}")
Dot Product
import numpy as np
def dot_product(vec1: list, vec2: list) -> float:
v1 = np.array(vec1)
v2 = np.array(vec2)
return np.dot(v1, v2)
vector_a = [1, 2, 3]
vector_b = [2, 4, 6]
product = dot_product(vector_a, vector_b)
print(f"Dot product: {product}")
Performance Optimization
Batch Operations
def upsert_in_batches(vectors, batch_size=100):
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i + batch_size]
index.upsert(vectors=batch)
from qdrant_client.models import Batch
def insert_in_batches(points, batch_size=100):
for i in range(0, len(points), batch_size):
batch = points[i:i + batch_size]
client.upsert(
collection_name="documents",
points=Batch(
ids=[p.id for p in batch],
vectors=[p.vector for p in batch],
payloads=[p.payload for p in batch]
)
)
Indexing Strategies
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(
size=1536,
distance=Distance.COSINE,
hnsw_config={
"m": 16,
"ef_construct": 100
}
)
)
Caching
import hashlib
import pickle
from functools import lru_cache
def get_embedding_cache_key(text: str) -> str:
return hashlib.md5(text.encode()).hexdigest()
@lru_cache(maxsize=1000)
def get_cached_embedding(text: str) -> list:
cache_key = get_embedding_cache_key(text)
return get_embedding(text)
Production Considerations
Scaling
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
shard_number=4
)
Monitoring
stats = index.describe_index_stats()
print(f"Total vectors: {stats['total_vector_count']}")
print(f"Dimension: {stats['dimension']}")
info = client.get_collection("documents")
print(f"Vectors count: {info.vectors_count}")
print(f"Points count: {info.points_count}")
cluster_status = client.cluster.get_nodes()
print(cluster_status)
Backup and Recovery
client.create_snapshot(collection_name="documents")
Cost Optimization
Choosing Right Service
- Pinecone: Managed service, easy setup, good for production
- Qdrant: Open-source, self-hosted option, flexible
- Weaviate: Open-source, GraphQL API, good for multimodal
Storage Optimization
Query Optimization
Best Practices
-
Choose Appropriate Embedding Model
- For text: OpenAI text-embedding-3-small or ada-002
- For images: CLIP, DINO, or domain-specific models
- For multimodal: CLIP or similar models
-
Preprocess Data
- Clean text by removing special characters
- Normalize whitespace
- Convert to lowercase for consistency
-
Use Appropriate Chunking
- Chunk long documents
- Use semantic chunking
- Maintain context between chunks
-
Implement Caching
- Cache embeddings to reduce API calls
- Cache query results
- Use Redis for caching
-
Monitor Performance
- Track query latency
- Monitor storage usage
- Set up alerts for anomalies
-
Use Filters Effectively
- Use metadata filters to reduce search space
- Combine vector search with keyword search
- Use hybrid search when appropriate
-
Handle Errors Gracefully
- Implement retry logic
- Handle rate limits
- Log errors for debugging
-
Test Thoroughly
- Test with real data
- Evaluate search quality
- Benchmark performance
-
Security
- Use authentication in production
- Encrypt sensitive data
- Follow principle of least privilege
-
Scalability
- Design for horizontal scaling
- Use appropriate sharding strategies
- Monitor resource usage
Related Skills