DP-420 common mistakes
DP-420 · Understand the traps and how to avoid them.
Choosing the item's unique ID (or a low-cardinality value such as a status or tenant-type field) as the partition key, then only discovering the problem when one logical partition throttles at the 20 GB / single-partition throughput ceiling.
Candidates treat the partition key as a synonym for the primary key and optimize for uniqueness instead of for even storage and RU distribution across the query workload.
What to do instead
Pick a partition key with high cardinality that is also the most common equality filter in your queries (e.g., tenantId, userId, deviceId). Validate by simulating writes and checking the normalized RU charge per partition; keep the unique ID as the item 'id' only.
Wrapping unrelated items in a TransactionalBatch (or the JS stored-procedure equivalent) that span multiple partition keys, or exceeding 100 operations / 2 MB payload, and expecting an atomic commit.
Batch APIs in other databases accept heterogeneous rows, so candidates assume Cosmos DB batches are also cross-partition and only learn the constraint at runtime from a 400 error.
What to do instead
Model related entities that must be written atomically under the same partition key value (denormalize into an embedded array or use the same pk), keep batches under 100 operations and 2 MB, and fall back to individual item operations or a change-feed-driven saga for cross-partition consistency.
Implementing optimistic concurrency by comparing a manually stored 'version' property in application code, or by passing the ETag as the wrong access condition (e.g., using IfNoneMatch when the intent is update-only-if-unchanged).
Candidates know the concept of optimistic concurrency but not the SDK surface: ETags come back on the item's response headers and must be fed into ItemRequestOptions.IfMatchEtag with AccessConditionType.IfMatch.
What to do instead
Read the item, take the ETag from the response, and send it back in ItemRequestOptions { IfMatchEtag = etag }; treat a 412 Precondition Failed as the conflict signal and retry. Use IfNoneMatch = "*" only to enforce create-if-absent.
Assuming the change feed returns delete operations and full history out of the box, or building a custom checkpointer instead of configuring the lease container (with its own partition key and throughput) for the change feed processor.
Most tutorials show the default 'latest version' mode, so candidates do not realize deletes require 'all versions and deletes' continuous backup mode, and they under-specify lease container settings.
What to do instead
For deletes or intermediate versions, enable continuous backup (7/30-day tier) and use ChangeFeedStartFrom/ChangeFeedMode = AllVersionsAndDeletes; always supply a separate lease container, set the instance name and lease prefix, and let the processor manage checkpoints rather than storing continuation tokens yourself.
Writing queries that omit the partition key in the WHERE clause for point-read scenarios, then accepting the resulting cross-partition fan-out RU charge because 'it still returns the right answer'.
Query correctness hides cost. Candidates validate results, not RU consumption, and never open the query metrics/execution statistics to see the number of partitions hit.
What to do instead
Include the partition key as an equality predicate for point reads and use ReadItemAsync when the id and pk are both known. Inspect QueryMetrics / the portal's query stats for partition count and RU charge, and only use cross-partition queries deliberately with the right indexing and (if needed) a composite index.
Relying on ORDER BY across two or more properties, or on aggregates over non-indexed paths, without adding a composite index - and expecting an indexing policy change to take effect immediately for existing data.
Candidates assume the default 'include /*' policy covers every query shape, and they also treat indexing policy edits as instantaneous rather than an asynchronous index transformation.
What to do instead
Add composite indexes in the same order as the ORDER BY clauses (including the partition key for multi-property sorts), exclude unused paths to cut write RU cost, and remember that changing the policy triggers an asynchronous reindex - monitor indexing progress before benchmarking.
Provisioning shared database throughput when containers have very different load profiles, or setting autoscale max RU/s to the expected average and being surprised that the minimum billable autoscale throughput is 10% of max (with a floor of 1000 RU/s).
Candidates conflate manual and autoscale semantics and do not account for the fact that shared throughput is split across containers rather than guaranteed per container.
What to do instead
Give hot containers dedicated throughput and share only among low-traffic containers; size autoscale max for peak and remember the 10%-or-1000-RU/s floor for the bill; use serverless only for spiky/dev workloads where per-request billing wins.
Connecting applications with the account primary key in a connection string, or assigning the control-plane 'Cosmos DB Account Reader/Contributor' role and expecting it to grant read/write access to items.
Candidates mix up management-plane RBAC roles (which govern the account resource in ARM) with data-plane RBAC roles (which govern items, containers, and stored procedures), and default to keys because they always work.
What to do instead
Use Microsoft Entra ID with data-plane built-in roles such as 'Cosmos DB Built-in Data Contributor' or a custom role definition scoped to the container/partition key; for services, use a managed identity and DefaultAzureCredential. Reserve account keys for break-glass and rotate them.
Creating a container with a vector embedding policy and then trying to change the dimensions or distance function afterwards, or querying with VectorDistance using a distance function that does not match the container's declared vector policy.
Candidates treat the vector policy like an indexing policy that can be edited later, and they do not match the distance function between the index definition and the query expression.
What to do instead
Fix dimensions, data type, and distance function (cosine, dotproduct, or euclidean) at container creation - they are immutable - and choose the same function in VectorDistance calls. Use DiskANN (or quantizedFlat) for the vector index, include the vector path in the indexing policy, and verify TOP_N plus a filter predicate before relying on results.
Assuming the analytical store can be queried through the normal Cosmos DB SDK/query API, or that analytical TTL and transactional TTL are the same setting - and enabling Synapse Link only to find the container was created without analytical store enabled.
Candidates blur the boundary between the transactional row store and the column-oriented analytical store, and they do not realize analytical storage must be opted into per container and is read via Synapse, not via the item APIs.
What to do instead
Enable analytical store on the account and the container (with its own analytical TTL, independent of transactional TTL, or -1 for no expiry), and query it through Azure Synapse Link/Spark or the Synapse serverless SQL pool using the analytical store's columnar schema. Keep transactional queries on the row store and do not expect analytical store to serve point reads.