Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this quickstart, you compare three vector index algorithms (DiskANN, HNSW, and IVF) and three similarity functions (cosine, L2, and inner product) to find the optimal configuration for your search workload. This quickstart uses a sample hotel dataset with precalculated embeddings from the text-embedding-3-small model.
Find the sample code on GitHub.
Prerequisites
- An Azure subscription. If you don't have an Azure subscription, create a free account.
An existing Azure DocumentDB cluster. If you don't have a cluster, create a new cluster.
-
Custom domain is configured.
text-embedding-3-smallmodel is deployed.
Visual Studio Code. Ensure that you have the Azure DocumentDB extension.
Use the Bash environment in Azure Cloud Shell. For more information, see Get started with Azure Cloud Shell.
If you prefer to run CLI reference commands locally, install the Azure CLI. If you're running on Windows or macOS, consider running Azure CLI in a Docker container. For more information, see How to run the Azure CLI in a Docker container.
If you're using a local installation, sign in to the Azure CLI by using the az login command. To finish the authentication process, follow the steps displayed in your terminal. For other sign-in options, see Authenticate to Azure using Azure CLI.
When you're prompted, install the Azure CLI extension on first use. For more information about extensions, see Use and manage extensions with the Azure CLI.
Run az version to find the version and dependent libraries that are installed. To upgrade to the latest version, run az upgrade.
Azure Developer CLI (optional). Use
azd upto deploy all required Azure resources in one command.TypeScript: Install TypeScript globally:
npm install -g typescript
Create the Node.js project
You should end up with the following project structure:
select-algorithm-typescript/
├── data/
│ └── Hotels_Vector.json
├── src/
│ ├── compare-all.ts
│ └── utils.ts
├── package.json
└── tsconfig.json
Create a new directory for your project and open it in Visual Studio Code:
mkdir select-algorithm-typescript cd select-algorithm-typescript code .
Initialize a TypeScript Node.js project:
npm init -y npm pkg set type="module"Verify the project was initialized:
ls package.json
Install the required packages:
npm install mongodb openai @azure/identity npm install --save-dev typescript @types/nodemongodb: MongoDB driver for Node.js.openai: OpenAI client library to create vectors.@azure/identity: Azure Identity library for passwordless authentication.typescript: TypeScript compiler.
Verify that
npm listshows all installed packages without errors.Create a
tsconfig.jsonfile in the project root:{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "strict": true, "rootDir": "./src", "outDir": "./dist" }, "include": ["src/**/*"], "exclude": ["node_modules"] }Update your
package.jsonto include:{ "type": "module", "scripts": { "build": "tsc", "start": "node dist/compare-all.js" } }Create the
srcdirectory:mkdir src
Create data file with vectors
Create a new data directory for the hotels data file:
mkdir data
Download the
Hotels_Vector.jsondata file with vectors to yourdatadirectory:curl -o data/Hotels_Vector.json https://raw.githubusercontent.com/Azure-Samples/documentdb-samples/refs/heads/main/ai/data/Hotels_Vector.json
Verify the file was downloaded:
ls data/Hotels_Vector.json
You should see Hotels_Vector.json in the data directory.
Configure environment variables
Set the required environment variables in your current shell session before you run the sample:
export DOCUMENTDB_CLUSTER_NAME=<your-cluster-name>
export AZURE_OPENAI_EMBEDDING_ENDPOINT=https://<your-resource>.openai.azure.com
export AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small
export AZURE_DOCUMENTDB_DATABASENAME=Hotels
export DATA_FILE_WITH_VECTORS=data/Hotels_Vector.json
export EMBEDDED_FIELD=DescriptionVector
export EMBEDDING_DIMENSIONS=1536
Replace the placeholder values with your own information:
AZURE_OPENAI_EMBEDDING_ENDPOINT: Your Azure OpenAI resource endpoint URLDOCUMENTDB_CLUSTER_NAME: Your Azure DocumentDB cluster name
Prefer passwordless authentication, but it requires additional setup. For more information on setting up managed identity and the full range of your authentication options, see Authenticate JavaScript apps to Azure services using the Azure SDK for JavaScript.
Create the algorithm comparison code
Create the src/compare-all.ts file with the following code:
import path from 'path';
import { readFileReturnJson, getClientsPasswordless, getConfig, insertData } from './utils.js';
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
interface AlgorithmConfig {
name: string;
kind: string;
options: Record<string, number>;
}
interface SearchResult {
algorithm: string;
similarity: string;
top1Name: string;
top1Score: number;
top2Name: string;
top2Score: number;
}
const ALGORITHMS: AlgorithmConfig[] = [
{ name: 'IVF', kind: 'vector-ivf', options: { numLists: 1 } },
{ name: 'HNSW', kind: 'vector-hnsw', options: { m: 16, efConstruction: 64 } },
{ name: 'DiskANN', kind: 'vector-diskann', options: { maxDegree: 32, lBuild: 50 } },
];
const SIMILARITIES = ['COS', 'L2', 'IP'];
async function main() {
const baseConfig = getConfig();
const queryText = process.env.QUERY_TEXT || 'luxury hotel near the beach';
const topK = parseInt(process.env.TOP_K || '5', 10);
const collectionName = 'hotels';
const { aiClient, dbClient } = getClientsPasswordless();
try {
if (!aiClient) throw new Error('AI client is not configured.');
if (!dbClient) throw new Error('Database client is not configured.');
await dbClient.connect();
const db = dbClient.db(baseConfig.dbName);
// Drop collection if it exists for a clean start
const collections = await db.listCollections({ name: collectionName }).toArray();
if (collections.length > 0) {
try {
const col = db.collection(collectionName);
const existingIndexes = await col.listIndexes().toArray();
for (const idx of existingIndexes) {
if (idx.name !== '_id_') {
try {
await col.dropIndex(idx.name);
} catch {}
}
}
await db.dropCollection(collectionName);
console.log(`Dropped existing collection: ${collectionName}`);
} catch (e: any) {
console.log(`Cleanup note: ${e.message.split('\n')[0]}`);
}
}
// Load data once for reuse
const data = await readFileReturnJson(path.join(__dirname, '..', baseConfig.dataFile));
console.log(`Loaded ${data.length} documents`);
// Insert data into collection
const collection = db.collection(collectionName);
await insertData(baseConfig, collection, data, false);
// Generate one embedding for the query
console.log(`\nQuery: "${queryText}"`);
const embeddingResponse = await aiClient.embeddings.create({
model: baseConfig.deployment,
input: [queryText]
});
const queryVector = embeddingResponse.data[0].embedding;
console.log(`Embedding generated (${queryVector.length} dimensions)`);
// Sequential create→search→drop for each algorithm+similarity combo
// DocumentDB does not allow multiple vector indexes of the same kind on the same field
console.log(`\nRunning searches (top ${topK} results)...\n`);
const results: SearchResult[] = [];
for (const algo of ALGORITHMS) {
for (const sim of SIMILARITIES) {
const indexName = `vector_${algo.kind.replace('vector-', '')}_${sim.toLowerCase()}`;
// 1. Drop all existing vector indexes
const indexes = await collection.listIndexes().toArray();
let droppedAny = false;
for (const idx of indexes) {
if (idx.key && idx.key[baseConfig.embeddedField] === 'cosmosSearch') {
try { await collection.dropIndex(idx.name); droppedAny = true; } catch {}
}
}
// 2. Create this specific index
const indexOptions = {
createIndexes: collectionName,
indexes: [{
name: indexName,
key: { [baseConfig.embeddedField]: 'cosmosSearch' },
cosmosSearchOptions: {
kind: algo.kind,
...algo.options,
similarity: sim,
dimensions: baseConfig.embeddingDimensions
}
}]
};
await db.command(indexOptions);
console.log(` ✓ ${indexName} created`);
// 3. Search with bounded retry while the new index becomes ready
const searchPipeline = [
{
$search: {
cosmosSearch: {
vector: queryVector,
path: baseConfig.embeddedField,
k: topK
}
}
},
{
$project: {
score: { $meta: 'searchScore' },
document: '$$ROOT'
}
}
];
let searchResults: any[] = [];
let lastSearchError: unknown;
for (let attempt = 1; attempt <= 6; attempt++) {
try {
searchResults = await collection.aggregate(searchPipeline).toArray();
if (searchResults.length > 0 || attempt === 6) {
break;
}
console.log(` ...search returned no results yet, retrying (${attempt}/6)`);
} catch (e) {
lastSearchError = e;
if (attempt === 6) {
throw e;
}
console.log(` ...search not ready yet, retrying (${attempt}/6)`);
}
await new Promise(r => setTimeout(r, 2000));
}
if (searchResults.length === 0 && lastSearchError) {
throw lastSearchError;
}
// Record top 2 results
const top1 = searchResults[0] as any;
const top2 = searchResults[1] as any;
results.push({
algorithm: algo.name,
similarity: sim,
top1Name: top1?.document?.HotelName ?? '(none)',
top1Score: top1?.score ?? 0,
top2Name: top2?.document?.HotelName ?? '(none)',
top2Score: top2?.score ?? 0,
});
}
}
// Print comparison table
printComparisonTable(results);
} catch (error) {
console.error('Compare-all failed:', error);
process.exitCode = 1;
} finally {
// Cleanup: drop the comparison collection
if (dbClient) {
try {
const db = dbClient.db(baseConfig.dbName);
await db.dropCollection(collectionName);
console.log(`\nCleanup: dropped collection "${collectionName}"`);
} catch (cleanupErr) {
console.error('Cleanup warning:', cleanupErr);
}
await dbClient.close();
console.log('Database connection closed');
}
}
}
function printComparisonTable(results: SearchResult[]) {
const algoW = 10;
const simW = 8;
const name1W = 28;
const score1W = 8;
const name2W = 28;
const score2W = 8;
const diffW = 7;
const pad = (s: string, w: number) => s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length);
const cols = [algoW, simW, name1W, score1W, name2W, score2W, diffW];
const topLine = `┌${cols.map(w => '─'.repeat(w)).join('┬')}┐`;
const headerSep = `├${cols.map(w => '─'.repeat(w)).join('┼')}┤`;
const rowSep = `├${cols.map(w => '─'.repeat(w)).join('┼')}┤`;
const bottomLine = `└${cols.map(w => '─'.repeat(w)).join('┴')}┘`;
console.log(topLine);
console.log(
`│${pad(' Algorithm', algoW)}│${pad(' Metric', simW)}│${pad(' Top 1 Result', name1W)}│${pad(' Score', score1W)}│${pad(' Top 2 Result', name2W)}│${pad(' Score', score2W)}│${pad(' Diff', diffW)}│`
);
console.log(headerSep);
results.forEach((r, i) => {
const diff = Math.abs(r.top1Score - r.top2Score).toFixed(4);
console.log(
`│${pad(` ${r.algorithm}`, algoW)}│${pad(` ${r.similarity}`, simW)}│${pad(` ${r.top1Name}`, name1W)}│${pad(` ${r.top1Score.toFixed(4)}`, score1W)}│${pad(` ${r.top2Name}`, name2W)}│${pad(` ${r.top2Score.toFixed(4)}`, score2W)}│${pad(` ${diff}`, diffW)}│`
);
if (i < results.length - 1) {
console.log(rowSep);
}
});
console.log(bottomLine);
}
main().catch(error => {
console.error('Unhandled error:', error);
process.exitCode = 1;
});
This script orchestrates the algorithm comparison by:
- Loading configuration from environment variables.
- Initializing MongoDB and Azure OpenAI clients with passwordless authentication.
- Loading hotel data with precalculated embeddings.
- Testing each algorithm/similarity combination by creating a collection, inserting data, creating an index, and executing a search.
- Measuring and comparing search performance across all configurations.
- Displaying results in a comparison table.
Create utility functions
Create the src/utils.ts file with the following code:
import { Collection, Document, MongoClient, OIDCResponse, OIDCCallbackParams } from 'mongodb';
import { AzureOpenAI } from 'openai';
import { promises as fs } from "fs";
import { AccessToken, DefaultAzureCredential, TokenCredential, getBearerTokenProvider } from '@azure/identity';
// Define a type for JSON data
export type JsonData = Record<string, any>;
export function getConfig() {
return {
dbName: process.env.AZURE_DOCUMENTDB_DATABASENAME || 'Hotels',
dataFile: process.env.DATA_FILE_WITH_VECTORS || 'data/Hotels_Vector.json',
embeddedField: process.env.EMBEDDED_FIELD || 'DescriptionVector',
similarity: process.env.SIMILARITY || '',
embeddingDimensions: parseInt(process.env.EMBEDDING_DIMENSIONS || '1536', 10),
deployment: process.env.AZURE_OPENAI_EMBEDDING_MODEL || 'text-embedding-3-small',
batchSize: parseInt(process.env.LOAD_SIZE_BATCH || '100', 10)
};
}
export const AzureIdentityTokenCallback = async (params: OIDCCallbackParams, credential: TokenCredential): Promise<OIDCResponse> => {
const tokenResponse: AccessToken | null = await credential.getToken(['https://ossrdbms-aad.database.windows.net/.default']);
if (!tokenResponse || !tokenResponse.token) {
throw new Error('Failed to acquire token');
}
return {
accessToken: tokenResponse.token,
expiresInSeconds: Math.floor((tokenResponse.expiresOnTimestamp - Date.now()) / 1000)
};
};
export function getClientsPasswordless(): { aiClient: AzureOpenAI | null; dbClient: MongoClient | null } {
let aiClient: AzureOpenAI | null = null;
let dbClient: MongoClient | null = null;
// Validate all required environment variables upfront
const endpoint = process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT!;
const deployment = process.env.AZURE_OPENAI_EMBEDDING_MODEL!;
const clusterName = process.env.DOCUMENTDB_CLUSTER_NAME!;
if (!endpoint || !deployment || !clusterName) {
throw new Error('Missing required environment variables: AZURE_OPENAI_EMBEDDING_ENDPOINT, AZURE_OPENAI_EMBEDDING_MODEL, DOCUMENTDB_CLUSTER_NAME');
}
console.log(`Using Azure OpenAI Embedding Deployment/Model: ${deployment}`);
const credential = new DefaultAzureCredential();
// For Azure OpenAI with DefaultAzureCredential
{
const scope = "https://cognitiveservices.azure.com/.default";
const azureADTokenProvider = getBearerTokenProvider(credential, scope);
aiClient = new AzureOpenAI({
apiVersion: process.env.AZURE_OPENAI_EMBEDDING_API_VERSION || "2023-05-15",
endpoint,
deployment,
azureADTokenProvider,
timeout: 30000,
maxRetries: 3,
});
}
// For DocumentDB with DefaultAzureCredential (uses signed-in user)
{
dbClient = new MongoClient(
`mongodb+srv://${clusterName}.global.mongocluster.cosmos.azure.com/`, {
connectTimeoutMS: 120000,
tls: true,
retryWrites: false,
maxIdleTimeMS: 120000,
authMechanism: 'MONGODB-OIDC',
authMechanismProperties: {
OIDC_CALLBACK: (params: OIDCCallbackParams) => AzureIdentityTokenCallback(params, credential),
ALLOWED_HOSTS: ['*.azure.com']
}
}
);
}
return { aiClient, dbClient };
}
export async function readFileReturnJson(filePath: string): Promise<JsonData[]> {
console.log(`Reading JSON file from ${filePath}`);
const fileAsString = await fs.readFile(filePath, "utf-8");
return JSON.parse(fileAsString);
}
export async function insertData(
config: { batchSize: number },
collection: Collection,
data: Document[],
createScalarIndexes: boolean = true
) {
console.log(`Processing in batches of ${config.batchSize}...`);
const totalBatches = Math.ceil(data.length / config.batchSize);
let inserted = 0;
let failed = 0;
for (let i = 0; i < totalBatches; i++) {
const start = i * config.batchSize;
const end = Math.min(start + config.batchSize, data.length);
const batch = data.slice(start, end);
try {
const result = await collection.insertMany(batch, { ordered: false });
inserted += result.insertedCount || 0;
console.log(`Batch ${i + 1} complete: ${result.insertedCount} inserted`);
} catch (error: any) {
if (error?.writeErrors) {
console.error(`Error in batch ${i + 1}: ${error?.writeErrors.length} failures`);
failed += error?.writeErrors.length;
inserted += batch.length - error?.writeErrors.length;
} else {
console.error(`Error in batch ${i + 1}:`, error);
failed += batch.length;
}
}
// Small pause between batches to reduce resource contention
if (i < totalBatches - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
if (createScalarIndexes) {
const indexColumns = ["HotelId", "Category", "Description", "Description_fr"];
for (const col of indexColumns) {
const indexSpec: Record<string, number> = {};
indexSpec[col] = 1;
await collection.createIndex(indexSpec);
}
}
return { total: data.length, inserted, failed };
}
export function printSearchResults(insertSummary: any, vectorIndexSummary: any, searchResults: Document[]) {
console.log(`\nInsert summary: ${JSON.stringify(insertSummary)}`);
console.log(`Vector index: ${JSON.stringify(vectorIndexSummary)}`);
if (!searchResults || searchResults.length === 0) {
console.log('No search results found.');
return;
}
searchResults.map((result: Document, index: number) => {
const { document, score } = result;
console.log(`${index + 1}. HotelName: ${document.HotelName}, Score: ${score.toFixed(4)}`);
});
}
/**
* Print a side-by-side comparison table of vector search results across collections
*/
export function printComparisonTable(
results: Array<{
collectionName: string;
algorithm: string;
similarity: string;
searchResults: any[];
latencyMs: number;
}>
): void {
console.log('\n╔══════════════════════════════════════════════════════════════════════════════════╗');
console.log('║ Vector Algorithm Comparison Results ║');
console.log('╠══════════════════════════════════════════════════════════════════════════════════╣');
// Header
console.log(
'║ ' +
'Algorithm'.padEnd(12) +
'Similarity'.padEnd(14) +
'Top Result'.padEnd(24) +
'Score'.padEnd(12) +
'Latency(ms)'.padEnd(14) +
'║'
);
console.log('╠══════════════════════════════════════════════════════════════════════════════════╣');
for (const r of results) {
const topResult = r.searchResults[0];
const topName = topResult ? (topResult.document.HotelName as string).substring(0, 22) : 'N/A';
const topScore = topResult ? topResult.score.toFixed(4) : 'N/A';
console.log(
'║ ' +
r.algorithm.padEnd(12) +
r.similarity.padEnd(14) +
topName.padEnd(24) +
topScore.padEnd(12) +
r.latencyMs.toFixed(0).padEnd(14) +
'║'
);
}
console.log('╚══════════════════════════════════════════════════════════════════════════════════╝');
// Detailed results per collection
for (const r of results) {
console.log(`\n--- ${r.algorithm} / ${r.similarity} (${r.collectionName}) ---`);
if (r.searchResults.length === 0) {
console.log(' No results.');
continue;
}
r.searchResults.forEach((item: Document, i: number) => {
console.log(` ${i + 1}. ${item.document.HotelName}, Score: ${item.score.toFixed(4)}`);
});
console.log(` Latency: ${r.latencyMs.toFixed(0)}ms`);
}
}
The utilities provide essential functions for:
- Passwordless authentication to DocumentDB and Azure OpenAI using DefaultAzureCredential.
- Reading JSON data files.
- Batch insertion of documents with DocumentDB's 16 MB payload limit in mind.
- Formatted display of comparison results showing algorithm performance.
Note
The Node.js sample configures the DocumentDB connection with retryWrites=false, which is required for DocumentDB vector search operations.
Run the code
Sign in with Azure CLI for passwordless authentication:
az loginExecute the comparison script to test all 9 algorithm × similarity combinations:
npm run build npm start
Expected output:
Using Azure OpenAI Embedding Deployment/Model: text-embedding-3-small
Reading JSON file from data/Hotels_Vector.json
Loaded 50 documents
Processing in batches of 50...
Batch 1 complete: 50 inserted
Query: "luxury hotel near the beach"
Embedding generated (1536 dimensions)
Running searches (top 5 results)... ✓ vector_ivf_cos created
✓ vector_ivf_l2 created
✓ vector_ivf_ip created
✓ vector_hnsw_cos created
✓ vector_hnsw_l2 created
✓ vector_hnsw_ip created
✓ vector_diskann_cos created
✓ vector_diskann_l2 created
✓ vector_diskann_ip created
┌──────────┬────────┬────────────────────────────┬────────┬────────────────────────────┬────────┬───────┐
│ Algorithm│ Metric │ Top 1 Result │ Score │ Top 2 Result │ Score │ Diff │
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF │ COS │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF │ L2 │ Ocean Water Resort & Spa │ 0.8736 │ Windy Ocean Motel │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ IVF │ IP │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW │ COS │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW │ L2 │ Ocean Water Resort & Spa │ 0.8736 │ Windy Ocean Motel │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ HNSW │ IP │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DiskANN │ COS │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DiskANN │ L2 │ Ocean Water Resort & Spa │ 0.8736 │ Windy Ocean Motel │ 0.9943 │ 0.1208│
├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤
│ DiskANN │ IP │ Ocean Water Resort & Spa │ 0.6184 │ Windy Ocean Motel │ 0.5056 │ 0.1128│
└──────────┴────────┴────────────────────────────┴────────┴────────────────────────────┴────────┴───────┘
Cleanup: dropped collection "hotels"
Database connection closed
The Diff column shows the score gap between the top-1 and top-2 results. A smaller diff indicates the algorithm found results with more similar relevance scores.
Understanding the results
The comparison table demonstrates key behaviors of vector search in DocumentDB:
All algorithms return identical results on small datasets. With 50 documents, every algorithm finds the same matches because the dataset fits entirely in memory regardless of index structure. Algorithm selection becomes important at scale (millions of documents) where tradeoffs in latency, memory, and recall diverge.
COS and IP produce identical scores (0.6184 / 0.5056) because the
text-embedding-3-smallmodel outputs normalized (unit-length) vectors. For normalized vectors, cosine similarity equals inner product mathematically.L2 (Euclidean distance) scores represent distance. In this output, the top result has the lower L2 score (0.8736) and the second result is farther away (0.9943).
Score separation (Diff column) shows the gap between the top two results. A smaller diff indicates the algorithm found results with more similar relevance scores.
Choosing the right algorithm
Important
For production workloads, start with DiskANN on an M30+ cluster. DiskANN supports higher embedding dimensions, uses less cluster memory, and is less likely to require an index redesign as your models evolve.
Use this quick-reference table to select the right algorithm for your workload:
| Scenario | Algorithm | Cluster tier | Max dimensions |
|---|---|---|---|
| Dev/test, demos, small datasets | IVF | M10+ | 2,000 |
| Production (default) | DiskANN | M30+ | 16,000 |
| Production (max recall priority) | HNSW | M30+ | 8,000 |
IVF (inverted file index):
- Best for: Test environments, demos, and small clusters
- Pros: Fast to build, low resource requirements
- Cons: Lower recall compared to graph-based algorithms at scale
- Tune: Increase
numListsfor larger datasets, increasenProbesfor better recall
DiskANN (disk-based approximate nearest neighbor) — recommended for production:
- Best for: Production workloads on M30+ clusters
- Pros: Supports embeddings up to 16,000 dimensions, keeps most index data on disk freeing cluster memory for reads and writes, lighter index updates, easier backups, faster recovery
- Cons: Requires M30+ cluster tier
- Tune: Increase
maxDegreeandlBuildfor better accuracy, increaselSearchfor better recall - Why default: As embedding models evolve (some already exceed 8,000 dimensions), DiskANN avoids costly index redesigns. Its disk-based architecture also means your cluster memory stays available for operational workloads rather than index storage.
HNSW (hierarchical navigable small world):
- Best for: Production workloads on M30+ clusters where maximum recall is the top priority
- Pros: Excellent recall, fast queries
- Cons: Requires M30+ cluster tier, supports embeddings up to 8,000 dimensions (vs 16,000 for DiskANN), higher memory usage since the full graph lives in RAM
- Tune: Increase
mandefConstructionfor better index quality, increaseefSearchfor better recall
Choosing the right similarity function
| Function | Score meaning | Best for |
|---|---|---|
| COS (Cosine) | Higher = more similar (0–1) | Text embeddings (normalized vectors) |
| L2 (Euclidean) | Lower = more similar (distance) | When magnitude matters |
| IP (Inner Product) | Higher = more similar | Equivalent to COS for normalized vectors |
For the text-embedding-3-small model used in this quickstart, COS (cosine similarity) is recommended because OpenAI embeddings are normalized and optimized for cosine similarity.
Troubleshooting
| Issue | Solution |
|---|---|
MongoServerSelectionError |
Verify your DOCUMENTDB_CLUSTER_NAME environment variable and ensure your IP is in the DocumentDB firewall rules. |
MongoServerError: Authentication failed |
Verify your Microsoft Entra token is valid. Run az login to refresh your credentials. |
| TypeScript compilation errors | Run npx tsc --version to verify TypeScript is installed. Check tsconfig.json settings match the values shown in this article. |
Cannot find module errors |
Run npm install to ensure all dependencies are installed. |
Embedding dimension mismatch |
Verify the AZURE_OPENAI_EMBEDDING_MODEL environment variable matches the model deployed in your Azure OpenAI resource. |
| Empty search results | The vector index may take a few minutes to build. Wait 2-3 minutes after index creation, then rerun the script. |
Clean up resources
Remove the database using the DocumentDB for VS Code extension:
- Install the DocumentDB for VS Code extension.
- Connect to your Azure DocumentDB cluster.
- Expand the cluster, right-click the Hotels database, and select Drop Database.