Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.
Vector Database Best Practices
Optimize your vector database usage for performance, cost, and accuracy.
Embedding Best Practices
Choose the Right Embedding Model
| Model | Dimensions | Speed | Quality | Cost | Best For |
|---|---|---|---|---|---|
| text-embedding-ada-002 | 1536 | Fast | Good | Low | General purpose |
| text-embedding-3-small | 1536 | Fast | Better | Low | Cost-effective |
| text-embedding-3-large | 3072 | Slow | Best | High | High accuracy |
| all-MiniLM-L6-v2 | 384 | Very Fast | Good | Free | Low latency |
Normalize Your Vectors
For cosine similarity, ensure vectors are normalized:
- TypeScript
- Java
- Go
- .NET
function normalizeVector(vector: number[]): number[] {
const magnitude = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
return vector.map((v) => v / magnitude);
}
function normalizeVector(vector: number[]): number[] Map.of(
Map<String, Object> magnitude = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
return vector.map((v) => v / magnitude);
)
function normalizeVector(vector: number[]): number[] {
magnitude := Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
return vector.map((v) => v / magnitude);
}
function normalizeVector(vector: number[]): number[] {
var magnitude = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
return vector.map((v) => v / magnitude);
}
Batch Embedding Generation
Process embeddings in batches for efficiency:
- TypeScript
- Java
- Go
- .NET
async function generateEmbeddings(texts: string[]): Promise<number[][]> {
const batchSize = 100;
const embeddings: number[][] = [];
for (let i = 0; i < texts.length; i += batchSize) {
const batch = texts.slice(i, i + batchSize);
const batchEmbeddings = await embeddingProvider.embedBatch(batch);
embeddings.push(...batchEmbeddings);
}
return embeddings;
}
async function generateEmbeddings(texts: string[]): Promise<number[][]> Map.of(
Map<String, Object> batchSize = 100;
Map<String, Object> embeddings: number[][] = [];
for (Map<String, Object> i = 0; i < texts.length; i += batchSize) Map.of(
Map<String, Object> batch = texts.slice(i, i + batchSize);
Map<String, Object> batchEmbeddings = embeddingProvider.embedBatch(batch);
embeddings.push(...batchEmbeddings);
)
return embeddings;
)
async function generateEmbeddings(texts: string[]): Promise<number[][]> {
batchSize := 100;
const embeddings: number[][] = [];
for (i := 0; i < texts.length; i += batchSize) {
batch := texts.slice(i, i + batchSize);
batchEmbeddings := embeddingProvider.embedBatch(batch);
embeddings.push(...batchEmbeddings);
}
return embeddings;
}
async function generateEmbeddings(texts: string[]): Promise<number[][]> {
var batchSize = 100;
var embeddings: number[][] = [];
for (var i = 0; i < texts.length; i += batchSize) {
var batch = texts.slice(i, i + batchSize);
var batchEmbeddings = await embeddingProvider.embedBatch(batch);
embeddings.push(...batchEmbeddings);
}
return embeddings;
}
Data Management
Use Meaningful IDs
- TypeScript
- Java
- Go
- .NET
// Good - meaningful, queryable IDs
await ductape.vector.upsert({
tag: 'my-vectors',
vectors: [
{ id: 'doc:product:12345', values: [...], metadata: {...} },
{ id: 'doc:article:67890', values: [...], metadata: {...} },
],
});
// Bad - random IDs that can't be reconstructed
await ductape.vector.upsert({
tag: 'my-vectors',
vectors: [
{ id: crypto.randomUUID(), values: [...], metadata: {...} },
],
});
// Good - meaningful, queryable IDs
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
vectors: [
Map.of( "id", "doc:"product", 12345", values: [...], metadata: Map.of(...) ),
Map.of( "id", "doc:"article", 67890", values: [...], metadata: Map.of(...) ),
]
));
// Bad - random IDs that can't be reconstructed
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
vectors: [
Map.of( id: crypto.randomUUID(), values: [...], metadata: Map.of(...) ),
]
));
// Good - meaningful, queryable IDs
client.vector.upsert({
"tag": "my-vectors",
vectors: [
{ "id": "doc:"product": 12345", values: [...], metadata: {...} },
{ "id": "doc:"article": 67890", values: [...], metadata: {...} },
],
});
// Bad - random IDs that can't be reconstructed
client.vector.upsert({
"tag": "my-vectors",
vectors: [
{ id: crypto.randomUUID(), values: [...], metadata: {...} },
],
});
// Good - meaningful, queryable IDs
await ductape.vector.upsert({
["tag"] = "my-vectors",
vectors: [
{ ["id"] = "doc:["product"] = 12345", values: [...], metadata: {...} },
{ ["id"] = "doc:["article"] = 67890", values: [...], metadata: {...} },
],
});
// Bad - random IDs that can't be reconstructed
await ductape.vector.upsert({
["tag"] = "my-vectors",
vectors: [
{ id: crypto.randomUUID(), values: [...], metadata: {...} },
],
});
Structure Metadata for Filtering
Design metadata to support your query patterns:
- TypeScript
- Java
- Go
- .NET
// Good - filterable, typed metadata
{
id: 'doc-123',
values: [...],
metadata: {
type: 'article', // Categorical - exact match
category: 'technology', // Categorical - exact match
published_at: 1703980800, // Numeric - range queries
word_count: 1500, // Numeric - range queries
tags: ['ai', 'ml'], // Array - $in queries
is_featured: true, // Boolean - exact match
},
}
// Good - filterable, typed metadata
Map.of(
"id", "doc-123",
values: [...],
metadata: Map.of(
"type", "article", // Categorical - exact match
"category", "technology", // Categorical - exact match
"published_at", 1703980800, // Numeric - range queries
"word_count", 1500, // Numeric - range queries
tags: ['ai', 'ml'], // Array - $in queries
"is_featured", true, // Boolean - exact match
)
)
// Good - filterable, typed metadata
{
"id": "doc-123",
values: [...],
metadata: {
"type": "article", // Categorical - exact match
"category": "technology", // Categorical - exact match
"published_at": 1703980800, // Numeric - range queries
"word_count": 1500, // Numeric - range queries
tags: ['ai', 'ml'], // Array - $in queries
"is_featured": true, // Boolean - exact match
},
}
// Good - filterable, typed metadata
{
["id"] = "doc-123",
values: [...],
metadata: {
["type"] = "article", // Categorical - exact match
["category"] = "technology", // Categorical - exact match
["published_at"] = 1703980800, // Numeric - range queries
["word_count"] = 1500, // Numeric - range queries
tags: ['ai', 'ml'], // Array - $in queries
["is_featured"] = true, // Boolean - exact match
},
}
Avoid Storing Large Data in Metadata
- TypeScript
- Java
- Go
- .NET
// Good - store references, not full content
{
id: 'doc-123',
values: [...],
metadata: {
title: 'Introduction to ML',
summary: 'A brief overview of...', // Short summary
content_id: 'cms:article:123', // Reference to full content
url: '/articles/intro-to-ml',
},
}
// Bad - storing full content in metadata
{
id: 'doc-123',
values: [...],
metadata: {
title: 'Introduction to ML',
full_content: '... 10,000 words ...', // Too large!
},
}
// Good - store references, not full content
Map.of(
"id", "doc-123",
values: [...],
metadata: Map.of(
"title", "Introduction to ML",
"summary", "A brief overview of...", // Short summary
"content_id", "cms:"article", 123", // Reference to full content
"url", "/articles/intro-to-ml"
)
)
// Bad - storing full content in metadata
Map.of(
"id", "doc-123",
values: [...],
metadata: Map.of(
"title", "Introduction to ML",
"full_content", "... 10,000 words ...", // Too large!
)
)
// Good - store references, not full content
{
"id": "doc-123",
values: [...],
metadata: {
"title": "Introduction to ML",
"summary": "A brief overview of...", // Short summary
"content_id": "cms:"article": 123", // Reference to full content
"url": "/articles/intro-to-ml",
},
}
// Bad - storing full content in metadata
{
"id": "doc-123",
values: [...],
metadata: {
"title": "Introduction to ML",
"full_content": "... 10,000 words ...", // Too large!
},
}
// Good - store references, not full content
{
["id"] = "doc-123",
values: [...],
metadata: {
["title"] = "Introduction to ML",
["summary"] = "A brief overview of...", // Short summary
["content_id"] = "cms:["article"] = 123", // Reference to full content
["url"] = "/articles/intro-to-ml",
},
}
// Bad - storing full content in metadata
{
["id"] = "doc-123",
values: [...],
metadata: {
["title"] = "Introduction to ML",
["full_content"] = "... 10,000 words ...", // Too large!
},
}
Performance Optimization
Use Namespaces for Data Isolation
- TypeScript
- Java
- Go
- .NET
// Separate data by tenant, user, or category
await ductape.vector.upsert({
tag: 'my-vectors',
namespace: `tenant:${tenantId}`,
vectors: [...],
});
// Query within namespace
const results = await ductape.vector.query({
tag: 'my-vectors',
namespace: `tenant:${tenantId}`,
vector: queryVector,
topK: 10,
});
// Separate data by tenant, user, or category
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
namespace: `tenant:$Map.of(tenantId)`,
vectors: [...]
));
// Query within namespace
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
namespace: `tenant:$Map.of(tenantId)`,
vector: queryVector,
"topK", 10
));
import "context"
// Separate data by tenant, user, or category
client.vector.upsert({
"tag": "my-vectors",
namespace: `tenant:${tenantId}`,
vectors: [...],
});
// Query within namespace
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
namespace: `tenant:${tenantId}`,
vector: queryVector,
"topK": 10,
});
// Separate data by tenant, user, or category
await ductape.vector.upsert({
["tag"] = "my-vectors",
namespace: `tenant:${tenantId}`,
vectors: [...],
});
// Query within namespace
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
namespace: `tenant:${tenantId}`,
vector: queryVector,
["topK"] = 10,
});
Optimize TopK
Return only what you need:
- TypeScript
- Java
- Go
- .NET
// Start conservative
const results = await ductape.vector.query({
tag: 'my-vectors',
vector: queryVector,
topK: 5, // Start small
minScore: 0.7, // Filter low-quality results
});
// Only increase if needed
if (results.matches.length < 5) {
// Expand search with lower threshold
}
// Start conservative
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
vector: queryVector,
"topK", 5, // Start small
"minScore", 0.7, // Filter low-quality results
));
// Only increase if needed
if (results.matches.length < 5) Map.of(
// Expand search with lower threshold
)
import "context"
// Start conservative
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
vector: queryVector,
"topK": 5, // Start small
"minScore": 0.7, // Filter low-quality results
});
// Only increase if needed
if (results.matches.length < 5) {
// Expand search with lower threshold
}
// Start conservative
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
vector: queryVector,
["topK"] = 5, // Start small
["minScore"] = 0.7, // Filter low-quality results
});
// Only increase if needed
if (results.matches.length < 5) {
// Expand search with lower threshold
}
Use Filters to Reduce Search Space
- TypeScript
- Java
- Go
- .NET
// Good - filter before similarity search
const results = await ductape.vector.query({
tag: 'my-vectors',
vector: queryVector,
topK: 10,
filter: {
category: 'electronics',
price: { $lt: 500 },
},
});
// Less efficient - filter after retrieval
const results = await ductape.vector.query({
tag: 'my-vectors',
vector: queryVector,
topK: 1000, // Over-fetch
});
const filtered = results.matches.filter(
(m) => m.metadata?.category === 'electronics'
);
// Good - filter before similarity search
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
vector: queryVector,
"topK", 10,
filter: Map.of(
"category", "electronics",
price: Map.of( $"lt", 500 )
)
));
// Less efficient - filter after retrieval
Map<String, Object> results = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
vector: queryVector,
"topK", 1000, // Over-fetch
));
Map<String, Object> filtered = results.matches.filter(
(m) => m.metadata?.category === 'electronics'
);
import "context"
// Good - filter before similarity search
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
vector: queryVector,
"topK": 10,
filter: {
"category": "electronics",
price: { $"lt": 500 },
},
});
// Less efficient - filter after retrieval
results := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
vector: queryVector,
"topK": 1000, // Over-fetch
});
filtered := results.matches.filter(
(m) => m.metadata?.category === 'electronics'
);
// Good - filter before similarity search
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
vector: queryVector,
["topK"] = 10,
filter: {
["category"] = "electronics",
price: { $["lt"] = 500 },
},
});
// Less efficient - filter after retrieval
var results = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
vector: queryVector,
["topK"] = 1000, // Over-fetch
});
var filtered = results.matches.filter(
(m) => m.metadata?.category === 'electronics'
);
Batch Operations
- TypeScript
- Java
- Go
- .NET
// Good - batch upserts
await ductape.vector.upsert({
tag: 'my-vectors',
vectors: allVectors, // Up to 100 vectors at once
});
// Bad - individual upserts
for (const vector of allVectors) {
await ductape.vector.upsert({
tag: 'my-vectors',
vectors: [vector],
});
}
// Good - batch upserts
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
vectors: allVectors, // Up to 100 vectors at once
));
// Bad - individual upserts
for (Map<String, Object> vector of allVectors) Map.of(
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
vectors: [vector]
));
)
// Good - batch upserts
client.vector.upsert({
"tag": "my-vectors",
vectors: allVectors, // Up to 100 vectors at once
});
// Bad - individual upserts
for (const vector of allVectors) {
client.vector.upsert({
"tag": "my-vectors",
vectors: [vector],
});
}
// Good - batch upserts
await ductape.vector.upsert({
["tag"] = "my-vectors",
vectors: allVectors, // Up to 100 vectors at once
});
// Bad - individual upserts
for (var vector of allVectors) {
await ductape.vector.upsert({
["tag"] = "my-vectors",
vectors: [vector],
});
}
Accuracy Optimization
Chunk Documents Appropriately
Break large documents into meaningful chunks:
- TypeScript
- Java
- Go
- .NET
function chunkDocument(
content: string,
chunkSize: number = 500,
overlap: number = 50
): string[] {
const chunks: string[] = [];
let start = 0;
while (start < content.length) {
const end = Math.min(start + chunkSize, content.length);
chunks.push(content.slice(start, end));
start = end - overlap;
}
return chunks;
}
// Better - chunk by semantic boundaries
function chunkBySentences(content: string, maxChunkSize: number): string[] {
const sentences = content.split(/[.!?]+/);
const chunks: string[] = [];
let currentChunk = '';
for (const sentence of sentences) {
if ((currentChunk + sentence).length > maxChunkSize) {
chunks.push(currentChunk.trim());
currentChunk = sentence;
} else {
currentChunk += sentence + '. ';
}
}
if (currentChunk) {
chunks.push(currentChunk.trim());
}
return chunks;
}
function chunkDocument(
content: string,
chunkSize: number = 500,
overlap: number = 50
): string[] Map.of(
Map<String, Object> chunks: string[] = [];
Map<String, Object> start = 0;
while (start < content.length) Map.of(
Map<String, Object> end = Math.min(start + chunkSize, content.length);
chunks.push(content.slice(start, end));
start = end - overlap;
)
return chunks;
)
// Better - chunk by semantic boundaries
function chunkBySentences(content: string, maxChunkSize: number): string[] Map.of(
Map<String, Object> sentences = content.split(/[.!?]+/);
Map<String, Object> chunks: string[] = [];
Map<String, Object> currentChunk = '';
for (Map<String, Object> sentence of sentences) Map.of(
if ((currentChunk + sentence).length > maxChunkSize) Map.of(
chunks.push(currentChunk.trim());
currentChunk = sentence;
) else Map.of(
currentChunk += sentence + '. ';
)
)
if (currentChunk) Map.of(
chunks.push(currentChunk.trim());
)
return chunks;
)
function chunkDocument(
content: string,
chunkSize: number = 500,
overlap: number = 50
): string[] {
const chunks: string[] = [];
start := 0;
while (start < content.length) {
end := Math.min(start + chunkSize, content.length);
chunks.push(content.slice(start, end));
start = end - overlap;
}
return chunks;
}
// Better - chunk by semantic boundaries
function chunkBySentences(content: string, maxChunkSize: number): string[] {
sentences := content.split(/[.!?]+/);
const chunks: string[] = [];
currentChunk := '';
for (const sentence of sentences) {
if ((currentChunk + sentence).length > maxChunkSize) {
chunks.push(currentChunk.trim());
currentChunk = sentence;
} else {
currentChunk += sentence + '. ';
}
}
if (currentChunk) {
chunks.push(currentChunk.trim());
}
return chunks;
}
function chunkDocument(
content: string,
chunkSize: number = 500,
overlap: number = 50
): string[] {
var chunks: string[] = [];
var start = 0;
while (start < content.length) {
var end = Math.min(start + chunkSize, content.length);
chunks.push(content.slice(start, end));
start = end - overlap;
}
return chunks;
}
// Better - chunk by semantic boundaries
function chunkBySentences(content: string, maxChunkSize: number): string[] {
var sentences = content.split(/[.!?]+/);
var chunks: string[] = [];
var currentChunk = '';
for (var sentence of sentences) {
if ((currentChunk + sentence).length > maxChunkSize) {
chunks.push(currentChunk.trim());
currentChunk = sentence;
} else {
currentChunk += sentence + '. ';
}
}
if (currentChunk) {
chunks.push(currentChunk.trim());
}
return chunks;
}
Include Context in Chunks
- TypeScript
- Java
- Go
- .NET
// Add document context to each chunk
function createChunksWithContext(doc: Document): VectorRecord[] {
const chunks = chunkDocument(doc.content);
return chunks.map((chunk, index) => ({
id: `${doc.id}:chunk:${index}`,
values: generateEmbedding(chunk),
metadata: {
document_id: doc.id,
document_title: doc.title,
chunk_index: index,
total_chunks: chunks.length,
content: chunk,
// Add surrounding context
context: `From "${doc.title}": ${chunk}`,
},
}));
}
// Add document context to each chunk
function createChunksWithContext(doc: Document): VectorRecord[] Map.of(
Map<String, Object> chunks = chunkDocument(doc.content);
return chunks.map((chunk, index) => (Map.of(
id: `$Map.of(doc.id):chunk:$Map.of(index)`,
values: generateEmbedding(chunk),
metadata: Map.of(
document_id: doc.id,
document_title: doc.title,
chunk_index: index,
total_chunks: chunks.length,
content: chunk,
// Add surrounding context
context: `From "$Map.of(doc.title)": $Map.of(chunk)`
)
)));
)
// Add document context to each chunk
function createChunksWithContext(doc: Document): VectorRecord[] {
chunks := chunkDocument(doc.content);
return chunks.map((chunk, index) => ({
id: `${doc.id}:chunk:${index}`,
values: generateEmbedding(chunk),
metadata: {
document_id: doc.id,
document_title: doc.title,
chunk_index: index,
total_chunks: chunks.length,
content: chunk,
// Add surrounding context
context: `From "${doc.title}": ${chunk}`,
},
}));
}
// Add document context to each chunk
function createChunksWithContext(doc: Document): VectorRecord[] {
var chunks = chunkDocument(doc.content);
return chunks.map((chunk, index) => ({
id: `${doc.id}:chunk:${index}`,
values: generateEmbedding(chunk),
metadata: {
document_id: doc.id,
document_title: doc.title,
chunk_index: index,
total_chunks: chunks.length,
content: chunk,
// Add surrounding context
context: `From "${doc.title}": ${chunk}`,
},
}));
}
Use Hybrid Search
Combine vector similarity with keyword matching:
- TypeScript
- Java
- Go
- .NET
async function hybridSearch(query: string) {
// 1. Get semantic results
const semanticResults = await ductape.vector.query({
tag: 'my-vectors',
vector: await generateEmbedding(query),
topK: 20,
includeMetadata: true,
});
// 2. Boost results that contain exact keywords
const keywords = query.toLowerCase().split(' ');
const boostedResults = semanticResults.matches.map((match) => {
const content = match.metadata?.content?.toLowerCase() || '';
const keywordMatches = keywords.filter((k) => content.includes(k)).length;
const boost = 1 + (keywordMatches / keywords.length) * 0.2;
return {
...match,
score: match.score * boost,
};
});
// 3. Re-rank by boosted score
return boostedResults.sort((a, b) => b.score - a.score).slice(0, 10);
}
async function hybridSearch(query: string) Map.of(
// 1. Get semantic results
Map<String, Object> semanticResults = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
vector: generateEmbedding(query),
"topK", 20,
"includeMetadata", true
));
// 2. Boost results that contain exact keywords
Map<String, Object> keywords = query.toLowerCase().split(' ');
Map<String, Object> boostedResults = semanticResults.matches.map((match) => Map.of(
Map<String, Object> content = match.metadata?.content?.toLowerCase() || '';
Map<String, Object> keywordMatches = keywords.filter((k) => content.includes(k)).length;
Map<String, Object> boost = 1 + (keywordMatches / keywords.length) * 0.2;
return Map.of(
...match,
score: match.score * boost
);
));
// 3. Re-rank by boosted score
return boostedResults.sort((a, b) => b.score - a.score).slice(0, 10);
)
import "context"
async function hybridSearch(query: string) {
// 1. Get semantic results
semanticResults := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
vector: generateEmbedding(query),
"topK": 20,
"includeMetadata": true,
});
// 2. Boost results that contain exact keywords
keywords := query.toLowerCase().split(' ');
boostedResults := semanticResults.matches.map((match) => {
content := match.metadata?.content?.toLowerCase() || '';
keywordMatches := keywords.filter((k) => content.includes(k)).length;
boost := 1 + (keywordMatches / keywords.length) * 0.2;
return {
...match,
score: match.score * boost,
};
});
// 3. Re-rank by boosted score
return boostedResults.sort((a, b) => b.score - a.score).slice(0, 10);
}
async function hybridSearch(query: string) {
// 1. Get semantic results
var semanticResults = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
vector: await generateEmbedding(query),
["topK"] = 20,
["includeMetadata"] = true,
});
// 2. Boost results that contain exact keywords
var keywords = query.toLowerCase().split(' ');
var boostedResults = semanticResults.matches.map((match) => {
var content = match.metadata?.content?.toLowerCase() || '';
var keywordMatches = keywords.filter((k) => content.includes(k)).length;
var boost = 1 + (keywordMatches / keywords.length) * 0.2;
return {
...match,
score: match.score * boost,
};
});
// 3. Re-rank by boosted score
return boostedResults.sort((a, b) => b.score - a.score).slice(0, 10);
}
Cost Management
Monitor Usage
- TypeScript
- Java
- Go
- .NET
// Track vector operations
const metrics = {
upserts: 0,
queries: 0,
vectorsStored: 0,
};
// Wrap operations with tracking
async function trackedQuery(options: QueryOptions) {
metrics.queries++;
return ductape.vector.query(options);
}
// Track vector operations
Map<String, Object> metrics = Map.of(
"upserts", 0,
"queries", 0,
"vectorsStored", 0
);
// Wrap operations with tracking
async function trackedQuery(options: QueryOptions) Map.of(
metrics.queries++;
return ductape.vector.query(options);
)
// Track vector operations
metrics := map[string]any{
"upserts": 0,
"queries": 0,
"vectorsStored": 0,
};
// Wrap operations with tracking
async function trackedQuery(options: QueryOptions) {
metrics.queries++;
return client.vector.query(options);
}
// Track vector operations
var metrics = new Dictionary<string, object?>
{
["upserts"] = 0,
["queries"] = 0,
["vectorsStored"] = 0,
};
// Wrap operations with tracking
async function trackedQuery(options: QueryOptions) {
metrics.queries++;
return ductape.vector.query(options);
}
Clean Up Old Data
- TypeScript
- Java
- Go
- .NET
// Delete old vectors by metadata
async function cleanupOldVectors(olderThanDays: number) {
const cutoffDate = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
// Fetch IDs of old vectors
const oldVectors = await ductape.vector.query({
tag: 'my-vectors',
vector: [/* any vector */],
topK: 10000,
filter: {
created_at: { $lt: cutoffDate },
},
});
// Delete in batches
const ids = oldVectors.matches.map((m) => m.id);
for (let i = 0; i < ids.length; i += 100) {
await ductape.vector.deleteVectors({
tag: 'my-vectors',
ids: ids.slice(i, i + 100),
});
}
}
// Delete old vectors by metadata
async function cleanupOldVectors(olderThanDays: number) Map.of(
Map<String, Object> cutoffDate = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
// Fetch IDs of old vectors
Map<String, Object> oldVectors = ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
vector: [/* any vector */],
"topK", 10000,
filter: Map.of(
created_at: Map.of( $lt: cutoffDate )
)
));
// Delete in batches
Map<String, Object> ids = oldVectors.matches.map((m) => m.id);
for (Map<String, Object> i = 0; i < ids.length; i += 100) Map.of(
ductape.vector.deleteVectors(Map.of(
"tag", "my-vectors",
ids: ids.slice(i, i + 100)
));
)
)
import "context"
// Delete old vectors by metadata
async function cleanupOldVectors(olderThanDays: number) {
cutoffDate := Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
// Fetch IDs of old vectors
oldVectors := client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
vector: [/* any vector */],
"topK": 10000,
filter: {
created_at: { $lt: cutoffDate },
},
});
// Delete in batches
ids := oldVectors.matches.map((m) => m.id);
for (i := 0; i < ids.length; i += 100) {
client.vector.deleteVectors({
"tag": "my-vectors",
ids: ids.slice(i, i + 100),
});
}
}
// Delete old vectors by metadata
async function cleanupOldVectors(olderThanDays: number) {
var cutoffDate = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
// Fetch IDs of old vectors
var oldVectors = await ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
vector: [/* any vector */],
["topK"] = 10000,
filter: {
created_at: { $lt: cutoffDate },
},
});
// Delete in batches
var ids = oldVectors.matches.map((m) => m.id);
for (var i = 0; i < ids.length; i += 100) {
await ductape.vector.deleteVectors({
["tag"] = "my-vectors",
ids: ids.slice(i, i + 100),
});
}
}
Use Appropriate Index Settings
Choose index settings based on your needs:
| Setting | Performance | Accuracy | Cost |
|---|---|---|---|
| High recall | Slower | Higher | Higher |
| Balanced | Medium | Medium | Medium |
| High speed | Faster | Lower | Lower |
Security
Don't Store Sensitive Data in Vectors
- TypeScript
- Java
- Go
- .NET
// Good - store reference to sensitive data
{
id: 'user-123',
values: [...],
metadata: {
user_id: 'usr_abc123', // Reference only
preferences_summary: 'Tech enthusiast, prefers email',
},
}
// Bad - sensitive data in vectors
{
id: 'user-123',
values: [...],
metadata: {
email: 'user@example.com', // PII!
ssn: '123-45-6789', // Sensitive!
},
}
// Good - store reference to sensitive data
Map.of(
"id", "user-123",
values: [...],
metadata: Map.of(
"user_id", "usr_abc123", // Reference only
"preferences_summary", "Tech enthusiast, prefers email"
)
)
// Bad - sensitive data in vectors
Map.of(
"id", "user-123",
values: [...],
metadata: Map.of(
"email", "user@example.com", // PII!
"ssn", "123-45-6789", // Sensitive!
)
)
// Good - store reference to sensitive data
{
"id": "user-123",
values: [...],
metadata: {
"user_id": "usr_abc123", // Reference only
"preferences_summary": "Tech enthusiast, prefers email",
},
}
// Bad - sensitive data in vectors
{
"id": "user-123",
values: [...],
metadata: {
"email": "user@example.com", // PII!
"ssn": "123-45-6789", // Sensitive!
},
}
// Good - store reference to sensitive data
{
["id"] = "user-123",
values: [...],
metadata: {
["user_id"] = "usr_abc123", // Reference only
["preferences_summary"] = "Tech enthusiast, prefers email",
},
}
// Bad - sensitive data in vectors
{
["id"] = "user-123",
values: [...],
metadata: {
["email"] = "user@example.com", // PII!
["ssn"] = "123-45-6789", // Sensitive!
},
}
Use Namespaces for Access Control
- TypeScript
- Java
- Go
- .NET
// Isolate data by organization
await ductape.vector.upsert({
tag: 'my-vectors',
namespace: `org:${organizationId}`,
vectors: [...],
});
// Enforce namespace in queries
async function searchWithAccessControl(query: string, user: User) {
return ductape.vector.query({
tag: 'my-vectors',
namespace: `org:${user.organizationId}`, // Enforce tenant isolation
vector: await generateEmbedding(query),
topK: 10,
});
}
// Isolate data by organization
ductape.vector.upsert(Map.of(
"tag", "my-vectors",
namespace: `org:$Map.of(organizationId)`,
vectors: [...]
));
// Enforce namespace in queries
async function searchWithAccessControl(query: string, user: User) Map.of(
return ductape.vectors().query(Map<String, Object>.of(
"tag", "my-vectors",
namespace: `org:$Map.of(user.organizationId)`, // Enforce tenant isolation
vector: generateEmbedding(query),
"topK", 10
));
)
import "context"
// Isolate data by organization
client.vector.upsert({
"tag": "my-vectors",
namespace: `org:${organizationId}`,
vectors: [...],
});
// Enforce namespace in queries
async function searchWithAccessControl(query: string, user: User) {
return client.VectorAPI.Query(ctx, map[string]any{
"tag": "my-vectors",
namespace: `org:${user.organizationId}`, // Enforce tenant isolation
vector: generateEmbedding(query),
"topK": 10,
});
}
// Isolate data by organization
await ductape.vector.upsert({
["tag"] = "my-vectors",
namespace: `org:${organizationId}`,
vectors: [...],
});
// Enforce namespace in queries
async function searchWithAccessControl(query: string, user: User) {
return ductape.Vector.Query(new Dictionary<string, object?>
{
["tag"] = "my-vectors",
namespace: `org:${user.organizationId}`, // Enforce tenant isolation
vector: await generateEmbedding(query),
["topK"] = 10,
});
}
Next Steps
- Getting Started - Set up your first vector database
- Querying - Advanced query patterns
- Using with Agents - Connect vectors to AI agents