Skip to main content
Cost FixCosmos DBDiskANNMatryoshka EmbeddingsQuantization

The Runaway Vector Index Bill: Tuning Dimensions
& Quantization in Azure Cosmos DB for AI

Your bill is not high because AI is expensive. It is high because you are storing every document as a 3,072-number vector when — by OpenAI's own benchmarks — you could store it as a 256-number vector and get better retrieval than your predecessor architecture had at 1,536. The default was chosen for you, and it is the wrong one for almost every RAG workload.

The failure signature this guide resolves
# Azure Cosmos DB cost report — the line item nobody expected:
Resource: cosmos-rag-prod (Provisioned throughput, DiskANN vector index)
Period:   last 30 days

  Item                                            RU-hours       Cost (USD)
  Storage — indexed data                        18,240,000       $3,650
  Storage — VECTOR INDEX (3072-dim, 8M docs)                    $9,720   ← 62% of bill
  Point reads                                    2,100,000         $170
  Vector search queries                          5,900,000         $470
  ────────────────────────────────────────────────────────────────────────
  Total                                                          $15,650
                                                                 ↑
                                                 The vector index costs
                                                 MORE than the underlying
                                                 data it is indexing.

# The math driving it — floats are 4 bytes, and it multiplies fast:
#   1 vector @ 3072 dims = 3072 × 4 bytes = 12,288 bytes ≈ 12 KB
#   8,000,000 vectors    = 8M × 12 KB     = ~92 GB of index

# The same corpus at 512 dimensions:
#   1 vector @ 512 dims  = 512 × 4 bytes  = 2,048 bytes  ≈ 2 KB
#   8,000,000 vectors    = 8M × 2 KB      = ~15 GB of index    (6× smaller)

# The retrieval quality question everyone assumes will be worse:
#   text-embedding-3-large @ 256 dims:  MTEB 62.0
#   text-embedding-ada-002 @ 1536 dims: MTEB 61.0    ← the OLD default
#                                                     you were happy with
# A 12× smaller vector beats the model your predecessor RAG ran on.

Symptom: Cosmos DB storage and RU consumption growing linearly with your corpus, with the vector index becoming the single largest line item on the bill.  Failure point: Client passes no dimensions parameter to the embedding API → receives the default (3072 for text-embedding-3-large, 1536 for -3-small) → stores every vector at full width forever.  Default platform behaviour: Both the OpenAI SDK and the Cosmos DB indexing policy will happily accept whatever you send. Neither warns you that you are provisioning 6–12× more storage than the workload needs.

3072 → 256
A text-embedding-3-large vector truncated to 256 dims still beats the old ada-002 at 1536 on MTEB retrieval benchmark
12× smaller
Vector index size scales linearly with dimension count. Halving dimensions halves storage. 12× lower dimensions = 12× lower index footprint
1,000 vectors
The floor for quantizedFlat and diskANN. Below this, Cosmos silently falls back to a full scan — with higher RUs than the flat index
4,096
The hard dimension ceiling for quantizedFlat and diskANN. The flat index type caps at just 505 dims

There is a particular flavour of cost incident that is uniquely damaging because the outcome looks correct. Your RAG works. Retrieval is snappy. Users are happy. And the bill is quietly ratcheting upward, month after month, because every document in your corpus is being stored as a 3,072-number vector when it could just as effectively be stored as a 512-number one. Nothing is broken. Nothing will be broken tomorrow either. But you are paying six times more than you have to for an index that would perform better if it were smaller — and the reason is that the OpenAI SDK returns 3,072 dimensions when you do not tell it otherwise, and neither Cosmos DB nor your monitoring will ever point out that the default was chosen for you.

Figure 1 — Why the vector index outgrows the data: the four-byte multiplication
ONE VECTOR × EIGHT MILLION DOCUMENTS — HOW THE INDEX BILL COMPOUNDS3072 DIMS (default)12 KB / vector× 8M docs~92 GB indexthe "runaway bill"$9,720 / mo1536 DIMS (halved)6 KB / vector× 8M docs~46 GB~$4,860 / mo512 DIMS (compact)2 KB / vector× 8M docs~15 GB~$1,620 / mo256 DIMS (aggressive)1 KB / vector× 8M docs~8 GB~$810 / mo…AND at 256 dims, text-embedding-3-large STILL beats ada-002 at 1536 dims on MTEB (62.0 vs 61.0).Storage is linear in dimension count. The bill is not because vectors are expensive; it is because they are 12× longer than they need to be.Cost figures are illustrative; use them to model your own corpus, not as quotes. The ratio holds regardless of the absolute numbers.
Vector index storage grows linearly with dimension count because every vector is a flat array of 4-byte floats. There is nothing exotic about the math — but its unforgiving linearity is why an 8M-document corpus at the OpenAI default swings from four figures to five on a monthly bill. The quality lever (Matryoshka retention) works in the opposite direction, which is the entire pitch.
01The Cost Math: Why 3,072 Dimensions Is a Financial Decision, Not a Technical DefaultRoot Cause

A vector is not a magical AI object. It is a flat array of 32-bit floating-point numbers, each occupying 4 bytes. That is it. The size of your vector index in bytes is therefore a mechanical calculation:

