Vector Stores
Production-ready vector storage for scalable RAG applications.
Overview
Vector stores provide efficient similarity search for embeddings. Meloqui supports three implementations:
- InMemoryVectorStore - Zero-dependency, for development and testing
- PineconeVectorStore - Cloud-native, serverless scaling
- QdrantVectorStore - Self-hosted, privacy-first
Quick Start
Development (InMemory)
import { InMemoryVectorStore, OpenAIEmbeddings } from 'meloqui';
const embeddings = new OpenAIEmbeddings();
const vectorStore = new InMemoryVectorStore({
dimensions: embeddings.dimensions
});
// Store vectors
const text = 'Hello world';
const embedding = await embeddings.embed(text);
await vectorStore.upsert([{
id: 'doc-1',
values: embedding,
metadata: { text }
}]);
// Search
const queryEmbedding = await embeddings.embed('greeting');
const results = await vectorStore.search(queryEmbedding, { topK: 5 });
console.log(results[0].metadata?.text); // 'Hello world'Production (Pinecone)
import {
PineconeVectorStore,
InMemoryDocumentStore,
OpenAIEmbeddings
} from 'meloqui';
const vectorStore = new PineconeVectorStore({
indexName: 'my-docs',
dimensions: 1536
});
const documentStore = new InMemoryDocumentStore({
embeddingProvider: new OpenAIEmbeddings(),
vectorStore
});
await documentStore.addDocuments([
{ content: '...', source: 'manual.pdf' }
]);Self-Hosted (Qdrant)
import { QdrantVectorStore, InMemoryDocumentStore } from 'meloqui';
const vectorStore = new QdrantVectorStore({
url: 'http://qdrant.internal:6333',
collectionName: 'support-docs',
dimensions: 1536
});
const documentStore = new InMemoryDocumentStore({
embeddingProvider: new OpenAIEmbeddings(),
vectorStore
});IVectorStore Interface
All vector stores implement the same interface:
interface IVectorStore {
upsert(vectors: Vector[], namespace?: string): Promise<void>;
search(vector: number[], options?: VectorSearchOptions, namespace?: string): Promise<VectorSearchResult[]>;
delete(ids: string[], namespace?: string): Promise<void>;
clear(namespace?: string): Promise<void>;
readonly dimensions: number;
}InMemoryVectorStore
Zero-dependency implementation for development.
When to use:
- Development and testing
- Small datasets (<50k vectors)
- Prototyping
Configuration:
const store = new InMemoryVectorStore({
dimensions: 1536,
metric: 'cosine' // or 'euclidean', 'dotProduct'
});Metrics:
cosine- Cosine similarity (default, best for normalized embeddings)euclidean- Euclidean distance (good for spatial data)dotProduct- Dot product (fast, for already-normalized vectors)
PineconeVectorStore
Cloud vector database with automatic scaling.
When to use:
- Production applications
- Large datasets (millions of vectors)
- Serverless deployment
Setup:
Install peer dependency:
bashnpm install @pinecone-database/pineconeSet API key:
bashexport PINECONE_API_KEY=your-api-keyCreate index in Pinecone dashboard
Configuration:
const store = new PineconeVectorStore({
apiKey: process.env.PINECONE_API_KEY, // or auto-detected
indexName: 'my-index',
dimensions: 1536,
batchSize: 100 // optional, for rate limiting
});Features:
- Automatic batching for API limits
- Namespace support for multi-tenancy
- Metadata filtering
QdrantVectorStore
Self-hosted vector database for privacy-sensitive deployments.
When to use:
- On-premise deployments
- Privacy requirements
- Cost optimization
Setup:
Install peer dependency:
bashnpm install @qdrant/js-client-restRun Qdrant (Docker):
bashdocker run -p 6333:6333 qdrant/qdrant
Configuration:
const store = new QdrantVectorStore({
url: 'http://localhost:6333',
collectionName: 'my-collection',
dimensions: 1536,
distance: 'Cosine', // or 'Euclid', 'Dot'
createIfNotExists: true
});Features:
- Auto-creates collections with HNSW index
- Namespace via payload filtering
- Advanced metadata filtering
Search Options
All vector stores support the same search options:
const results = await store.search(queryVector, {
topK: 10, // Number of results
minScore: 0.8, // Minimum similarity threshold
includeMetadata: true, // Include metadata in results
includeValues: false, // Include vector values
filter: { // Metadata filter (implementation-specific)
category: 'tech'
}
});Multi-Tenancy
Use namespaces to isolate data:
// Store per-tenant data
await store.upsert(customerDocs, `tenant-${customerId}`);
// Search within tenant
const results = await store.search(
queryVector,
{ topK: 5 },
`tenant-${customerId}`
);
// Clear tenant data
await store.clear(`tenant-${customerId}`);Integration with DocumentStore
Document stores can delegate vector operations:
const documentStore = new InMemoryDocumentStore({
embeddingProvider: new OpenAIEmbeddings(),
vectorStore: new PineconeVectorStore({
indexName: 'docs',
dimensions: 1536
})
});
// Chunks stored locally, vectors in Pinecone
await documentStore.addDocuments([...]);
// Search uses Pinecone
const results = await documentStore.search('query');Error Handling
Vector store errors include context:
try {
await store.upsert(vectors);
} catch (error) {
if (error instanceof VectorStoreError) {
console.log(error.store); // 'pinecone'
console.log(error.operation); // 'upsert'
console.log(error.isRetryable); // true for network errors
}
}Comparison
| Feature | InMemory | Pinecone | Qdrant |
|---|---|---|---|
| Setup | None | Cloud account | Docker/self-host |
| Cost | Free | Usage-based | Infrastructure |
| Scale | <50k vectors | Millions | Millions |
| Latency | Microseconds | ~50ms | ~10ms (local) |
| Namespaces | ✓ | ✓ | ✓ (via payload) |
| Metadata filter | Basic | Advanced | Advanced |
| Dependencies | None | Peer | Peer |
Best Practices
Development:
- Use InMemoryVectorStore for local testing
- Keep test datasets small (<1k vectors)
Production:
- Pinecone for managed, serverless
- Qdrant for self-hosted, cost-optimized
- Always specify dimensions explicitly
- Use namespaces for multi-tenancy
- Monitor vector store latency
Performance:
- Batch upserts when possible
- Use
includeValues: falseunless needed - Set appropriate
minScorethresholds - Consider caching frequent queries
Migration Guide
From Custom Vector Storage
If you're currently managing vectors manually, migrating to IVectorStore provides:
- Consistent API across different backends
- Built-in namespace support for multi-tenancy
- Automatic batching and optimization
- Error handling with retry support
Step 1: Identify your current approach
// Before: Manual vector management
const vectors = new Map<string, number[]>();
vectors.set('doc-1', embedding1);
// Search with custom logic
const results = Array.from(vectors.entries())
.map(([id, vec]) => ({ id, score: cosineSim(queryVec, vec) }))
.sort((a, b) => b.score - a.score)
.slice(0, 10);Step 2: Choose implementation
- Development/Testing: InMemoryVectorStore (drop-in replacement)
- Production (<1M vectors): PineconeVectorStore (managed, simple)
- Production (self-hosted): QdrantVectorStore (cost-effective, private)
Step 3: Migrate data
import { InMemoryVectorStore } from 'meloqui';
// Initialize new store
const store = new InMemoryVectorStore({ dimensions: 1536 });
// Migrate existing vectors
const vectorsToMigrate = Array.from(vectors.entries()).map(([id, values]) => ({
id,
values,
metadata: existingMetadata.get(id) // Add metadata if available
}));
await store.upsert(vectorsToMigrate);
// Replace search logic
const results = await store.search(queryVector, { topK: 10 });Step 4: Update document store integration
// Before: Separate vector and document management
const documentStore = new InMemoryDocumentStore({
embeddingProvider: new OpenAIEmbeddings()
});
// After: Delegate vectors to vector store
const documentStore = new InMemoryDocumentStore({
embeddingProvider: new OpenAIEmbeddings(),
vectorStore: store // Optional delegation
});From Pinecone SDK Directly
If you're using @pinecone-database/pinecone directly:
// Before: Direct Pinecone usage
import { Pinecone } from '@pinecone-database/pinecone';
const pc = new Pinecone({ apiKey });
const index = pc.index('my-index');
await index.upsert([{ id, values, metadata }]);
// After: Via IVectorStore
import { PineconeVectorStore } from 'meloqui';
const store = new PineconeVectorStore({
indexName: 'my-index',
dimensions: 1536
});
await store.upsert([{ id, values, metadata }]);Benefits:
- Automatic batching (no need to split upserts manually)
- Consistent error handling across all operations
- Namespace parameter standardization
- Swap to other backends without code changes
From Qdrant Client Directly
If you're using @qdrant/js-client-rest directly:
// Before: Direct Qdrant usage
import { QdrantClient } from '@qdrant/js-client-rest';
const client = new QdrantClient({ url, apiKey });
await client.upsert('collection', {
points: [{ id, vector: values, payload: metadata }]
});
// After: Via IVectorStore
import { QdrantVectorStore } from 'meloqui';
const store = new QdrantVectorStore({
url,
collectionName: 'collection',
dimensions: 1536
});
await store.upsert([{ id, values, metadata }]);Benefits:
- Auto-creates collections with optimal HNSW configuration
- Namespace abstraction via payload filtering
- Consistent with Pinecone/InMemory APIs
See Also
- RAG Guide - Document stores and chunking
- API Reference - Full type definitions
