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.
This quickstart compares vector index algorithms (DiskANN, HNSW, IVF) in Azure DocumentDB using Java to help you select the best configuration for your vector search workload. The sample uses the same hotel dataset with precalculated vectors as the other quickstarts to demonstrate performance differences across algorithms and similarity functions.
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.Java 21 or later
Create a Java project
Create a new directory for your project and open it in Visual Studio Code:
mkdir select-algorithm-java cd select-algorithm-java code .
Create a standard Maven project structure:
mkdir -p src/main/java/com/azure/documentdb/selectalgorithm
Create a
pom.xmlfile in the root directory with the following content:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.azure.documentdb</groupId> <artifactId>select-algorithm-java</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>DocumentDB Select Algorithm - Java</name> <description>Demonstrates IVF, HNSW, and DiskANN vector search indexes with Azure DocumentDB</description> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>5.4.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.16.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-ai-openai</artifactId> <version>1.0.0-beta.16</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <source>21</source> <target>21</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.4.1</version> <configuration> <mainClass>com.azure.documentdb.selectalgorithm.Main</mainClass> </configuration> </plugin> </plugins> </build> <profiles> <profile> <id>compare</id> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.4.1</version> <configuration> <mainClass>com.azure.documentdb.selectalgorithm.CompareAll</mainClass> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>Verify that all dependencies resolve without errors by running
mvn dependency:resolve.
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 that the file exists and is valid JSON:
ls -lh 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 Azure resource information:
DOCUMENTDB_CLUSTER_NAME: Your Azure DocumentDB cluster nameAZURE_OPENAI_EMBEDDING_ENDPOINT: Your Azure OpenAI resource endpoint URL
This sample uses passwordless authentication with DefaultAzureCredential, which requires your identity to have proper RBAC roles assigned. For more information on authentication options, see Authenticate Java apps to Azure services by using the Azure SDK for Java.
Create code files
When you're done, the project structure should look like this:
select-algorithm-java/
├── data/
│ └── Hotels_Vector.json
├── src/main/java/com/azure/documentdb/selectalgorithm/
│ ├── CompareAll.java
│ ├── Main.java
│ └── Utils.java
└── pom.xml
Create the algorithm comparison code
Create the following source files to implement the vector search comparison.
Create utility functions
Create src/main/java/com/azure/documentdb/selectalgorithm/Utils.java and paste the following code:
package com.azure.documentdb.selectalgorithm;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.EmbeddingItem;
import com.azure.ai.openai.models.EmbeddingsOptions;
import com.azure.core.credential.AccessToken;
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.InsertManyOptions;
import org.bson.Document;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class Utils {
public static String getEnv(String key, String defaultValue) {
String value = System.getenv(key);
return (value != null && !value.isBlank()) ? value : defaultValue;
}
public static String getEnv(String key) {
return getEnv(key, null);
}
public static MongoClient getMongoClient() {
String clusterName = getEnv("DOCUMENTDB_CLUSTER_NAME");
if (clusterName == null) {
throw new IllegalStateException("DOCUMENTDB_CLUSTER_NAME environment variable is required");
}
String clusterHost = String.format("%s.global.mongocluster.cosmos.azure.com", clusterName);
// Use custom OIDC callback with DefaultAzureCredential
// This chains through CLI, managed identity, etc.
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
String tokenResource = "https://ossrdbms-aad.database.windows.net/.default";
MongoCredential mongoCredential = MongoCredential.createOidcCredential(null)
.withMechanismProperty("OIDC_CALLBACK", (MongoCredential.OidcCallback) context -> {
AccessToken token = credential.getToken(
new com.azure.core.credential.TokenRequestContext()
.addScopes(tokenResource)).block();
return new MongoCredential.OidcCallbackResult(token.getToken());
});
MongoClientSettings settings = MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.srvHost(clusterHost))
.applyToSslSettings(builder -> builder.enabled(true))
.credential(mongoCredential)
.retryWrites(false)
.build();
return MongoClients.create(settings);
}
public static OpenAIClient getOpenAIClient() {
String endpoint = getEnv("AZURE_OPENAI_EMBEDDING_ENDPOINT");
if (endpoint == null) {
throw new IllegalStateException("AZURE_OPENAI_EMBEDDING_ENDPOINT environment variable is required");
}
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
return new OpenAIClientBuilder()
.endpoint(endpoint)
.credential(credential)
.buildClient();
}
public static List<Document> readJsonFile(String path) {
try {
String content = Files.readString(Path.of(path));
// Parse JSON array of documents
@SuppressWarnings("unchecked")
List<Document> docs = Document.parse("{\"data\":" + content + "}").getList("data", Document.class);
return docs;
} catch (IOException e) {
throw new RuntimeException("Failed to read data file: " + path, e);
}
}
public static void insertData(MongoCollection<Document> collection, List<Document> data, int batchSize) {
System.out.printf(" Inserting %d documents in batches of %d...%n", data.size(), batchSize);
InsertManyOptions options = new InsertManyOptions().ordered(false);
for (int i = 0; i < data.size(); i += batchSize) {
List<Document> batch = data.subList(i, Math.min(i + batchSize, data.size()));
// Remove _id to avoid duplicate key errors on re-run
List<Document> cleaned = new ArrayList<>();
for (Document doc : batch) {
Document copy = new Document(doc);
copy.remove("_id");
cleaned.add(copy);
}
try {
collection.insertMany(cleaned, options);
} catch (Exception e) {
// Ignore duplicate key errors on re-insert
if (!e.getMessage().contains("duplicate key")) {
throw e;
}
}
System.out.printf(" Inserted batch %d-%d%n", i + 1, Math.min(i + batchSize, data.size()));
}
System.out.println(" Data insertion complete.");
}
public static void dropVectorIndexes(MongoCollection<Document> collection, String vectorField) {
try {
for (Document idx : collection.listIndexes()) {
String name = idx.getString("name");
if (name != null && name.contains(vectorField) && !name.equals("_id_")) {
System.out.printf(" Dropping existing index: %s%n", name);
collection.dropIndex(name);
}
}
} catch (Exception e) {
// Ignore errors when indexes don't exist
System.out.println(" No existing vector indexes to drop.");
}
}
public static List<Float> getEmbedding(OpenAIClient client, String text, String model) {
EmbeddingsOptions options = new EmbeddingsOptions(List.of(text));
List<EmbeddingItem> embeddings = client.getEmbeddings(model, options).getData();
if (embeddings.isEmpty()) {
throw new RuntimeException("No embedding returned for query text");
}
return embeddings.get(0).getEmbedding();
}
public static List<Document> performVectorSearch(
MongoCollection<Document> collection,
OpenAIClient aiClient,
String query,
String vectorField,
String model,
int topK) {
System.out.printf(" Generating embedding for query: \"%s\"%n", query);
List<Float> queryVector = getEmbedding(aiClient, query, model);
System.out.printf(" Embedding generated (%d dimensions)%n", queryVector.size());
// Convert List<Float> to List<Double> for BSON
List<Double> vectorAsDoubles = queryVector.stream()
.map(Float::doubleValue)
.toList();
Document searchStage = new Document("$search", new Document("cosmosSearch", new Document()
.append("vector", vectorAsDoubles)
.append("path", vectorField)
.append("k", topK)));
Document projectStage = new Document("$project", new Document()
.append("_id", 0)
.append("HotelName", 1)
.append("Description", 1)
.append("score", new Document("$meta", "searchScore")));
List<Document> pipeline = List.of(searchStage, projectStage);
List<Document> results = new ArrayList<>();
collection.aggregate(pipeline).forEach(results::add);
return results;
}
public static void printResults(List<Document> results) {
System.out.println("\n === Search Results ===");
for (int i = 0; i < results.size(); i++) {
Document doc = results.get(i);
System.out.printf(" %d. %s (score: %.4f)%n",
i + 1,
doc.getString("HotelName"),
doc.getDouble("score"));
System.out.printf(" %s%n", doc.getString("Description"));
}
System.out.println();
}
}
This utility class provides:
- Environment variable management: Reads configuration from environment variables with
System.getenv(). - Passwordless authentication: Uses
DefaultAzureCredentialfor both MongoDB and Azure OpenAI. - MongoDB client creation: Configures OIDC authentication for DocumentDB.
- Azure OpenAI client creation: Sets up the OpenAI client for embedding generation.
- Data loading: Reads hotel data from JSON file.
- Embedding generation: Creates vector embeddings for text queries.
- Index configuration: Generates algorithm-specific vector index options.
- Search configuration: Generates algorithm-specific search parameters.
- Results formatting: Prints comparison table of algorithm performance.
Note
The Java sample configures the DocumentDB connection with retryWrites=false, which is required for DocumentDB vector search operations.
Create main comparison logic
Create the following source files in src/main/java/com/azure/documentdb/selectalgorithm/:
CompareAll.java
package com.azure.documentdb.selectalgorithm;
import com.azure.ai.openai.OpenAIClient;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
/**
* Unified comparison runner that executes all 9 combinations
* (3 algorithms x 3 similarity metrics) and prints a formatted table.
*/
public class CompareAll {
private static final String COLLECTION_NAME = "hotels";
private static final String[] ALGORITHMS = {"ivf", "hnsw", "diskann"};
private static final String[] METRICS = {"COS", "L2", "IP"};
public static void main(String[] args) {
run();
}
public static void run() {
String queryText = Utils.getEnv("QUERY_TEXT", "luxury hotel near the beach");
int topK = Integer.parseInt(Utils.getEnv("TOP_K", "5"));
String databaseName = Utils.getEnv("AZURE_DOCUMENTDB_DATABASENAME", "Hotels");
String dataFile = Utils.getEnv("DATA_FILE_WITH_VECTORS", "data/Hotels_Vector.json");
String vectorField = Utils.getEnv("EMBEDDED_FIELD", "DescriptionVector");
int dimensions = Integer.parseInt(Utils.getEnv("EMBEDDING_DIMENSIONS", "1536"));
String model = Utils.getEnv("AZURE_OPENAI_EMBEDDING_MODEL", "text-embedding-3-small");
System.out.println("==============================================");
System.out.println(" Azure DocumentDB - Compare All Algorithms");
System.out.println("==============================================");
System.out.printf(" Query: \"%s\"%n", queryText);
System.out.printf(" Top K: %d%n", topK);
System.out.printf(" Metrics: COS, L2, IP%n");
System.out.printf(" Algos: IVF, HNSW, DiskANN%n");
System.out.println();
List<SearchResult> results = new ArrayList<>();
try (MongoClient mongoClient = Utils.getMongoClient()) {
MongoDatabase database = mongoClient.getDatabase(databaseName);
MongoCollection<Document> collection = database.getCollection(COLLECTION_NAME);
// Load data ONCE into the single collection
System.out.println(" Loading data from: " + dataFile);
List<Document> data = Utils.readJsonFile(dataFile);
System.out.printf(" Loaded %d documents%n", data.size());
collection.drop();
System.out.println(" Collection reset.");
int batchSize = Integer.parseInt(System.getenv().getOrDefault("LOAD_SIZE_BATCH", "100"));
Utils.insertData(collection, data, batchSize);
// Generate ONE embedding for the query (reused for all 9 searches)
OpenAIClient aiClient = Utils.getOpenAIClient();
System.out.printf("%n Generating embedding for: \"%s\"%n", queryText);
List<Float> queryVector = Utils.getEmbedding(aiClient, queryText, model);
System.out.printf(" Embedding generated (%d dimensions)%n%n", queryVector.size());
// Convert to doubles for BSON
List<Double> vectorAsDoubles = queryVector.stream()
.map(Float::doubleValue)
.toList();
// Run 9 algorithm × metric combinations sequentially (create→search→drop)
// DocumentDB does not allow multiple vector indexes of the same kind
// on the same field path simultaneously.
System.out.println(" Running 9 algorithm × metric combinations...\n");
for (String algo : ALGORITHMS) {
for (String metric : METRICS) {
String indexName = String.format("vector_%s_%s", algo, metric.toLowerCase());
// 1. Drop all existing vector indexes
dropVectorIndexes(collection, vectorField);
// 2. Create this specific index
createIndex(database, collection, vectorField, dimensions, algo, metric);
System.out.printf(" ✓ %s created%n", indexName);
// 3. Search with retries while the index becomes available
List<Document> searchResults = performSearchWithRetry(
collection, vectorAsDoubles, vectorField, topK, indexName);
if (searchResults.isEmpty()) {
results.add(new SearchResult(algo.toUpperCase(), metric, "(failed)", 0.0, "(failed)", 0.0));
continue;
}
// 4. Extract top 2 results
String top1Name = "-"; double top1Score = 0.0;
String top2Name = "-"; double top2Score = 0.0;
if (!searchResults.isEmpty()) {
Document top1 = searchResults.get(0);
top1Name = top1.getString("HotelName") != null ? top1.getString("HotelName") : "-";
top1Score = top1.getDouble("score") != null ? top1.getDouble("score") : 0.0;
}
if (searchResults.size() > 1) {
Document top2 = searchResults.get(1);
top2Name = top2.getString("HotelName") != null ? top2.getString("HotelName") : "-";
top2Score = top2.getDouble("score") != null ? top2.getDouble("score") : 0.0;
}
results.add(new SearchResult(algo.toUpperCase(), metric, top1Name, top1Score, top2Name, top2Score));
}
}
// Print comparison table
printComparisonTable(results);
int successCount = (int) results.stream().filter(r -> !r.top1Name().equals("(failed)")).count();
if (successCount == 0) {
throw new IllegalStateException("All 9 comparisons failed — no algorithm returned results.");
}
System.out.printf("%nSummary: %d succeeded, %d failed%n", successCount, 9 - successCount);
// Cleanup: drop the comparison collection
System.out.println("\n Cleanup: dropping comparison collection...");
collection.drop();
System.out.println(" Cleanup: dropped collection 'hotels'");
}
}
private static void dropVectorIndexes(MongoCollection<Document> collection, String vectorField) {
for (Document idx : collection.listIndexes()) {
String name = idx.getString("name");
Document key = idx.get("key", Document.class);
if (key != null && "cosmosSearch".equals(key.getString(vectorField))) {
try {
collection.dropIndex(name);
} catch (Exception e) {
// Ignore if index doesn't exist
}
}
}
}
private static void createIndex(MongoDatabase database, MongoCollection<Document> collection,
String vectorField, int dimensions,
String algo, String metric) {
String indexName = String.format("vector_%s_%s", algo, metric.toLowerCase());
Document cosmosSearchOptions = new Document()
.append("dimensions", dimensions)
.append("similarity", metric);
switch (algo) {
case "ivf" -> cosmosSearchOptions
.append("kind", "vector-ivf")
.append("numLists", 1);
case "hnsw" -> cosmosSearchOptions
.append("kind", "vector-hnsw")
.append("m", 16)
.append("efConstruction", 64);
case "diskann" -> cosmosSearchOptions
.append("kind", "vector-diskann")
.append("maxDegree", 32)
.append("lBuild", 50);
}
Document indexDefinition = new Document()
.append("name", indexName)
.append("key", new Document(vectorField, "cosmosSearch"))
.append("cosmosSearchOptions", cosmosSearchOptions);
Document command = new Document("createIndexes", collection.getNamespace().getCollectionName())
.append("indexes", List.of(indexDefinition));
try {
database.runCommand(command);
} catch (Exception e) {
// Idempotent: ignore if index already exists
if (!e.getMessage().contains("already exists")) {
throw e;
}
}
}
private static List<Document> performSearch(MongoCollection<Document> collection,
List<Double> vectorAsDoubles,
String vectorField, int topK) {
Document searchStage = new Document("$search", new Document("cosmosSearch", new Document()
.append("vector", vectorAsDoubles)
.append("path", vectorField)
.append("k", topK)));
Document projectStage = new Document("$project", new Document()
.append("_id", 0)
.append("HotelName", 1)
.append("Description", 1)
.append("score", new Document("$meta", "searchScore")));
List<Document> pipeline = List.of(searchStage, projectStage);
List<Document> results = new ArrayList<>();
collection.aggregate(pipeline).forEach(results::add);
return results;
}
private static List<Document> performSearchWithRetry(MongoCollection<Document> collection,
List<Double> vectorAsDoubles,
String vectorField,
int topK,
String indexName) {
int maxRetries = 5;
int retryDelayMs = 2000;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
List<Document> results = performSearch(collection, vectorAsDoubles, vectorField, topK);
if (!results.isEmpty()) {
return results;
}
if (attempt < maxRetries) {
System.out.printf(" No results for %s yet. Retrying in 2 seconds (%d/%d)...%n",
indexName, attempt + 1, maxRetries);
try {
Thread.sleep(retryDelayMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
System.out.printf(" Search for %s did not return results after %d retries. Recording as failed.%n",
indexName, maxRetries);
return List.of();
}
private static void printComparisonTable(List<SearchResult> results) {
System.out.println("┌──────────┬────────┬────────────────────────────┬────────┬────────────────────────────┬────────┬───────┐");
System.out.printf("│ %-9s│ %-7s│ %-27s│ %-7s│ %-27s│ %-7s│ %-6s│%n",
"Algorithm", "Metric", "Top 1 Result", "Score", "Top 2 Result", "Score", "Diff");
System.out.println("├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤");
for (int i = 0; i < results.size(); i++) {
SearchResult r = results.get(i);
double diff = Math.abs(r.top1Score() - r.top2Score());
String top1Display = r.top1Name().length() > 27 ? r.top1Name().substring(0, 24) + "..." : r.top1Name();
String top2Display = r.top2Name().length() > 27 ? r.top2Name().substring(0, 24) + "..." : r.top2Name();
System.out.printf("│ %-9s│ %-7s│ %-27s│ %-7.4f│ %-27s│ %-7.4f│ %-6.4f│%n",
r.algorithm(), r.metric(), top1Display, r.top1Score(), top2Display, r.top2Score(), diff);
if (i < results.size() - 1) {
System.out.println("├──────────┼────────┼────────────────────────────┼────────┼────────────────────────────┼────────┼───────┤");
}
}
System.out.println("└──────────┴────────┴────────────────────────────┴────────┴────────────────────────────┴────────┴───────┘");
}
private record SearchResult(
String algorithm,
String metric,
String top1Name,
double top1Score,
String top2Name,
double top2Score) {
}
}
Main.java
package com.azure.documentdb.selectalgorithm;
public class Main {
public static void main(String[] args) {
System.out.println("==============================================");
System.out.println(" Azure DocumentDB - Compare All Algorithms");
System.out.println("==============================================");
System.out.println();
CompareAll.run();
System.out.println("==============================================");
System.out.println(" Comparison complete.");
System.out.println("==============================================");
}
}
This main comparison logic provides:
- Algorithm comparison logic: Tests all combinations of algorithms and similarity functions.
- Collection management: Creates separate collections for each configuration.
- Data loading: Inserts hotel data in batches.
- Index creation: Creates vector indexes for each algorithm and metric combination.
- Performance measurement: Measures average query latency.
- Results display: Outputs comparison table.
Run the code
Sign in with Azure CLI for passwordless authentication:
az loginCompile the project:
mvn clean compileVerify that the build output ends with
BUILD SUCCESS.Run the comparison entry point.
Main.javacallsCompareAll.run()and always executes all 9 combinations (3 algorithms × 3 metrics):mvn exec:java -Dexec.mainClass="com.azure.documentdb.selectalgorithm.Main"
The program prints output similar to the following:
==============================================
Azure DocumentDB - Compare All Algorithms
==============================================
Query: "luxury hotel near the beach"
Top K: 5
Metrics: COS, L2, IP
Algos: IVF, HNSW, DiskANN
Loading data from: data/Hotels_Vector.json
Loaded 50 documents
Collection reset.
Generating embedding for: "luxury hotel near the beach"
Embedding generated (1536 dimensions)
Running 9 algorithm × metric combinations...
✓ 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│
└──────────┴────────┴────────────────────────────┴────────┴────────────────────────────┴────────┴───────┘
Summary: 9 succeeded, 0 failed
Cleanup: dropping comparison collection...
Cleanup: dropped collection 'hotels'
==============================================
Comparison complete.
==============================================
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.
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 |
|---|---|
MongoTimeoutException |
Verify the DOCUMENTDB_CLUSTER_NAME environment variable, and ensure your IP is in the DocumentDB firewall rules. |
MongoSecurityException |
Verify your Microsoft Entra token is valid. Run az login to refresh your credentials. |
| Maven build failures | Run mvn dependency:resolve to check for missing dependencies. Ensure Java 21 or later is installed. |
No plugin found for prefix 'exec' |
Add exec-maven-plugin to your pom.xml as shown in this article. |
| Empty search results | Index may not be ready. The sample retries up to 5 times with 2-second intervals after index creation. If results are still empty, increase the wait time or verify index status with the DocumentDB for VS Code extension. |
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.