The formula that determines your billindex_size_bytes = vector_count × dimensions × 4 × index_overhead # Working example — one document, three dimension choices: # text-embedding-3-large @ 3072 → 12,288 bytes/vector (~12 KB) # text-embedding-3-large @ 1024 → 4,096 bytes/vector (~ 4 KB) # text-embedding-3-large @ 256 → 1,024 bytes/vector (~ 1 KB) # index_overhead varies by index type. For the numbers here, treat it # as a small multiplier (~1.1-1.3x) — the raw vector storage dominates.

Two things follow from that formula, and they are the only two things you need to know to see the whole problem:

  • Storage is linear in dimensions. Halving dimensions halves storage. Quartering dimensions quarters storage. There is no economy of scale in the wrong direction to save you.
  • The multiplier is your corpus. At a thousand documents, nobody notices. At ten million documents, a 12× dimension difference is the difference between a hobby project and a five-figure monthly line item.

So the "runaway bill" is not exotic. It is the OpenAI default (3072 for text-embedding-3-large, 1536 for text-embedding-3-small) meeting a corpus that grew. Your predecessors chose those defaults implicitly, by not passing a dimensions parameter, and Cosmos DB happily indexed whatever arrived. Both systems worked exactly as designed. The problem is that the design assumes you know a lever exists.

The bill has two parts — don't optimise only one

Your Cosmos DB vector spend is not one line — it is storage (bytes at rest) plus RUs per query (compute at read time). Shrinking dimensions attacks both, because a smaller vector is faster to compare as well as cheaper to store. Switching to a smarter index type (Section 6) further reduces the RUs per query. If you only tune one of the two, you have half-fixed the problem — and it will keep growing on the other axis until you go back and do the second half. Treat this as a two-lever exercise from the start.

02Matryoshka Embeddings: Truncate Without RetrainingConcept

The instinct is that a smaller vector must mean worse retrieval, so shrinking dimensions must be a quality trade. That instinct is right for most embedding models — but not for the ones that matter here. OpenAI's text-embedding-3-small and text-embedding-3-large are trained with Matryoshka Representation Learning (MRL), and it changes the arithmetic completely.

MRL trains the model so that the first N dimensions of the vector carry the most important information — the coarse semantic summary — and later dimensions add progressively finer detail. Truncating to fewer dimensions loses nuance but keeps the signal. The dimensions are not equally valuable; the training deliberately concentrates value in the early ones.

This is what makes the headline result surprising. On OpenAI's own MTEB benchmark:

Model & dimensionsMTEB retrieval scoreRelative storage
text-embedding-ada-002 @ 1536 (the old default)61.0
text-embedding-3-large @ 25662.00.17× — six times smaller and better
text-embedding-3-large @ 1024~64.60.67×
text-embedding-3-large @ 3072 (default)64.6

Read that first two rows again. A 3-large vector truncated to 256 dimensions outperforms the ada-002 vector at its full 1,536. The truncated vector is one-twelfth the width and still wins. This is why "shrink the vector" is not a quality-cost trade for MRL-trained models — for many workloads it is a strict improvement, and any team still on 3,072 dimensions by default is paying to lose the trade in the wrong direction.

The one API parameter that fixes it — Pythonfrom openai import AzureOpenAI client = AzureOpenAI(...) # WRONG — accepts the 3072-dim default. This is what your predecessor did. resp = client.embeddings.create( model="text-embedding-3-large", input="How does semantic search work?" ) # RIGHT — request a shortened, MRL-truncated vector at embed time. # OpenAI handles the truncation server-side and RE-NORMALIZES the vector, # which is why this is NOT the same as slicing it yourself in Python. resp = client.embeddings.create( model="text-embedding-3-large", input="How does semantic search work?", dimensions=512 # any value; common choices: 256, 512, 1024 )
Do not manually slice the vector — pass dimensions

You can truncate a full-size vector in your own code by taking the first N values, but the resulting vector will not be re-normalised. OpenAI's server-side truncation via the dimensions parameter returns a properly normalised vector at the requested size, which is what cosine similarity assumes. Truncating client-side without renormalising subtly corrupts distance calculations for reasons that are hard to see until retrieval quality degrades on a specific slice of your data. Let the API do the truncation.

The retention curve is graceful — measure it, don't guess it

MRL is not magic. The gentle degradation curve is real but has limits, and it depends on your corpus. On the STS benchmark, an MRL model truncated to just 8% of its original dimensions kept ~98% of its full performance; a non-MRL model truncated the same way kept ~96%, and the gap widens fast as you cut deeper. So the pattern is: aggressive cuts are viable for MRL models, but the exact retention on your data is empirical. Build a golden set of query-to-expected-document pairs and measure recall@k at each candidate dimension size before you commit. Validation (Section 10) shows how.

03The Three Cosmos DB Index Types (and When Each Is Wrong)Correction

The brief calls the fix "scalar quantization." In Cosmos DB, that fix has a specific name — actually two — and choosing between them is where more cost incidents originate than dimension count does. Cosmos DB offers three vector index types, and the differences are stark.

Index typeMax dimsSearch algorithmAccuracyCost per query
flat505Brute-force (exhaustive)100% recallHighest RU cost
quantizedFlat4,096Brute-force over compressed vectorsSlightly below 100%Lower RU than flat
diskANN4,096Approximate nearest neighbour (graph)High but ANN — below quantizedFlat/flatLowest RU

