Databricks GenAI Engineer: Mosaic AI Vector Search Indexes, Endpoints, Querying and Sizing
Mosaic AI Vector Search is Databricks' serverless vector database: it holds embeddings in Unity Catalog-governed indexes on dedicated endpoints and returns the rows most similar to a query, which makes it the retrieval layer of RAG applications and agents on Databricks. This lesson covers its two building blocks (endpoints and indexes), the Delta Sync and Direct Vector Access index types, how to create and query an index from Python and SQL, how sync modes keep an index current, how similarity scores are computed, and how to choose the endpoint type and sync mode from the number of embeddings, update frequency, latency and cost a solution requires.
On this page10 sections
- What Mosaic AI Vector Search is: endpoints and indexes
- The index types: Delta Sync versus Direct Vector Access
- Creating a Delta Sync index
- Keeping an index current: triggered sync, continuous sync and direct writes
- Querying an index with similarity_search
- Querying an index from SQL with vector_search()
- How similarity is measured: HNSW, L2 distance and the returned score
- Choosing an endpoint type: standard versus storage-optimized
- Controlling cost and latency
- Who can query an index: Unity Catalog privileges
- Explain how Vector Search endpoints and indexes relate, including the per-endpoint index limit and that indexes are Unity Catalog objects
- Choose between Delta Sync (Databricks-computed or self-managed embeddings) and Direct Vector Access for a stated data flow
- Create a Delta Sync index with the correct embedding arguments and keep it current with triggered sync, continuous sync, or upsert and delete calls
- Query an index with similarity_search and vector_search(), choosing query_text or query_vector, columns, num_results, filters and query type
- Interpret similarity scores from HNSW with L2 distance and normalize vectors for cosine-equivalent ranking
- Configure endpoint type, sync mode and throughput for a workload's vector count, update frequency, latency and cost
- Grant the Unity Catalog privileges a service principal needs to query an index
What Mosaic AI Vector Search is: endpoints and indexes
Mosaic AI Vector Search is Databricks' serverless vector database: it stores embeddings in an approximate-nearest-neighbor index and returns the rows most similar to a query, which is the retrieval step of a RAG application or agent. Recent Databricks documentation also calls it Databricks AI Search (formerly Vector Search); the exam guide uses the Mosaic AI Vector Search name, and the core method names (create_delta_sync_index, similarity_search, sync, upsert) are the same.
It has two resources, and the exam expects you to keep them apart:
| Resource | What it is | Key facts |
|---|---|---|
| Vector Search endpoint | The serving compute that answers queries | Standard or storage-optimized type; one endpoint hosts up to 50 indexes; it bills while it hosts indexes, even with no traffic; workspace permissions CAN CREATE / CAN USE / CAN MANAGE |
| Vector index | The searchable copy of your vectors and synced columns | A Unity Catalog object named catalog.schema.index; created on one endpoint; governed with Unity Catalog grants like a table |
The relationship runs one way: an endpoint hosts many indexes, and each index lives on exactly one endpoint. They are created, governed and deleted separately. The 50-index limit applies whatever the endpoint type, index type or throughput setting, so when an endpoint is full the answer is another endpoint, not a bigger one. (Limits such as this one are Databricks product limits that can change; check the current documentation for exact figures.)
The index types: Delta Sync versus Direct Vector Access
Vector Search has two index types, distinguished by where the data comes from: a Delta Sync index follows a Delta table, and a Direct Vector Access index is written directly by your code. Delta Sync then splits again by who computes the embeddings.
| Index type | Source of rows | Who embeds | How it changes | Query with |
|---|---|---|---|---|
| Delta Sync, Databricks-computed (managed) embeddings | A Unity Catalog Delta table with a text column | Databricks, through a named embedding model serving endpoint, at sync and at query time | Sync from the table (triggered or continuous) | query_text (Databricks embeds it) |
| Delta Sync, self-managed embeddings | A Delta table that already has an array<float> vector column | You, in your own pipeline | Sync from the table (triggered or continuous) | query_vector embedded by you with the same model |
| Direct Vector Access | No source table: a schema you define | You (it has no managed-embedding mode) | Only your upsert and delete API calls | query_vector |
Choose by the constraints in the scenario:
- No embedding pipeline and no wish to write one: Delta Sync with Databricks-computed embeddings.
- Vectors already in the table (for example from a fine-tuned model) and no budget to embed twice: Delta Sync with self-managed embeddings.
- Records arrive from an application or stream and there is no Delta table, and the team accepts writing upsert code: Direct Vector Access.
Preparing the source table itself (chunking, a unique primary key, enabling change data feed) is covered in the data preparation lessons; here the point is that Delta Sync needs that table and Direct Vector Access does not have one.
Creating a Delta Sync index
You create a Delta Sync index with create_delta_sync_index (or the UI or REST API), naming the endpoint, the source table, the index name, the sync mode, the primary key, and one of two embedding argument pairs:
| Embedding mode | Arguments | Meaning |
|---|---|---|
| Databricks-computed | embedding_source_column + embedding_model_endpoint_name | The text column to embed, and the serving endpoint whose model embeds it (and embeds query_text later) |
| Self-managed | embedding_vector_column + embedding_dimension | The column that already holds vectors, and their length |
The pairs do not mix: a text column is never an embedding_vector_column, and a dimension without a vector column describes nothing.
client.create_delta_sync_index(
endpoint_name="manuals-endpoint",
source_table_name="main.docs.manual_sections",
index_name="main.docs.manual_sections_idx",
pipeline_type="TRIGGERED",
primary_key="section_id",
embedding_source_column="section_text",
embedding_model_endpoint_name="manuals-embedder",
)
Other arguments and options to know:
pipeline_type:"TRIGGERED"or"CONTINUOUS"(see the next section).columns_to_sync: limits which source columns are copied into the index (the primary key and embedding column are always included). It is fixed at creation and is not supported on storage-optimized endpoints, which sync every column.- Save computed embeddings: for an index with Databricks-computed embeddings, this option writes the vectors Databricks computed to a Unity Catalog table named after the index with
_writeback_tableappended. It is the way to reuse those vectors (for clustering or analysis) without paying to embed again; the source table never receives them. The table is managed with the index, is deleted with it, and should not be modified. - Storage-optimized endpoints also require an embedding dimension divisible by 16.
A Direct Vector Access index is created instead with create_direct_access_index, giving a primary key, the vector column, its dimension and a schema for the stored columns.
Keeping an index current: triggered sync, continuous sync and direct writes
How an index picks up new data depends on its type and, for Delta Sync, on its sync mode.
| Mode | When the index updates | Cost | Fits |
|---|---|---|---|
| Delta Sync, triggered | Only when you call sync() (SDK or REST) or press Sync now in the UI | Most cost-effective: compute runs only during a sync | Batch-updated tables: call sync() as the final task of the job that writes the table |
| Delta Sync, continuous | Within seconds of each table change, via an always-running streaming pipeline | Higher: the pipeline's cluster runs all the time | Tables edited throughout the day where the index must reflect edits almost immediately |
| Direct Vector Access | Immediately, when your code calls upsert or delete | No sync pipeline | Applications that already own the write path |
Three behaviours that scenarios turn on:
- Triggered indexes do not watch the table. New rows written to the source table are invisible until the next sync, because maintenance or metadata operations on the table itself do not touch the index; only a sync does.
- Direct Vector Access indexes have no link to any table. Deleting a record in the application's database leaves it in the index; you must call the index's delete API with the primary keys.
sync()and change data feed only apply to Delta Sync. - Storage-optimized endpoints support only triggered sync. A continuous index must live on a standard endpoint.
Changing the schema or the embedded column
An existing Delta Sync index does not accept source schema changes or a switch to a different embedding column. The documented approach is to build a new index, switch the application to it once it has synced, then delete the old one. Deleting first, even to reuse the name, leaves users with failed searches until the rebuild finishes. Changing the embedding model itself is covered in the embedding-model lesson; it also requires a re-embedded index.
Querying an index with similarity_search
You query an index with similarity_search on the index object, passing the query, the columns to return, how many results you want, and optionally filters and a query type.
results = index.similarity_search(
query_text="How do I replace the filter cartridge?",
columns=["section_id", "section_text", "manual_url"],
num_results=8,
filters={"product_line": "pumps", "revision >=": 3},
)
query_textversusquery_vector. Only an index with Databricks-computed embeddings knows which model to apply to text, so it acceptsquery_text. For self-managed and Direct Vector Access indexes, the application embeds the question itself, with the same model that produced the stored vectors, and passesquery_vector; a vector from another model lives in a different embedding space and returns poor matches.columnsdecides what each result row carries (plus a score). Listing a synced column, such as the chunk text or a source URL, returns it without changing the index.num_results,filtersandquery_typechange which rows come back and how they are ranked, not which columns they carry.num_results: request what you will use. Asking for far more rows than the chain keeps makes each query slower without changing the top results.query_type:"ANN"(default, vector similarity) or"HYBRID", which combines keyword (BM25-style) matching of the text with vector similarity, useful for exact identifiers such as part numbers. On a self-managed index, hybrid search needs bothquery_text(for the keyword half) andquery_vector(for the vector half). Hybrid queries use roughly twice the resources of ANN queries and return at most 200 results. The Python SDK and REST API also offer"FULL_TEXT"(Beta), a keyword-only search that uses no embeddings at all. (When to use hybrid search and reranking for quality is covered in the retrieval lessons.)- Result limits: the SDK paginates ANN results up to 10,000 rows (a current product limit, like the 200-result hybrid cap), so large result sets (for example several thousand candidates for an evaluation set) come from
similarity_searchin a notebook rather than from SQL.
Filter syntax depends on the endpoint type
| Endpoint | filters format | Example |
|---|---|---|
| Standard | A dictionary; operators go in the key | {"product_line": "pumps", "revision >=": 3, "status NOT": "archived"} |
| Storage-optimized | A SQL-like string | "product_line = 'pumps' AND revision >= 3" |
Moving an index to a storage-optimized endpoint therefore means rewriting dictionary filters as SQL-like strings: each endpoint type accepts only its own filter format.
Why a filtered query can come back empty on storage-optimized endpoints
Storage-optimized endpoints overfetch nearest neighbors and then apply the filter. If the matching rows are a tiny share of a huge index and none of them rank among the fetched candidates, the query returns no rows even though matching data exists. Large segments behave normally; very selective filters are the ones that suffer. Databricks documents this overfetch behaviour for storage-optimized endpoints.
Querying an index from SQL with vector_search()
vector_search() is a table-valued SQL function (Public Preview) that queries a Vector Search index from a serverless or pro SQL warehouse, so analysts and dashboards can retrieve similar rows without Python.
SELECT *
FROM vector_search(
index => 'main.docs.manual_sections_idx',
query_text => 'warranty claim steps',
num_results => 10
);
- Arguments are passed by name:
index, thenquery_text(for an index with Databricks-computed embeddings) orquery_vector(an array of numbers), plusnum_resultsand optionallyquery_type. - Supported query types are
ANNandHYBRID. num_resultsabove 100 is not supported, and there is no paging, so repeating the call does not reach further down the ranking. Use the SDK for larger result sets.- It does not support Direct Vector Access indexes.
Do not confuse it with ai_query(), which calls a model serving endpoint (an LLM or embedding model) rather than searching an index.
How similarity is measured: HNSW, L2 distance and the returned score
Vector Search finds neighbors with HNSW (an approximate nearest-neighbor graph) and measures closeness with L2 (Euclidean) distance. The score it returns is a similarity derived from that distance:
score = 1 / (1 + d²), where d is the L2 distance between the query vector and the stored vector.
Cosine similarity through normalization. Many embedding models are meant to be compared by cosine similarity. If both stored and query vectors are normalized to unit length, ordering by L2 distance gives the same order as cosine similarity, so normalize embeddings before writing them (and normalize query vectors the same way). Endpoint type, query type and numeric precision do not change the metric.
The score is not the cosine. For unit vectors, d² = 2 − 2 × cos. To turn a cosine cut-off into a score cut-off, substitute: a cosine of 0.7 gives d² = 0.6 and a score of 1 / 1.6 = 0.625. Filtering returned scores against the raw cosine value would discard relevant results.
Choosing an endpoint type: standard versus storage-optimized
Choose the endpoint type from the number of vectors, the latency and throughput target, and cost. The figures below are Databricks' published guidance at the time of writing; they change, so treat them as orders of magnitude.
| Standard endpoint | Storage-optimized endpoint | |
|---|---|---|
| Capacity | About 320 million vectors at 768 dimensions; capacity falls as dimension rises (roughly half, about 160 million, at 1536). A Direct Vector Access index holds far fewer (about 2 million at 768 dimensions) | More than a billion vectors |
| Query latency | Tens of milliseconds | A few hundred milliseconds |
| Throughput | Higher; scalable with target_qps | Modest |
| Cost per vector | Higher | Much lower (Databricks cites up to several times cheaper) |
| Sync modes | Triggered and continuous | Triggered only |
| columns_to_sync | Supported | Not supported (all columns sync) |
| Filter syntax | Dictionary | SQL-like string |
Reading a scenario:
- Customer-facing, tight latency, high QPS, index within capacity: standard. If load tests return HTTP 429 errors at peak, the fix is more provisioned serving capacity, which on a standard endpoint means setting
target_qps(at extra cost); client-side changes only reshape the same load.target_qpsraises throughput, not vector capacity. - Hundreds of millions to billions of vectors, latency of around half a second acceptable, low concurrency, tight budget: storage-optimized with triggered sync.
- Fits on either: let cost and latency decide. If the latency budget allows a few hundred milliseconds and traffic is light, storage-optimized is cheaper.
- Raising the embedding dimension shrinks how many vectors a standard endpoint holds. A corpus that fits at 768 dimensions can overflow at 1536. Keep the latency requirement fixed when you solve that: an option that makes room by giving up the latency or throughput the scenario demands does not meet it.
Controlling cost and latency
Most Vector Search cost comes from endpoints that exist and sync pipelines that run, and most avoidable latency comes from authentication, oversized requests and the embedding endpoint.
Cost levers
- Idle endpoints still bill. An endpoint hosting indexes incurs serving cost with no queries; Vector Search endpoints do not scale to zero. Consolidate small, lightly used indexes onto one endpoint (up to 50 per endpoint) and delete the empty endpoints.
- Prefer triggered sync when the table changes on a schedule; continuous sync runs a streaming cluster all the time.
- Use storage-optimized endpoints for large, latency-tolerant indexes, since they cost less per vector.
target_qpsandnum_resultsdo not reduce base serving cost;target_qpsadds capacity and cost.
Latency levers
- Authenticate with OAuth tokens for a service principal rather than personal access tokens. PATs add hundreds of milliseconds per query; OAuth uses network-optimized routing.
- Request only the results you use with
num_results(Databricks suggests 10 to 100 for most RAG use); more results means slower queries. - Use ANN unless keyword matching is needed; hybrid search costs extra resources.
- Do not let the embedding endpoint scale to zero. With Databricks-computed embeddings, every
query_textis first embedded by the model serving endpoint; if that endpoint has scaled to zero, the first queries after a quiet period can be slow or time out while the Vector Search endpoint itself looks healthy. - Separate the query embedding endpoint. A managed-embedding index can use a different serving endpoint for query embeddings (
model_endpoint_name_for_queryin the SDK) while the configured endpoint embeds ingestion. Heavy backfills then no longer starve user queries. The query endpoint must serve the same model, or query vectors land in a different space and retrieval quality changes.
Who can query an index: Unity Catalog privileges
Because an index is a Unity Catalog object, querying it as a non-owner requires USE CATALOG on the catalog, USE SCHEMA on the schema and SELECT on the index. This applies to a service principal that an agent or serving endpoint runs as.
- SELECT on the source table does not grant access to the index; the query reads the index, not the table.
- SELECT granted at the schema level works, because it is inherited by every object in the schema, but it grants far more than one index, so it fails a least-privilege requirement.
- Endpoint permissions (
CAN CREATE,CAN USE,CAN MANAGE) are workspace ACLs on the compute.CAN USElets a user create indexes on the endpoint; it is not what authorizes reading an index's data.
Passing the end user's identity through to retrieval (on-behalf-of-user authorization) and serving-endpoint access control are covered in their own lessons.
Tip. Expect scenario items that give a corpus size, embedding dimension, update pattern, latency or QPS target and budget, then ask for the endpoint type, sync mode or combination that fits, sometimes as a select-two. Other items show a create_delta_sync_index or similarity_search call, a filter, or a vector_search() query and ask which arguments are right or what change fixes an error, so know which argument pairs belong to which index type and which features each endpoint type lacks. Operational items describe a symptom (rows missing after a batch load, deleted records still returned, empty filtered results, first-query timeouts, 429 errors, steady idle charges, a permission error) and ask for the fix. Expect a calculation of a score threshold from a cosine cut-off, and questions on the endpoint-index relationship and the privileges needed to query an index.
- An endpoint is serving compute that hosts up to 50 indexes; each index is a Unity Catalog object governed with grants.
- Delta Sync follows a Delta table (Databricks-computed or self-managed embeddings); Direct Vector Access has no table and changes only through upsert and delete calls.
- Databricks-computed embeddings: embedding_source_column + embedding_model_endpoint_name, query with query_text. Self-managed: embedding_vector_column + embedding_dimension, query with query_vector from the same model.
- Triggered sync updates only when sync() or Sync now runs and is the cheapest mode; continuous sync is near real time but runs a cluster all the time and is standard-endpoint only.
- Schema or embedding-column changes need a new index: create it, switch the app, then delete the old one.
- Filters are a dictionary on standard endpoints and a SQL-like string on storage-optimized endpoints, where very selective filters can return nothing because of overfetch-then-filter.
- Scores are 1 / (1 + squared L2 distance); normalize vectors for cosine-equivalent ranking, and convert cosine cut-offs before thresholding scores.
- Standard endpoints: tens of milliseconds, high QPS via target_qps, capacity that shrinks as dimension grows. Storage-optimized: a billion-plus vectors, cheaper, a few hundred milliseconds, triggered sync only, no columns_to_sync.
- Cut cost by consolidating idle endpoints and using triggered sync; cut latency with service principal OAuth, smaller num_results, an always-on embedding endpoint and a separate query embedding endpoint serving the same model.
- A non-owner needs USE CATALOG, USE SCHEMA and SELECT on the index to query it.
Frequently asked questions
What is the difference between a Delta Sync index and a Direct Vector Access index in Databricks Vector Search?
A Delta Sync index is built from a Unity Catalog Delta table and stays current by syncing from it, either when sync() is called (triggered) or continuously. It can have Databricks compute the embeddings from a text column or use vectors already stored in the table. A Direct Vector Access index has no source table: the application writes vectors into it with upsert calls and removes them with delete calls, and it always stores vectors the caller supplies.
When should I use a storage-optimized Vector Search endpoint instead of a standard endpoint?
Use a storage-optimized endpoint when the index is very large (hundreds of millions to billions of vectors) or cost per vector matters more than speed, and the application can accept a few hundred milliseconds of retrieval latency at modest query rates. Use a standard endpoint when retrieval must take tens of milliseconds, when throughput must scale with target_qps, or when you need continuous sync or columns_to_sync, which storage-optimized endpoints do not support.
Why doesn't my triggered Vector Search index show rows I just added to the Delta table?
A Delta Sync index in triggered mode does not watch its source table. It only updates when sync() is called through the SDK or REST API, or when Sync now is pressed in the UI. Add a sync() call as the last step of the job that writes the table, or use continuous sync if edits must appear within seconds and the extra always-on cost is acceptable.
Should I pass query_text or query_vector to similarity_search?
Pass query_text only to an index with Databricks-computed embeddings, because that index knows which embedding model to apply. For an index with self-managed embeddings or a Direct Vector Access index, embed the question yourself with the same model that produced the stored vectors and pass query_vector. Hybrid search on a self-managed index needs both: query_text for keyword matching and query_vector for vector similarity.
How does Databricks Vector Search compute similarity scores?
Vector Search uses HNSW approximate nearest-neighbor search with L2 (Euclidean) distance and returns a score of 1 divided by (1 plus the squared distance). If you normalize all stored and query vectors to unit length, ranking by this score matches ranking by cosine similarity, but the score value is not the cosine: for unit vectors a cosine of 0.7 corresponds to a score of 0.625.
How do I reduce the cost of Mosaic AI Vector Search?
Endpoints that host indexes bill for serving even when idle and do not scale to zero, so consolidate small, rarely used indexes onto one endpoint (each holds up to 50) and delete the empty ones. Use triggered sync instead of continuous sync when the source table changes on a schedule, and host large indexes that tolerate a few hundred milliseconds of latency on storage-optimized endpoints, which cost less per vector.
What permissions does a service principal need to query a Vector Search index?
If the service principal does not own the index, it needs USE CATALOG on the catalog, USE SCHEMA on the schema and SELECT on the index itself. SELECT on the source table is not enough, and endpoint permissions such as CAN USE control creating indexes on the endpoint rather than reading an index.
Can I query a Vector Search index from SQL?
Yes. The vector_search() table-valued function (Public Preview) runs on serverless or pro SQL warehouses and takes named arguments such as index, query_text or query_vector, num_results and query_type (ANN or HYBRID). It returns at most 100 results with no paging and does not support Direct Vector Access indexes; use similarity_search in Python for larger result sets.
Source
This lesson covers the "Assembling and Deploying Applications" domain of the official Databricks GenAI exam guide. Vendors revise their guides — check the source for the current version.
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.