The flat index gives you 100% recall — it is guaranteed to find the most similar vectors — but it has a hard 505-dimension ceiling, which means any real production embedding will not fit. That single number rules flat out of most modern RAG stacks.

quantizedFlat keeps the brute-force search behaviour but compresses the vectors themselves, giving you the same shape of guarantee at a fraction of the storage. And diskANN — Microsoft Research's approximate nearest neighbour algorithm — gives you the lowest RU cost and lowest latency at scale, at the price of trading 100% recall for very-high recall. It is what powers the built-in vector search in Cosmos DB.

The 1,000-vector floor is a trap in dev environments

Both quantizedFlat and diskANN require at least 1,000 vectors in the container before the index becomes effective — this is needed for the quantization to be accurate. Below that threshold, Cosmos DB silently falls back to a full scan, and the RU charges for those queries can be higher than a plain flat index would have been. This is why teams often build a proof-of-concept, watch it hit implausibly high RUs on a tiny dev corpus, and conclude the whole approach is expensive. The fix is not to change indexes — it is to reach 1,000 vectors, at which point costs drop dramatically. Benchmark on a corpus of realistic size.

Two more Cosmos DB constraints worth flagging up-front

Vector indexing and search on Cosmos DB for NoSQL are not supported on accounts with Shared Throughput — you must use provisioned or serverless. And once vector indexing is enabled on a container, it cannot be disabled. So the choice is not one you back out of casually. Do the dimension and index-type selection deliberately, on a new container, and validate before you point production at it.

Architectural Topology: Failing vs Remediated

LayerFailing configuration (current)Remediated configuration (fix)
Embed callNo dimensions param — accepts 3072-dim defaultExplicit dimensions=1024 (or lower) at embed time
Modeltext-embedding-3-large at full widthSame model, truncated via MRL — no retraining needed
Cosmos DB index typeflat, or no explicit choicequantizedFlat for accuracy-critical; diskANN for scale
Dimension ceilingCapped at 505 (flat) — you were forced to change something eventually4,096 (quantizedFlat / diskANN) — headroom, deliberately unused
quantizationByteSizeNot set — system decidesExplicitly tuned once you have measured recall vs RU
Storage per vector~12 KB at 3072 dims~1–4 KB at 256–1024 dims, further reduced by quantization
Query RUsHigh — brute force over 3072-float comparisonsSub-linear at scale on diskANN
Retrieval qualityAssumed good; never measuredRecall@k measured on a golden set; documented before commit
05Fix 1 — Set the dimensions Parameter at Embed TimeCheapest Win

This is the change that recovers the most money for the least effort. One parameter on the embedding call determines every downstream storage byte and every downstream query comparison. Set it deliberately.

Python — the ONE line that changes the bill (Azure OpenAI SDK)from openai import AzureOpenAI client = AzureOpenAI( api_key=os.environ["AZURE_OPENAI_API_KEY"], api_version="2024-10-21", azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], ) def embed(text: str, target_dims: int = 1024) -> list[float]: """ Return an MRL-truncated, re-normalized embedding at the requested size. Common target_dims: 256 (aggressive), 512 (balanced), 1024 (conservative). Cosmos DB flat index requires <= 505, so choose 256 or 384 if you must use flat; otherwise you're on quantizedFlat or diskANN. """ resp = client.embeddings.create( model="text-embedding-3-large", input=text, dimensions=target_dims, # <-- the whole point of this article ) return resp.data[0].embedding
C# — the same, with the Azure.AI.OpenAI SDKusing Azure; using Azure.AI.OpenAI; using OpenAI.Embeddings; AzureOpenAIClient azure = new(new Uri(endpoint), new AzureKeyCredential(key)); EmbeddingClient embedClient = azure.GetEmbeddingClient("text-embedding-3-large"); // Explicit dimensions — this is not the default. Set it or lose the money. EmbeddingGenerationOptions options = new() { Dimensions = 1024 }; OpenAIEmbedding e = await embedClient.GenerateEmbeddingAsync(text, options); ReadOnlyMemory<float> vector = e.ToFloats();

How to pick the target dimension

There is no universal right number, but there is a defensible decision procedure. Pick your candidate sizes from a short list — 256, 384, 512, 1024 — because MRL retention is most reliable at common power-of-two-ish cuts. Then measure. Do not eyeball it.

Target dimsUse whenStorage vs 3072
256Storage is the acute bottleneck; corpus is huge; queries are broad~ 12× smaller
512Mobile or edge deployments; also the ceiling if you insist on flat index (with room to spare)~ 6× smaller
1024Default recommendation. Balanced storage/quality trade for most production RAG~ 3× smaller
1536You've measured a meaningful recall drop at 1024 on your corpus~ 2× smaller
3072Almost never justified. Retention curve is nearly flat above 1024–15361× (baseline)
Corpus vs query dimension: the mistake that unifies all failures

The same dimension setting must be used for the vectors you index and the vectors you query with. This is not a Cosmos DB or an OpenAI limitation — it is how vector similarity works. Embed your entire corpus at 1024 dims and then embed queries at 512, and the resulting cosine distances are meaningless. If you have code paths that embed at different times (e.g. an ingestion pipeline and a query service), the dimensions setting must be shared configuration, not an inline literal. Get this wrong once and every silent recall regression that follows will look mysterious.

06Fix 2 — Switch to quantizedFlat or DiskANNStructural

Dimensions are the first lever. The index type is the second, and it does the other half of the job — reducing RU cost per query, and adding a further storage compression on top of what you already saved by shrinking the vector.

The decision is workload-shaped, not universal.

  • Small corpus (< 100K vectors) with hybrid filters: quantizedFlat. You're often filtering to a small candidate set with a WHERE clause, then vector-searching over what remains. Brute force is fine when the "brute" set is small, and quantization gives you the storage win.
  • Large corpus, latency-sensitive: diskANN. Its cost is highly sub-linear in the number of vectors — going from 100K to 1M barely raises per-query RUs. It is the reason Cosmos DB can search a 35M-vector corpus at low latency at all.
  • Absolute-recall-critical, small dims: flat. Only if you can live inside the 505-dimension ceiling.
Container-level vector policy — embedding + index in one place{ "id": "docs", "partitionKey": { "paths": ["/tenantId"], "kind": "Hash" }, // The embedding metadata — Cosmos needs to know the SHAPE of the vector "vectorEmbeddingPolicy": { "vectorEmbeddings": [ { "path": "/embedding", "dataType": "float32", "distanceFunction": "cosine", "dimensions": 1024 // MUST match your embed() output } ] }, // The indexing policy — this is where you pick the index type "indexingPolicy": { "indexingMode": "consistent", "automatic": true, "includedPaths": [ { "path": "/*" } ], "excludedPaths": [ { "path": "/embedding/*" } ], // exclude from range/hash index "vectorIndexes": [ { "path": "/embedding", "type": "diskANN", // or "quantizedFlat" or "flat" "quantizationByteSize": 96, // see Section 7 for tuning "indexingSearchListSize": 100 // DiskANN only; default 100 } ] } }
Azure CLI — create a new container with the vector policy# Vector indexing cannot be enabled on an existing container. # You MUST create a new container and migrate. See Section 8 for the trap. az cosmosdb sql container create \ --account-name cosmos-rag-prod \ --database-name rag \ --name docs \ --resource-group rg-ai-prod \ --partition-key-path /tenantId \ --throughput 4000 \ --idx @container-vector-policy.json
Exclude the vector path from the normal index

A subtle but material step: add the vector path (e.g. /embedding/*) to excludedPaths in the normal indexing policy. If you leave it included, Cosmos DB will attempt to index the array elements individually as scalar range/hash entries in addition to your vector index — bloating storage and RU consumption for zero benefit. The vector index is the correct home for that data; the general index has no reason to be there.

Figure 2 — Two independent levers, multiplied — dimension count × index compression
CUMULATIVE SAVINGS — each lever multiplies with the otherLEVEL 0 — BASELINEtext-embedding-3-large @ 3072 dims · flat index · ~12 KB per vector · ~$9,720/mo (illustrative)1.0×+ LEVER 1 — dimensions=1024MRL truncation via the API · ~4 KB per vector · quality nearly identical · ~$3,240/mo3× smaller+ LEVER 2 — quantizedFlat indexfurther byte compression at rest · brute force over smaller vectors · lower RU per query · ~$1,300/mo~7× smaller+ LEVER 2b — diskANN at scaleRU per query sub-linear in corpus size — the compute lever, not the storage lever · latency drops tooRU ↓↓Illustrative dollar figures — the ratios are the point, not the absolute values. Model your own corpus.
Dimension count and index type are independent levers, and their gains multiply rather than add. Cutting from 3,072 to 1,024 dimensions gives you a ~3× storage reduction on its own. Layering quantizedFlat compression on top compounds that further at rest, while diskANN attacks the read-side by making per-query RU cost sub-linear in corpus size. Do only one of the two and you have half-fixed the bill.
07Fix 3 — Tune quantizationByteSize (the Second Cost Lever)Tuning

Once you've picked a quantized index type, one more parameter deserves a deliberate value: quantizationByteSize. It applies to both quantizedFlat and diskANN, and it controls how much each vector is compressed in the index — separate from, and layered on top of, the dimension count you chose at embed time.

ParameterRangeApplies toEffect
quantizationByteSizeMin 1, Max 512, default dynamic (system decides)quantizedFlat + diskANNLarger = higher accuracy, higher RU, higher latency. Smaller = the opposite
quantizerTypeproduct or sphericalquantizedFlat + diskANNMethod of quantization. Default is usually appropriate
indexingSearchListSizeMin 10, Max 500, default 100DiskANN onlyHow many vectors are searched during index construction. Larger = better recall, longer index build times, higher ingest latency
Do not tune this on the first day

Leave quantizationByteSize at its default (system-chosen) until you have a working baseline. It is a fine-tuning knob, not a starter setting, and turning it before you have a benchmark to compare against usually degrades one of the three axes (accuracy, RU, latency) invisibly. When you do tune it, hold every other variable fixed — same corpus, same query set, same dimension count — and measure recall@k and RU per query at each candidate byte size. Then pick the point on the curve where a further increase costs more RU than it recovers in recall.

Illustrative — how the three knobs interact in practice# Small corpus (~50K vectors), accuracy critical, hybrid filters used: { "type": "quantizedFlat", "quantizationByteSize": 128 # more bytes = closer to exact } # Large corpus (5M+ vectors), latency and RU cost critical: { "type": "diskANN", "quantizationByteSize": 64, # aggressive compression "indexingSearchListSize": 100 # default; raise if recall too low } # Medium corpus, no strong preference yet — LET COSMOS DECIDE: { "type": "diskANN" # omit quantizationByteSize entirely }
08⚠ The Re-Indexing Trap: Why You Cannot Migrate HalfwayCritical

This is the section that saves you from the incident, because the fix in this article cannot be applied in place, and the failure mode of an incorrect migration is silent — semantically meaningless retrieval that will not throw an error but will quietly degrade your RAG for anyone reading the answers.

Three hard constraints, all of them stated by Microsoft:

  • Vector indexing cannot be enabled on an existing container. To use vector search — or to change how it is configured — you must create a new container and specify the vector embedding policy at creation time.
  • Once vector indexing is enabled on a container, it cannot be disabled. This choice is one-way.
  • Vectors are only comparable within the space of the model that produced them, at the dimension count they were produced at. Mixing dimension sizes across a single index is not a supported operation; it is a corruption.

Put together, this means the migration has a shape that must be planned:

  1. Create a new Cosmos DB container with the desired vector policy — target dimensions, target index type, target quantizationByteSize.
  2. Re-embed the entire corpus at the target dimension count. Not slice, not truncate client-side — re-embed, so vectors are re-normalised by OpenAI at the target size.
  3. Ingest the new vectors into the new container.
  4. Update your query path to embed queries at the same target dimension.
  5. Cut over. Deprecate the old container.
You cannot query a corpus with vectors of a different width than it was indexed with

This is a variant of the embedding-space failure mode: mixing dimensions across indexing and querying corrupts distance calculations. If your ingest pipeline re-embeds at 1024 dims and your query service still calls the embedding API without a dimensions parameter — which returns the 3072 default — you now have a 3072-dim query trying to search a 1024-dim index. In the best case Cosmos DB rejects the request. In the worst case, if some layer of your stack silently pads or truncates, you get retrieved "results" that are semantically meaningless. Share the dimension setting through one config value that both paths import.

Blue-green the migration; measure before you swing traffic

Do not big-bang the cutover. Ingest into the new container in parallel with the old one running, then run a period of shadow queries — send each production query to both containers and compare recall@k on a golden set. Only when the new container is measurably at least as good on your actual traffic do you flip the switch. If you skip the shadow period, the day the new container becomes canonical is the day you might discover that 1024 dims was too aggressive for your specific corpus, and by then the old container may already be deprovisioned.

09Anti-Patterns: Cost "Fixes" That Wreck RetrievalTraps

Because vector-cost problems are so obvious once the bill arrives, the wrong instincts are strong. These are the fixes teams reach for that either fail silently or create larger problems than they solve.

Anti-patternWhy it feels rightWhy it isn't
Truncate the vector in client code (vec[:1024])"It's the same numbers — how could it be different?"Not re-normalised. Cosine distance assumes unit vectors. Retrieval quietly degrades on subsets of the data
Downgrade to text-embedding-3-small"Smaller model, smaller vectors, smaller bill"Different model = different vector space. You must re-index the whole corpus AND lose the MRL retention advantage. Often worse than a truncated -3-large
Delete the vector index and re-enable it"Fresh start with better settings"Once vector indexing is on, it cannot be disabled. You need a new container, not a policy edit
Assume 8-bit is always safe because "quantization""Every DB does it, mustn't be a big deal"Cosmos DB's quantizationByteSize is a per-vector budget, not a global 8-bit switch. Under-tuning it drops recall well below where the same corpus would sit at full width
Skip the 1,000-vector floor test"Dev works, prod will work"Below 1,000 vectors, both quantizedFlat and diskANN silently full-scan. Dev shows higher RUs than prod, and you tune the wrong knob
Tune quantizationByteSize before measuring"Bigger number, higher accuracy — obviously"You are trading RU for accuracy you cannot see. Without a golden set, you're not tuning, you're guessing

Validation & Verification: Confirm the Fix

The two failure modes are opposite. One: the bill did not actually go down (your dimensions parameter is not being applied). Two: the bill went down but retrieval quality went with it (Matryoshka retention is not as gentle as the benchmarks promised on your data). Validate both, in that order.

Step 1 — Confirm the dimension parameter is actually being applied# Sanity check: your embed function is returning the size you configured. # If this is 3072 when you expected 1024, nothing you tuned below matters. >>> v = embed("sample query", target_dims=1024) >>> len(v) 1024 # PASS - as configured # Spot-check a random document from the CORPUS side (not just the query # side — a mismatch between them is the silent failure). >>> doc = container.read_item("some-known-id", partition_key="tenant-1") >>> len(doc["embedding"]) 1024 # PASS - matches the query dimension
Step 2 — THE quality test: recall@k against a golden set# Do this BEFORE any production cutover. 50-100 real query/answer pairs # from actual user traffic is enough to catch a meaningful regression. GOLDEN = [ ("what is the return policy for damaged items?", "policy-doc-42"), ("do you ship internationally to Australia?", "shipping-doc-7"), # ... 50-100 pairs, drawn from production query logs ] def recall_at_k(container, query_dims: int, k: int = 5) -> float: hits = 0 for query_text, expected_doc_id in GOLDEN: qv = embed(query_text, target_dims=query_dims) results = container.query_items( query="SELECT TOP @k c.id FROM c " "ORDER BY VectorDistance(c.embedding, @qv)", parameters=[{"name": "@k", "value": k}, {"name": "@qv", "value": qv}], enable_cross_partition_query=True, ) if expected_doc_id in {r["id"] for r in results}: hits += 1 return hits / len(GOLDEN) # Compare the SAME golden set against BOTH containers side by side: print(f"recall@5 at 3072 dims (old): {recall_at_k(old_container, 3072):.3f}") print(f"recall@5 at 1024 dims (new): {recall_at_k(new_container, 1024):.3f}") # PASS: new recall is within an acceptable margin (typically 1-3 points). # FAIL: a meaningful drop means 1024 is too aggressive for YOUR corpus. # Try 1536 before you conclude the whole approach doesn't work.
Step 3 — Prove the bill actually moved (RU + storage)# A. Storage: point at the new container and read it directly. az cosmosdb sql container show \ --account-name cosmos-rag-prod --database-name rag --name docs-v2 \ --resource-group rg-ai-prod \ --query "resource.statistics.storageInKB" # B. RU per query: run a fixed workload against BOTH containers and compare # the RU charge header. This is measurable per-request from the SDK: result = container.query_items( query="SELECT TOP 10 c.id FROM c " "ORDER BY VectorDistance(c.embedding, @qv)", parameters=[{"name": "@qv", "value": query_vector}], enable_cross_partition_query=True, populate_query_metrics=True, ) list(result) # materialize to trigger the query ru_charge = container.client_connection.last_response_headers[ "x-ms-request-charge"] print(f"RU per query: {ru_charge}") # PASS: RU per query drops meaningfully, AND storage size dropped in proportion # to the dimension ratio (roughly 3072/1024 = 3x for pure vector data).
What "fixed" actually means here

Three conditions must hold together. One: your query path is verifiably returning vectors at the target dimension — not the API default sneaking back in behind you. Two: recall@k on a golden set drawn from real production queries is within an acceptable margin of the baseline; if MRL retention did not hold at your chosen size on your specific corpus, you find out here, before users do. Three: both storage size and RU per query are meaningfully lower on the new container. Miss any of the three and you have won a partial argument — the bill went down but retrieval degraded, or retrieval held but the bill did not actually move because a query path was silently defaulting to 3072. Only when all three land is the fix real.

Key Takeaways

Storage is linear in dimensions. Halving dimensions halves storage. Quartering dimensions quarters storage. The runaway bill is not exotic — it is 4 bytes × N floats × M documents, and M is growing.
text-embedding-3-large at 256 dims outperforms ada-002 at 1536 on MTEB. A 12× smaller vector beats the model your predecessor architecture was happy with. For MRL-trained models, "shrink the vector" is often a strict improvement.
Pass the dimensions parameter — never truncate in client code. Server-side truncation returns a re-normalised vector. Slicing the array yourself corrupts cosine distances in ways that are hard to see.
Two levers, multiplied. Dimension count + Cosmos DB index type (quantizedFlat or diskANN). Doing one and not the other half-fixes the problem.
The 1,000-vector floor bites in dev. Below it, Cosmos falls back to full scan with higher RU cost than flat. Benchmark at production-realistic scale or you tune the wrong knob.
Vector indexing is one-way per container. You cannot enable it on an existing container, and you cannot disable it once set. Migration means a new container, blue-green cutover, and a golden set.
Measure recall, not just megabytes. A cheaper index that retrieves worse is not a saving — it is a regression with a smaller invoice. Golden-set recall@k is the only claim that can't be faked.

Frequently Asked Questions

Will shrinking my embeddings to 512 or 1024 dimensions hurt my RAG quality?
Probably not meaningfully — and it may not hurt at all — but the honest answer is that this is empirical, not universal. OpenAI's text-embedding-3-large and -3-small are trained with Matryoshka Representation Learning, which packs the most important semantic information into the earliest dimensions. On OpenAI's own MTEB retrieval benchmark, text-embedding-3-large truncated to 256 dimensions still outperforms the previous-generation text-embedding-ada-002 at its full 1,536 — so a 12× smaller vector is beating the model your predecessor stack was probably built on. That said, retention on your specific corpus depends on the corpus. Build a golden set of 50–100 real query/expected-document pairs, run recall@k at each candidate dimension size, and use the numbers to choose. Do not guess.
What's the difference between quantizedFlat and DiskANN in Cosmos DB?
Both are quantized (compressed) vector indexes and both support up to 4,096 dimensions, but they use different search algorithms. quantizedFlat performs a brute-force search over the compressed vectors — it will examine every candidate, but each comparison is cheap. It is the right choice when you use query filters to narrow down to a small candidate set before the vector search, and when very high accuracy matters. diskANN is Microsoft Research's approximate nearest neighbour graph algorithm; its query cost is sub-linear in the number of vectors, so going from 100,000 to a million vectors barely changes per-query RUs. That makes it the right choice at scale, or when latency and RU cost are the primary constraints. The trade is that DiskANN is approximate — accuracy is usually very high but is not the 100% recall of the pure flat index. For most production RAG workloads over a large corpus, DiskANN is the default.
Can I change the vector index type on an existing Cosmos DB container?
No, and this catches teams out. Two Microsoft-documented constraints combine here: vector search cannot be enabled on an existing container, and once it is enabled, it cannot be disabled. So to change the vector policy — dimension count, index type, quantizationByteSize, anything — you must create a new container with the desired policy, re-embed the corpus at the target dimension count, ingest into the new container, and cut over. Run the two containers in parallel for a window while you shadow queries and confirm recall@k has held. Then deprecate the old container. A big-bang cutover is where most cost migrations turn into retrieval-quality incidents.
Why can't I just slice the vector to fewer numbers in my code?
Because the resulting vector is not normalised. Cosine similarity — which is what Cosmos DB uses when you set distanceFunction: "cosine" — assumes unit-length vectors, and OpenAI's dimensions parameter returns a properly normalised vector at the requested size. Taking the first N values yourself in Python or C# gives you a vector that looks like a shorter version of the original but has a different magnitude, which subtly warps every distance calculation you then perform. The failure mode is silent: retrieval "works" but returns degraded results on a slice of queries whose semantic profile happens to be more sensitive to the missing normalisation. Pass the dimensions parameter to the API and let OpenAI do the truncation; the extra work on their side is exactly the point.

Popular posts from this blog

Learn how to use Azure Chaos Studio to simulate data center outages, test Azure OpenAI failover, and validate AI app resiliency using KQL and CLI workflows

Resiliency Testing Chaos Studio Zone Down Azure OpenAI Failover Testing AI Resiliency: Using Azure Chaos Studio to Simulate Data Center Outages on Your LLM Every multi-region Azure OpenAI architecture diagram has a failover arrow drawn on it. Almost none of them have ever actually been triggered. The arrow is a hypothesis, confirmed only by a real outage — unless you deliberately cause a controlled one first, on your own schedule, with a rollback plan, instead of finding out during an incident that the failover you designed never quite worked the way the diagram promised. The failure signature this guide resolves # The gap this article closes — a real architecture review finding: Design doc, page 4: "In the event of a regional outage, Azure Front Door automatically routes traffic to the secondary Azure OpenAI deployment in West Europe, with an expected failover time under 60 seconds." Verification performed to support this claim: NONE. Last time this path was ac...

Explore Amazon’s AI strategy: how AWS Bedrock hosts frontier models like Claude, uses Trainium silicon, and bets on cloud neutrality.

The Cloud Incumbent: AWS Bedrock Hosts Every Frontier Model and Amazon Is Betting on Neutrality AWS at $37.6 billion quarterly revenue, growing 28%. $13 billion invested in Anthropic. A $100 billion Anthropic-to-AWS commitment. Trainium with $225 billion in customer revenue commitments. The most quietly powerful AI strategy in the race. By Francis Avorgbedor | Azure Engineer  ·  July 4, 2026  ·  14 min read  ·  Amazon · AWS · Cloud AI 74 SEVENAI Momentum Score — Rank #5 $37.6B AWS Q1 2026 revenue — 28% YoY growth ▲ Fastest in 15 quarters $13B Total Amazon investment in Anthropic to date ▲ Strategic anchor 100K+ Customers running Claude on AWS Bedrock ▲ Distribution moat Amazon's AI strategy is built on a thesis that every other Magnificent Seven company is testing against — and that Amazon is uniquely positioned to win regardless of the outcome. The thesis is neutrality. In a race where Microsoft has bet on OpenAI, Google has bet on Gemini, and Meta has bet...

Reduce AI application latency spikes by offloading embeddings to Foundry Local 1.2, improving edge performance, responsiveness, scalability, and cost efficiency.

Performance Fix Foundry Local 1.2 Linux ARM64 Embeddings Offline ASR The Edge Latency Drop: Fixing Latency Spikes by Offloading Embeddings to Foundry Local 1.2 You are paying a full cloud round trip — network, TLS, queue, throttle risk — to turn a twelve-word search query into a vector. That is the most expensive way possible to do one of the cheapest computations in your stack. Foundry Local 1.2 now runs on Linux ARM64, which means embeddings and speech recognition can happen on a Raspberry Pi, a Jetson, or a Graviton instance — offline, unmetered, and in single-digit milliseconds. The failure signature this guide resolves # Application Insights — the embedding call, not the LLM, is your tail latency: name p50 p95 p99 calls/day POST /embeddings (cloud) 89 ms 412 ms 3,847 ms 1,240,000 POST /chat/completions (cloud) 940 ms 1,720 ms 2,910 ms 38,000 ^^^^^^^^ ...

Find the hidden Windows 10 system files consuming up to 500GB, including hibernation, shadow copies, backups, and WinSxS, with safe cleanup steps.

  The 500GB System File That Eats Your Hard Drive Something on your Windows 10 drive is consuming hundreds of gigabytes and the normal tools cannot find it. This guide identifies every known culprit — from hibernation files and shadow copies to runaway backups and the Windows component store — and tells you exactly what is safe to delete, what to leave alone, and what the commands actually do.
How to Reset an Azure Virtual Machine to Factory Settings Using a Managed Disk Azure does not have a single "factory reset" button. What it does have is something better: the OS Disk Swap — a method that swaps out the corrupted or misconfigured OS disk for a clean Windows Server managed disk without deleting the VM, its NICs, its IP addresses, or any attached data disks. Here is how it works, when to use it, and the exact steps to execute it safely. FA Francis Avorgbedor Azure Engineer July 16, 2026 15 min read Azure VMs · Windows Server · Real-World Fix 3 Methods to achieve a clean Windows Server installation on an existing Azure VM ~15min Typical OS Disk Swap duration — VM retains its NICs, IPs, and data disks throughout 0 Data disks affected by an OS Disk Swap — data disks remain attached and untouched 1 Snapshot of the original OS disk you must take before starting — no exceptions Introduction Why Azure Does Not Have a Simple Factory Reset — and What to Do Instead On a ph...

Learn how to select Azure Files and Blob storage tiers, avoid early deletion fees, model costs, and automate lifecycle management for large file migrations.

Choosing the Right Azure Storage Tier for Large File Migrations The complete decision framework for storage tier selection during large file migrations — Azure Files tiers, Blob tiers, cost modelling, early deletion traps, lifecycle automation, and the 2026 changes that affect every migration running today. By Francis Avorgbedor | Azure Engineer  ·  July 14, 2026  ·  18 min read  ·  Storage Tiers · Cost Optimisation · Migration FA Francis Avorgbedor Azure Engineer  ·  SEVENAI  ·  Azure Field Notes 9 Distinct Azure storage tiers across Files and Blob — most engineers know only 3 15hrs Archive tier rehydration time at standard priority — the delay teams forget to plan for 128KB Minimum billable object size for Cool/Cold/Archive from July 2026 — a 32× trap for small files 70% How much retrieval and transaction fees add to a theoretical Archive storage bill The most expensive mistake I see on large Azure file migrations is not choosing the w...
Incident Playbook AKS Kubernetes kubectl 2026 AKS CrashLoopBackOff, Pending Pods, and NotReady Nodes — The Real Fixes Engineers Use Every AKS engineer eventually faces the same nightmare: CrashLoopBackOff at 2am, pods stuck Pending for no clear reason, or nodes flipping to NotReady mid-deployment. The difference between panic and control is knowing the exact diagnostic sequence — and the real fixes that work in production. This guide gives you both. 3 commands get pods, describe pod, and logs diagnose roughly 90% of AKS incidents before you touch anything else Exit 137 The code that means OOMKilled — the container hit its memory limit and was killed by the kernel (128 + SIGKILL 9) Events The bottom of kubectl describe is where the real cause lives — Pending, FailedScheduling, and image errors all surface there CoreDNS The single component behind most "intermittent" production failures — service discovery breaks quietly and looks like an app bug Table of Contents 01 The 3 Comm...
Can I Update My Old Computer to Windows 11 — and How Much Will It Cost? Your i7, 16GB RAM, 512GB SSD machine is powerful enough to run Windows 11 comfortably. The TPM 2.0 and Secure Boot wall is a security checkbox, not a performance ceiling. Here are two proven ways to get past it, what each one costs, and what you are trading away by doing so. $0 Cost of the Windows 11 licence if your existing Windows 10 is genuine — the upgrade remains free in 2026 2 Proven methods to bypass TPM 2.0 and Secure Boot — Rufus (easy) and Registry edit (manual) 25H2 Current Windows 11 version — all known bypass methods tested and confirmed working as of July 2026 Oct 2025 Windows 10 end of life — no more security updates. Staying on Windows 10 now carries real risk. First — Check Your BIOS Before Anything Else You Might Not Actually Need a Bypass Before running any bypass, open your BIOS and look at two settings. Many computers that fail the Windows 11 compatibility check have TPM 2.0 present in the hard...
Troubleshooting Guide AKS Kubernetes Real Solutions kubectl Azure Kubernetes Service (AKS) Troubleshooting Guide: Real Solutions to Common Problems CrashLoopBackOff at 2am. Pods stuck Pending with no obvious cause. Nodes going NotReady mid-deployment. DNS resolution silently failing in production. Every AKS engineer encounters these — the difference between engineers who panic and engineers who stay calm is knowing the exact sequence of diagnostic commands to run. This guide gives you that sequence, the root cause analysis for each failure mode, and the fix. 3 commands 90% of AKS problems are diagnosed with the same three kubectl commands: describe pod, logs --previous, and get events — in that order, every time Exit 137 The exit code that tells you everything: container killed by SIGKILL — either the Linux OOM killer (memory limit exceeded) or kubelet after grace period expired 5 min The CrashLoopBackOff ceiling: Kubernetes applies exponential backoff (10s → 20s → 40s → 80s → 160s → 3...
2026 Edition 100 Tools Software Engineering DevOps AIOps Top 100 Best AI Tools for Azure  Engineers and DevOps Professionals in 2026 85% of developers now regularly use AI tools. Fully AI-generated code accounts for nearly 28% of all pull requests. The question is no longer whether to use AI tools — it is which ones, in which combination, for which part of the lifecycle. This guide cuts through the noise: 100 tools, 10 categories, honest pricing, real use cases, and a selection framework for building your stack without redundancy. 85% Percentage of developers who now regularly use AI tools, per JetBrains' 2025 State of Developer Ecosystem report — up from near zero three years ago 28% Share of all pull requests containing primarily AI-generated code in 2026 — the metric that signals AI coding assistants have moved from experiment to workflow $50B Cursor's reported valuation in April 2026 Series D talks — the number that signals investor confidence in the AI developer tools mark...