Azure Cosmos DB: The Complete Guide from Core Ideas to Production Mastery
A deep, plain-English tour of Microsoft's globally distributed database: how it stores data, charges for work, spreads across the planet, stays consistent, stays secure, and how to design it so it is fast and affordable at any scale.
Imagine a huge restaurant chain with branches in Mumbai, London, Sydney and New York. Every branch keeps its own recipe book, and every customer expects the same dish, served quickly, no matter which branch they walk into. If a chef in London improves a recipe, the other branches should learn about it fast. If the Mumbai kitchen suddenly catches fire, customers should simply be served at the next branch without noticing anything. Now replace recipes with data, and branches with data centers. That is the problem Azure Cosmos DB was built to solve.
This guide assumes you know nothing about Cosmos DB. Every new word is explained the first time it appears, with an everyday comparison. But we do not stop at the basics. By the end you will understand the internal machinery: partitions, Request Units, indexing, consistency, replication, the change feed, and the security and cost levers that decide whether a real system succeeds or fails. Read it slowly, chapter by chapter, and the pieces will click together.
1What Cosmos DB Is and Why It Exists
Before any detail, we need a clear picture of the problem and the promise.
A database, but built for the whole planet
Azure Cosmos DB is a fully managed database service on Microsoft Azure. “Fully managed” means Microsoft runs the servers, applies patches, replaces broken hardware, keeps copies of your data, and monitors health. You never log into a machine. You create an account, create a container for your data, and start reading and writing. The service is often described as a NoSQL database, which simply means that it does not force your data into the rigid rows and columns of a traditional relational database. Instead, the most common form stores flexible JSON documents, where each document can have its own shape.
What makes Cosmos DB special is not that it stores JSON. Many systems do. It is special because of a bundle of promises that are very hard to deliver together: single-digit millisecond response times at the 99th percentile for ordinary reads and writes, elastic scaling in both storage and throughput, replication to any number of Azure regions with a click, multiple choices for how fresh your reads must be, automatic indexing, and published availability guarantees of up to 99.999 percent for multi-region accounts.
A traditional database is like one very large library in one city. If you live far away, every trip is long, and if the building floods, the books are gone. Cosmos DB is like a library system with identical branches in many cities, a smart librarian who instantly knows which shelf holds each book, and a rule book that lets you choose how strictly every branch must stay in sync with the others.
The problems it was designed to solve
Modern applications face three hard problems at once. First, users are everywhere, and distance creates delay because even light takes time to cross an ocean. Second, traffic is unpredictable: a shopping site may be quiet on Tuesday and overwhelmed on a sale day. Third, schemas change constantly because products evolve every week. Cosmos DB answers these with global distribution, elastic throughput, and schema-agnostic storage.
Those latency figures describe well-designed operations, such as reading one item when you know its identity and partition key. A badly designed query that touches every partition will be slower no matter how fast the engine is. Much of this guide is about learning to ask for data the cheap way.
Where Cosmos DB fits and where it does not
Cosmos DB shines for workloads with high volume, many users, global reach, flexible data shapes, and a need for predictable low latency. Examples include shopping carts, user profiles, gaming leaderboards, device telemetry, personalization, order tracking and metadata stores for AI applications. It is a weaker choice for heavy multi-table reporting with complex joins, for ad hoc analytics scanning entire datasets, or for a small single-region application that a plain relational database would serve at a fraction of the cost. Knowing when not to use it is part of expertise.
2Multi-Model Thinking and the Available APIs
Cosmos DB speaks several database “languages”, and choosing the right one early matters.
An API in this context is the set of commands and protocols your application uses to talk to the database. Cosmos DB offers several, and the choice is made when you create the account. You generally cannot change it afterwards, so it deserves care.
NoSQL (core)
The native API. JSON documents queried with a SQL-like language. Receives new features first, including vector search and the richest tooling.
MongoDB
Speaks the MongoDB wire protocol so existing MongoDB applications can connect with little change. Offered in a request-unit model and a vCore model.
Apache Cassandra
For wide-column workloads and teams with Cassandra drivers and skills who want a managed service without cluster operations.
Apache Gremlin
For graph data: people, places and the relationships between them, such as friend networks or recommendation paths.
Table
A simple key-value style API, handy for applications moving up from Azure Table Storage and needing global distribution and better latency.
PostgreSQL option
A distributed PostgreSQL flavor exists for relational workloads that need to scale out. Check current availability and guidance before planning around it.
Think of one airport serving many airlines. The runway, fuel and control tower are shared infrastructure, but each airline has its own counters and uniforms. The APIs are the airlines; the shared engine underneath, with its partitioning, replication and storage, is the airport.
How to choose
If you are building something new, the NoSQL API is usually the best choice because it is the most complete and the most actively improved. If you are migrating an existing application that already uses MongoDB or Cassandra drivers, the matching compatible API lets you move with minimal code changes. Be honest about compatibility: a compatible API implements a large portion of the original behavior, but not everything. Always test your real workload and check the list of supported features before committing.
Many beginners think the different APIs are different products stored in different places. They are different front doors to the same family of infrastructure. But the data created through one API is not readable through another within the same account, so you cannot freely mix them on the same data.
The rest of this guide focuses on the NoSQL API concepts, because nearly all of the core ideas such as Request Units, partitioning, consistency and global distribution apply across the family in some form.
3The Resource Model: Account, Database, Container, Item
Every Cosmos DB design starts from a four-level hierarchy.
flowchart TD
A["Azure Subscription"] --> B["Cosmos DB Account"]
B --> C["Database"]
C --> D["Container"]
D --> E["Items"]
B --> F["Region list and default consistency"]
D --> G["Partition key, indexing policy, TTL, unique keys"]
Account
The top-level unit. It carries the choice of API, the list of Azure regions, the default consistency level, networking rules, backup policy and the keys or identities used for access. Think of it as the whole company.
Database
A logical grouping of containers, like a department. It can optionally hold throughput that all of its containers share.
Container
The real unit of scale and the place where almost every important decision lives: partition key, indexing policy, time-to-live, unique key constraints and, often, provisioned throughput. A container is like a filing cabinet for items of related purpose.
Item
One JSON document, such as a customer or an order. Each item has an id that is unique within its partition key value, and can be up to about 2 MB in size.
Why the container is the star
Beginners from relational backgrounds often create one container per entity type, mimicking tables. That habit is expensive. Because throughput, partitioning and indexing all attach to containers, many small containers each provisioned separately can waste money, and cross-container operations cannot be done in one transaction. Experienced designers frequently place several related item types into one container, distinguishing them with a type property, so that related data shares a partition and can be read together.
Ask: “Which items are read together or updated together?” Items that answer yes usually belong in the same container with the same partition key value.
Identity of an item
An item is uniquely identified by the pair of its partition key value and its id. This pair is the fastest possible way to reach it, called a point read. Remember this pair; it will keep returning in later chapters because it is the heart of cheap, fast access.
4Request Units: The Currency of Cosmos DB
You do not pay for CPU, memory or disk operations separately. You pay in one blended unit.
A Request Unit, usually written RU, is an abstract measure of the resources an operation consumes: processor time, memory and input and output work, all rolled into one number. The service tells you the RU charge of every operation in the response. This simplicity is powerful, because you can predict cost from design rather than guess from hardware.
Imagine a coffee shop that does not charge separately for water, beans, electricity and the barista’s time. Instead, every drink has a single price in tokens. A glass of water costs one token, a latte costs five, a complex seasonal specialty costs twenty. You buy a certain number of tokens per second of shop time, and when you run out, new customers have to wait a moment.
Rules of thumb that every designer memorizes
The anchor point is that reading a 1 KB item by its id and partition key costs about 1 RU. Writing a similar item costs several times more, commonly around five or more, because the write must be replicated and indexed. Queries cost depending on how many items they scan, how many they return, how many index entries they use and whether they cross partitions. Larger items cost more. More indexed properties cost more on writes. Stronger consistency costs more on reads.
| Operation | Typical relative cost | Why |
|---|---|---|
| Point read by id and partition key | Lowest, about 1 RU for 1 KB | Direct route to the exact location, no index scan required |
| Write or replace of an item | Several times a point read | Replication to replicas plus index maintenance |
| Single-partition query with a good filter | Low to moderate | Index narrows the scan and only one partition is involved |
| Cross-partition query | Moderate to high | The request fans out to many partitions and merges results |
| Query with no useful index or large scan | High | Many items are loaded and examined to find few matches |
What happens when you exceed your budget
Throughput is expressed as RU per second. If your requests need more than you have provisioned in a given second, the service rejects the extra with an HTTP 429 response, politely named “request rate too large”, and tells the client how long to wait before retrying. Official client libraries retry automatically a limited number of times. A few 429s are normal and healthy at high utilization. Many sustained 429s mean the container is undersized or, more often, that one partition is overloaded while others sit idle, which is a design flaw we will examine later.
Treating a 429 as an error to be eliminated at all cost. A system with zero throttling is usually over-provisioned and overpaying. The goal is to keep throttling rare and well handled, not to buy enough capacity to never see it.
Why RUs make capacity planning possible
Because every operation has a measurable RU cost, you can estimate needs with simple arithmetic: expected reads per second times their cost, plus writes per second times theirs, plus queries. Teams that skip this exercise often discover surprise bills. Teams that practice it can compare design alternatives, such as embedding versus referencing, by their RU impact before writing production code.
5Partitioning: How Cosmos DB Scales Out
One idea explains almost all of Cosmos DB’s speed and almost all of its pitfalls.
A single machine can only hold so much data and serve so many requests. To go beyond that, Cosmos DB splits a container into pieces and spreads them across many machines. This is called horizontal partitioning, or sharding. The service uses the partition key, a property you choose when you create the container, to decide which piece each item belongs to. The key’s value is passed through a hash function, which turns it into a number that decides placement.
Partition key
The property you choose, such as a customer id, whose value decides which group an item joins.
Logical partition
All items sharing the same partition key value. Capped at 20 GB of data. Transactions and stored procedures operate inside one logical partition.
Physical partition
The actual storage and compute unit managed by the service. It hosts many logical partitions, holds up to about 50 GB, and serves up to about 10,000 RU/s.
Replica set
Each physical partition is kept as several copies on separate machines, so a failure never loses data.
graph TD
I["Items with partition key values"] --> H["Hash of partition key value"]
H --> L1["Logical partition: customer A"]
H --> L2["Logical partition: customer B"]
H --> L3["Logical partition: customer C"]
H --> L4["Logical partition: customer D"]
L1 --> P1["Physical partition 1"]
L2 --> P1
L3 --> P2["Physical partition 2"]
L4 --> P2
A large school sorts student files by class section. Each section’s folder is a logical partition. Several folders sit together in a physical filing cabinet. When a cabinet gets too full or too busy, the office splits its folders across two cabinets. You never rearrange the folders yourself; the school office, here the service, does it.
Automatic splits and the throughput consequence
When a physical partition grows beyond its storage limit, or when its share of throughput would exceed its limit, the service splits it into two, moving data in the background without downtime. Here is the subtle part: the throughput you provision for a container is divided evenly among its physical partitions. If you provision 30,000 RU/s and the container has three physical partitions, each can use about 10,000 RU/s. If all of your traffic hits one partition, you are limited to that partition’s share, and you may be throttled while the other two partitions are idle and the bill continues.
“I provisioned 30,000 RU/s so I can do 30,000 RU/s of work.” Only if your requests spread evenly. Throughput is a pool split by partition, not a free-for-all. Uneven spread is called a hot partition, and it is the single most common cause of mysterious throttling.
Partition key choices are mostly permanent
You choose the partition key when creating the container, and changing it later means creating a new container and copying data. Tools and features exist to help with such migrations, but they are projects, not toggles. This is why the next chapter, on choosing a key, is the most important design decision in the whole guide.
6Choosing a Great Partition Key
A good key spreads data and traffic evenly and lets your most common queries stay inside one partition.
Three qualities define a good partition key. First, high cardinality: many distinct values, so that data can spread across many logical partitions. Second, even distribution of both storage and request volume across those values. Third, alignment with your most frequent queries so that they carry the key in their filter and are served by a single partition. A key that fails the first two causes hot partitions. A key that fails the third causes expensive cross-partition fan-out.
flowchart TD
A["List your top queries and writes"] --> B["Find the property most queries filter on"]
B --> C{"Many distinct values?"}
C -->|No| D["Consider a synthetic or hierarchical key"]
C -->|Yes| E{"Traffic spread evenly across values?"}
E -->|No| D
E -->|Yes| F{"Each value stays under 20 GB?"}
F -->|No| D
F -->|Yes| G["Strong candidate key"]
Examples of good and bad keys
Good: customer id for an orders container
There are many customers, each has a modest number of orders, and the most common question is “show this customer’s orders”. The query filters on the key, so one partition answers it.
Good: device id for telemetry
Thousands or millions of devices write independently, and dashboards typically focus on one device at a time. Load spreads naturally.
Bad: country
Only a couple of hundred values exist, and a few countries hold most of the users. A handful of partitions do all the work and may even hit the 20 GB limit.
Bad: current date for a write-heavy log
All of today’s writes land on one value, so one partition receives the entire write load while yesterday’s partitions sit idle. It is the classic hot partition in time-series design.
Synthetic keys
When no single property works, you can build a synthetic key by combining properties, for example joining a tenant id with a month, or adding a small number suffix to spread a very busy value across several partitions. The trade-off is that queries now need to know how the key is composed, and spreading one value over several partitions means reading them all to see everything for that value.
Hierarchical partition keys
Hierarchical partition keys let you define up to three levels, such as tenant, then user, then session. The service treats the combination as the key while allowing queries that supply only the first level or two to be routed to a targeted subset of partitions instead of everything. They also help overcome the 20 GB limit for a single large tenant, because that tenant’s data can spread across sub-levels. For multi-tenant systems with one giant customer and many small ones, this feature can solve a problem that was once painful.
A postal address works hierarchically: country, then city, then street, then house. A letter to a specific city can be routed to that city’s sorting office without checking every house in the world. Hierarchical keys give the database the same ability to narrow down.
Measure before deciding. Model realistic data volumes and request patterns, then check how requests would distribute across key values. Ten minutes of arithmetic on a spreadsheet prevents months of regret.
7Throughput Modes: Manual, Autoscale and Serverless
Three ways to pay for capacity, each fitting a different traffic personality.
Provisioned, manual
You set a fixed RU/s figure and pay for it every hour, whether you use it or not. You may change the number whenever you like. Best for steady, predictable workloads.
Provisioned, autoscale
You set a maximum, and the system scales between ten percent of it and the full amount based on demand, billing for the highest level reached in each hour. Best for variable or spiky traffic.
Serverless
No provisioning at all. You pay only for the RUs your requests actually consume, plus storage. Best for development, testing and small or occasional workloads.
| Question | Manual | Autoscale | Serverless |
|---|---|---|---|
| Traffic pattern | Steady and predictable | Variable, with peaks | Occasional or experimental |
| Billing basis | Provisioned RU/s per hour | Highest scaled RU/s in each hour | RUs consumed per request |
| Idle cost | Full price for the provisioned amount | A fraction, since it scales down | Almost none beyond storage |
| Scale ceiling | Very high | Very high | Lower limits per container |
| Multi-region | Supported | Supported | Historically limited to one region |
Manual throughput is like renting a fixed-size delivery truck for the month. Autoscale is a truck that grows and shrinks and bills you for the biggest size used each hour. Serverless is paying a courier per parcel, which is cheap if you ship rarely and costly if you ship constantly.
Dedicated versus shared throughput
You can provision throughput on a single container, giving it guaranteed capacity, or on a database so that its containers share a pool. Shared throughput is economical for many lightly used containers, but one noisy container can consume the pool and starve the others, and you cannot guarantee any single container a minimum. Many production teams use dedicated throughput for important containers and a shared pool only for small supporting ones.
Burst capacity and right-sizing
The service can allow short bursts above provisioned throughput by using idle capacity accumulated in recent seconds, which helps with brief spikes. It should be seen as a safety margin, not a plan. For sizing, watch the normalized RU consumption metric: if the busiest partition is consistently near one hundred percent while the account average is low, you have a distribution problem, not a capacity problem, and buying more RU/s will not solve it.
If your usage stays above roughly two-thirds of the maximum for most of the day, manual throughput often costs less. If it swings widely, autoscale usually wins. If it is occasional, serverless wins.
8Data Modeling: Embed or Reference
The biggest mindset shift from relational databases is that you design for your questions, not for purity.
In a relational database you normalize: break data into many tables to avoid repetition, then join them at read time. Joins across machines are expensive in a distributed system, so Cosmos DB encourages the opposite for data that is read together: put it inside one document. This is called embedding or denormalization. The alternative, keeping separate documents and storing the id of one inside the other, is called referencing.
Embedding: Advantages
- One point read returns the whole picture
- Updates to the whole unit are atomic in a single item
- No joins, so fewer round trips and lower RU cost
- Very fast for read-heavy access patterns
- Simple application logic for related data
Embedding: Trade-offs
- Documents can grow without bound if you embed lists that keep growing
- Repeated data must be updated in many places if it changes
- Large documents cost more for every read and write
- Items are limited to about 2 MB
A decision guide
Embed when the related data is read together, changes together, is bounded in size, and rarely changes independently. A customer’s shipping addresses, or the line items of an order, are classic examples. Reference when the related data grows without limit, is shared by many parents, changes frequently on its own, or is rarely needed with the parent. A product’s thousands of reviews, or a blog post’s unlimited comments, should be separate items.
Embedding is like writing a guest’s whole itinerary on one card handed to the front desk. Referencing is like giving the desk a card that says “see folder 42”. The single card is faster to use, but if the itinerary grows to a hundred pages it becomes unusable, and then the folder approach is wiser.
Mixing item types in one container
A powerful pattern is to store a customer and its orders as different item types in the same container, all sharing the customer id as the partition key. A single query on that key can then return both, and everything for one customer lives together on one partition. A type property tells the application which shape it is reading. This gives some of the convenience of joins without cross-partition cost.
Problem
A document keeps an ever-growing array, such as every event ever recorded for a user, inside the user item.
Why It’s Harmful
The item grows toward its size limit, every update rewrites the entire document at rising RU cost, and concurrent updates collide.
Correct Approach
Store each event as its own item, sharing the user’s partition key, and keep only a small summary or counters in the user item.
Write down your top ten access patterns before drawing any model. Each pattern should be answerable by a point read or a single-partition query. If one is not, that is the pattern to redesign for first.
9Indexing Internals: Automatic, Flexible and Tunable
By default, Cosmos DB indexes everything. That is a gift and a cost.
An index is a separate structure that lets the database find items without examining every one, just like the index at the back of a book lets you find a topic without reading every page. In many databases you must create indexes by hand and forget to. In Cosmos DB, every property of every item is indexed automatically by default, with no schema or manual effort. The engine does this by converting each item into a tree of paths and values and indexing those paths. This is why you can query on any property from day one.
The indexing policy
Each container has an indexing policy that controls what is indexed. You can include or exclude specific paths, choose the indexing mode, and add special indexes for particular needs.
Range index
The general-purpose index supporting equality checks, comparisons, sorting and string operations on the indexed paths.
Composite index
Combines several properties in a defined order. Needed to sort by multiple properties and speeds up queries filtering on several at once.
Spatial index
Supports questions about location, such as which points fall within a distance or inside an area.
Vector and full-text
Specialized indexes for similarity search over embeddings and for keyword-style text search. Covered in the AI chapter.
Why tune it
Every indexed path adds work to each write, increasing RU cost and storage. If your documents have fifty properties but you only ever filter on three, indexing the other forty-seven is waste. A common production approach is to exclude everything by default and include only the paths used in filters and sorts. For write-heavy workloads with large documents, this change alone can cut the cost of writes dramatically.
A book index listing every single word would be as thick as the book itself and take forever to maintain. A good index lists only the topics readers actually look up. Tuning an indexing policy is choosing which topics deserve an entry.
Indexing modes and consistency of the index
In the standard mode, the index is updated as part of each write so queries immediately reflect new data. An option to turn indexing off exists for containers used only for point reads by id, where no queries are needed, which makes writes cheaper still. Changes to the policy are applied in the background by re-indexing, which consumes spare throughput and may take time on large containers, so plan changes for quieter periods.
Excluding paths and then being surprised that queries on them become full scans. Always verify the RU charge and scanned count of your critical queries after changing a policy.
10Querying and Query Cost Tuning
Understand what a query does under the hood and you can predict what it will cost.
The NoSQL API uses a query language that resembles standard SQL, with filters, projections, sorting, aggregates and grouping, plus operators for working with arrays and nested objects. It does not support arbitrary joins across containers. A join in this language means combining an item with its own embedded arrays, which stays inside one item and one partition.
sequenceDiagram
participant App as Application
participant SDK as Client library
participant GW as Routing layer
participant P1 as Partition 1
participant P2 as Partition 2
App->>SDK: Query with partition key
SDK->>GW: Route to one partition
GW->>P1: Execute using index
P1-->>App: Results and RU charge
App->>SDK: Query without partition key
SDK->>GW: Fan out to all partitions
GW->>P1: Execute
GW->>P2: Execute
P1-->>SDK: Partial results
P2-->>SDK: Partial results
SDK-->>App: Merged results and total RU charge
The cost levers of a query
Include the partition key in the filter
This confines work to one partition. It is the biggest lever, often turning a costly fan-out into a cheap targeted lookup.
Make the filter selective and index-friendly
Filters on indexed properties with equality or range conditions let the engine jump to matches. Functions applied to a property, such as transforming it before comparing, can prevent index use.
Project only the properties you need
Returning fewer fields lowers the data transferred and the work done, particularly for large items.
Add composite indexes for multi-property sorts
Sorting on several properties needs a matching composite index, and combined filters on several fields benefit from one too.
Page through results with continuation tokens
Instead of fetching everything at once, retrieve results in pages and carry a token to resume. This keeps memory and per-request cost predictable.
Whenever you can, replace a query with a point read. If you already know the id and partition key, a point read costs a fraction of even the best query. Design identifiers that your application naturally knows.
Query metrics
Responses can include metrics describing how many items were scanned versus returned, index lookup time, and where the time went. A healthy query scans roughly as many items as it returns. A large gap between scanned and returned, a “scan to result ratio”, signals a missing or unusable index. Make reviewing these numbers a regular habit for your ten most expensive queries.
Problem
The application routinely runs queries without the partition key, relying on cross-partition fan-out for its main screens.
Why It’s Harmful
Cost and latency rise with every new physical partition. A query that is fine at ten partitions becomes painful at a thousand, even though the code never changed.
Correct Approach
Choose a partition key that matches the main access pattern, or keep a second container, fed by the change feed, organized around the other key.
11The Five Consistency Levels
A distributed database must answer one awkward question: when I write in one place, when will others see it?
When data is copied across machines and regions, copies cannot all update at the exact same instant. There is a trade-off between how fresh your reads are, how fast they are, how much they cost, and how available the system stays during problems. Many databases offer only two choices: strict or loose. Cosmos DB offers five well-defined points along a spectrum, so you can match the guarantee to the need.
graph LR
A["Strong"] --> B["Bounded Staleness"]
B --> C["Session"]
C --> D["Consistent Prefix"]
D --> E["Eventual"]
Strong
Every read returns the most recent committed write, as if there were one copy. Highest latency and cost. Not available together with multi-region writes.
Bounded staleness
Reads may lag behind writes, but only by a limit you define, either a number of versions or a span of time. Order is preserved.
Session
The default and most popular. Within one client session, you always see your own writes and reads never go backward. Other sessions may lag slightly.
Consistent prefix
Reads never see writes out of order. You may be behind, but you never see a later update without the earlier ones that came before it.
Eventual
Copies converge eventually, with no ordering promise in the meantime. Lowest latency and cost, suitable when occasional staleness is harmless.
Picture a live cricket score shared among branches of a newspaper. Strong consistency is a single scoreboard that everyone must read directly, accurate but slow. Session consistency is you checking your own scribbled updates first, so you never see your numbers vanish. Eventual consistency is each branch updating its board whenever news reaches it; the boards agree in the end, but briefly may differ.
Cost and performance implications
Stronger levels need more coordination between replicas, so reads under strong or bounded staleness consume roughly double the RUs of the weaker levels. In multi-region setups, strong consistency adds the round-trip time between regions to the write path, which is why it is often impractical across continents. The default session level hits the sweet spot for most applications: users see their own actions instantly, and the system stays fast and cheap.
Relaxing consistency per request
The account has a default level, but an individual request can ask for a weaker level than the default, never a stronger one. That means an application can keep a conservative default and relax it for read-heavy, low-risk screens like a product catalog browse.
Choosing strong consistency “to be safe” without measuring. It quietly raises cost and latency across every read. Ask first whether stale data for a few hundred milliseconds would truly hurt the business. For most screens it would not.
Session tokens in practice
Session consistency works by passing a small token between the client and the service that records how far the client has seen. If your application is spread over many servers behind a load balancer, a user’s second request may land on a different server that lacks the token. For flows that need strict read-your-writes across servers, the application must carry the token along, for example inside the user’s own request state. Forgetting this is a classic source of confusing “my update vanished” reports.
12Global Distribution and Multi-Region Writes
Adding a region is a configuration change, not a migration project.
You can associate an account with any number of Azure regions, and the service replicates your data to each automatically. Applications then read from the region closest to them, cutting latency. There are two modes of operation. In single-write-region mode, one region accepts writes and the rest serve reads. In multi-region writes mode, every region accepts both reads and writes, giving users in every continent fast writes and a higher availability target.
flowchart LR
U1["Users in Asia"] --> R1["Region: Central India"]
U2["Users in Europe"] --> R2["Region: North Europe"]
U3["Users in Americas"] --> R3["Region: East US"]
R1 R2
R2 R3
R1 R3
Advantages
- Lower latency for users worldwide
- Survives the loss of an entire region
- Higher availability SLA with multi-region setups
- Regions can be added or removed without downtime
- Supports data residency by choosing where copies live
Disadvantages / Trade-offs
- Cost multiplies with each region, for both storage and throughput
- Multi-region writes introduce the possibility of write conflicts
- Strong consistency is not available with multi-region writes
- Applications must be region-aware to benefit fully
Conflicts and how they are resolved
If two users in different regions change the same item at nearly the same moment, a conflict occurs. The default policy is last writer wins, based on a timestamp property that the system maintains, so the later change quietly replaces the earlier one. You can instead choose a custom numeric property as the deciding value, or choose a policy that records conflicts into a feed so that your own code can inspect and merge them. For most applications the best approach is to avoid conflicts by design, for example by making each user “belong” to a home region for writes or by partitioning data so that a given item is normally written from one place.
Two editors in different cities edit the same shared page and press save within the same second. Last writer wins is like the system keeping whichever save carries the later timestamp. Custom resolution is like asking a supervisor to read both versions and combine them.
Failover and region priority
Regions have a priority order. If the write region becomes unavailable in a single-write setup, the service can fail over automatically to the next region in the list. Clients using the official libraries are told about the region list and redirect themselves, and you can set a preferred read order so that each application instance reads from the nearest healthy region. Rehearse failover in a test environment; teams that never practice often discover assumptions in their code only in a real outage.
Add a second region for resilience before adding it for latency. Many organizations start with one extra region in the same geography purely for disaster protection, then expand outward as user bases grow.
13High Availability, Zones and Backup
Availability is how often the service answers. Durability is whether your data survives. Backup is the last line of defense.
Layers of protection
Inside a single region, every physical partition is stored as a replica set of several copies, so a failed machine is invisible. With availability zones enabled, those copies are spread across physically separate buildings, each with independent power and cooling, so even the loss of a whole zone does not interrupt service. Across regions, geo-replication protects against regional disasters. Each added layer raises the availability the service promises, from 99.99 percent for a single region, a little higher with zones, up to 99.999 percent for multi-region accounts.
Backup modes
| Feature | Periodic backup | Continuous backup |
|---|---|---|
| How it works | Snapshots taken at set intervals and kept for a set time | Ongoing capture allowing restore to any moment within the retention window |
| Default behavior | Snapshot every few hours with a small number of copies retained | Opt-in, with short or longer retention tiers |
| Restore granularity | Nearest snapshot | A precise point in time |
| Restore method | Request through Microsoft support | Self-service restore into a new account |
| Best for | Basic protection needs | Accidental deletes and corruption recovery |
Periodic backup is a photo taken of your desk every four hours. If a coffee spills at 5:15, you restore the 4:00 photo and lose an hour of work. Continuous backup is a video recording of the desk that you can rewind to 5:14.
“Replication means I do not need backups.” Replication copies mistakes too. If your application deletes the wrong data, every region faithfully deletes it. Replication guards against hardware and region failure; backup guards against human and software error.
Recovery thinking
Two numbers define a recovery plan: how much data you can afford to lose, and how long you can afford to be down. Multi-region accounts with automatic failover tend to give very small data loss windows and short downtime for region failures. For accidental deletion, only a point-in-time restore helps, and restores go into a new account, so rehearse the steps of switching your application to it. Document who is allowed to trigger it and how long it takes.
14The Change Feed: A Log of What Happened
Every create and update is quietly recorded, ready for you to react to.
The change feed is a persistent, ordered record of changes made to the items in a container. Rather than polling “has anything changed?”, your code reads the feed and receives each change exactly as it arrived. It is built in, requires no setup, and does not slow normal operations. Order is guaranteed within a single partition key value, not across the whole container.
flowchart LR
A["Application writes items"] --> B["Container"]
B --> C["Change feed"]
C --> D["Change feed processor"]
D --> E["Search index update"]
D --> F["Message bus event"]
D --> G["Materialized view container"]
D --> H["Analytics pipeline"]
What teams use it for
Event-driven architecture
When an order is placed, downstream services such as shipping, email and billing are triggered by the new item without the order service knowing about them.
Materialized views
Keep a second container organized by a different partition key so that an alternate access pattern becomes a cheap single-partition read, kept up to date by the feed.
Search and cache synchronization
Push updates to a search engine or refresh caches as soon as data changes.
Data movement and migration
Copy data to another container or another system with minimal disruption to the live workload.
Processing at scale
The change feed processor library coordinates multiple workers reading in parallel. It stores its progress in a small separate container called the leases container, so that if a worker crashes, another resumes from where it stopped. Delivery is at least once, which means a change may be delivered more than once after a failure. Your handlers must therefore be idempotent, meaning that running them twice has the same effect as running once.
The change feed is the bank statement of the container. Instead of asking the bank for your balance every minute, you read the statement lines in order, and a bookmark remembers the last line you processed. If you lose your place and re-read a line, nothing breaks as long as processing the same line twice is harmless.
Modes and limits
The classic mode shows the latest version of each changed item and does not directly report deletions. A common workaround is soft delete: mark an item as deleted and let time-to-live remove it later, so the change is visible. A newer mode, available with continuous backup, can expose all versions of an item and deletions, giving a fuller history. Pick the mode that matches what your consumers need to know.
Writing non-idempotent handlers, such as adding to a running total on every delivery. A duplicate delivery then double counts. Store enough identity in each handler action to recognize and skip repeats.
15Transactions, Server-Side Logic, TTL and Unique Keys
Correctness features exist, but they live inside the boundary of a single logical partition.
Transactions within a partition
Cosmos DB provides ACID transactions, meaning operations that are atomic, consistent, isolated and durable, for items that share the same partition key value. A transactional batch lets you submit several operations on items in one partition and have them all succeed or all fail together. Stored procedures, written in JavaScript and run inside the database next to the data, offer the same guarantee with custom logic. There is no cross-partition ACID transaction. If your business rule spans partitions, you design for it with patterns like the saga: a sequence of local steps, each with a compensating action to undo it if a later step fails.
A transaction within a partition is like moving items between two drawers of the same desk: you can do it all at once and nobody sees it half-done. A saga is like coordinating moves between desks in different buildings: you do one step, then the next, and if something goes wrong you walk back and undo the earlier steps.
Optimistic concurrency
Every item carries an entity tag, called an ETag, that changes with each update. When you update, you can ask the service to apply your change only if the tag still matches what you read. If someone else changed the item in the meantime, your update is refused and you can re-read and try again. This prevents the “lost update” problem without locking.
Time to live
Time to live, or TTL, automatically deletes items after a set number of seconds. You can set a default on the container and override it on individual items. It is ideal for sessions, temporary tokens, caches and log data. Expired items stop appearing in queries promptly, and the physical removal runs in the background using spare throughput, which means it never competes with your live traffic but may lag when the container is very busy.
Unique keys
A unique key constraint ensures that no two items in the same logical partition share the same value for a chosen property, such as an email address within a tenant. It is enforced per partition, not across the entire container. Two different partition key values may legally hold the same unique property value, so for global uniqueness you must make the partition key and the unique property line up or enforce uniqueness another way.
Because unique keys cannot be added to an existing container, decide on them when the container is created. Treat the unique key policy as part of the initial design, alongside the partition key.
Problem
Business rules that must atomically update items in many different partitions are implemented as if a cross-partition transaction existed.
Why It’s Harmful
Partial failures leave data half-updated with no automatic repair, producing hard-to-trace inconsistencies.
Correct Approach
Reshape the model so the rule fits in one partition, or implement an explicit saga with compensating steps and idempotent operations.
16Performance Extras: Integrated Cache and the Analytical Store
Two features that add speed and analytics without disturbing your live workload.
Integrated cache
A dedicated gateway is a set of managed nodes that sit between your application and the database. When enabled, it contains an in-memory cache for point reads and queries. Repeated reads of the same data are served from memory and cost no RUs at all, which is dramatic for read-heavy applications with hot data. The cache applies to reads at session or eventual consistency, and you control how stale a cached answer may be by setting a maximum age.
A reception desk that remembers answers to questions it hears often. The first visitor asking for the cafeteria timing makes the receptionist look it up; the next hundred are answered instantly from memory, and the records department is not bothered at all.
Analytical store and mirroring
Running a large analytical query against the transactional store competes with your application’s reads and writes and burns RUs. The analytical store solves this by keeping a separate, automatically synchronized, column-oriented copy of your data optimized for scans and aggregates. Because it is separate, analytics do not consume your provisioned throughput. Services such as Azure Synapse can query it directly through a feature known as Synapse Link. Mirroring into Microsoft Fabric follows a related idea, continuously replicating operational data into open analytical storage where reporting and machine learning tools can use it without writing your own pipelines.
| Need | Best feature | Why |
|---|---|---|
| Speed up repeated reads of hot items | Integrated cache | Memory answers at no RU cost for cached hits |
| Reports and large aggregates | Analytical store or mirroring | Columnar copy keeps scans away from the live workload |
| React to changes | Change feed | Ordered record of every change |
| Alternate query shape | Materialized view container | Second partition key via the change feed |
Never run reporting-style scans on the transactional store of a busy application. Move those questions to the analytical side, even if the first version seems to work fine at small data volumes.
17Cosmos DB for AI: Vector, Full-Text and Hybrid Search
Modern AI applications need to find data by meaning, not just by exact match.
An embedding is a long list of numbers produced by an AI model that captures the meaning of a piece of text, an image or other content. Items with similar meaning end up with numbers that are close together. Vector search finds the items whose embeddings are nearest to the embedding of a question. This powers semantic search, recommendations and retrieval-augmented generation, where an AI assistant first retrieves relevant facts from your data and then writes an answer grounded in them.
Imagine every document placed as a pin on a giant map where similar ideas sit near each other. A question is also placed on the map, and the system returns the pins closest to it. The numbers in an embedding are simply the coordinates.
Why store vectors beside operational data
Cosmos DB for NoSQL can hold embeddings inside the same items as the rest of your data and index them with a dedicated vector index. This avoids operating a separate vector database and keeping it synchronized. Filters on ordinary properties, such as tenant or category, can be combined with the similarity search, so a multi-tenant application returns only the caller’s own data. Several index approaches are offered, from exact comparison suitable for small sets to approximate approaches built for very large sets, including one derived from a disk-based graph method for high scale at lower memory cost.
Vector search
Finds items by closeness of meaning using embeddings and a vector index.
Full-text search
Keyword-style search with language-aware processing and relevance ranking over text properties.
Hybrid search
Combines the rankings from vector and full-text results, so exact terms such as product codes and fuzzy meaning both contribute.
Agent memory
Chat history and long-term memory for AI agents stored as items, with low latency and global reach.
Design cautions for vector workloads
Embeddings are large, so items with vectors are bigger and cost more RUs to write and read. Exclude the vector path from the normal range index, since it must use the vector index instead. Keep an eye on the dimensions of the embeddings, because they affect storage and query cost. When the embedding model changes, the stored vectors must be regenerated, so keep the source text and a model version marker alongside each vector. Publicly, Microsoft has described Cosmos DB as part of the data layer behind very large AI services, which shows the engine is meant to carry such loads.
Mixing embeddings from different models in one vector index. Distances between vectors from different models are meaningless. Use one model per index, and plan a clean re-embedding path for upgrades.
18Security: Identity, Network and Encryption
Treat the database as the vault of the business and secure it in layers.
Identity and access
Historically, applications connected with account keys, which are powerful shared secrets that grant broad access. The modern approach is to use Microsoft Entra ID with role-based access control, where each application or person receives a precisely scoped role, such as read-only on one database. Applications running in Azure use a managed identity, so no secret is stored in code or configuration at all. A strong production stance is to disable key-based access entirely once all clients have moved to identities, so that a leaked key becomes impossible.
Account keys are like one master key to the entire building that gets copied and passed around. Role-based access is a personal badge that opens only the rooms that person needs and can be revoked any time without changing the locks.
Network protection
Firewall rules
Allow only specific address ranges to reach the account over the public endpoint.
Virtual network integration
Limit access to chosen subnets inside your Azure network.
Private endpoints
Give the account a private address inside your network and turn off public access altogether, so traffic never crosses the open internet.
Encryption and sensitive fields
Data is encrypted at rest by default with keys managed by the service, and all connections use encrypted transport. Organizations with stricter rules can bring their own key, stored in a key vault, and control rotation and revocation. For especially sensitive fields such as national identifiers or health values, client-side encryption lets the application encrypt a property before it is ever sent, so the service itself never sees it in plain form. The trade-off is that the database cannot filter or index freely on encrypted values, so the design must account for that.
Audit logging and threat detection services can record who accessed what and flag unusual patterns, such as a sudden large export or access from an unfamiliar location. Send diagnostic logs to a central workspace and set alerts on suspicious activity.
Problem
The primary account key is placed in configuration files, shared among teams, and never rotated.
Why It’s Harmful
Anyone holding the key has full control over all data. A single leak in a repository or a chat message exposes everything, and nobody can tell who used it.
Correct Approach
Use managed identities with scoped roles, disable key access where possible, and keep any remaining secrets in a managed vault with rotation.
19Monitoring, Logging and Troubleshooting
You cannot tune what you cannot see.
Cosmos DB reports metrics and logs through Azure Monitor. A well-run team builds a small dashboard and a few alerts early, rather than after the first incident. The metrics below tell most of the story.
Normalized RU consumption
Percentage of provisioned throughput used by the busiest partition. The best single signal of hot partitions.
Throttled requests (429)
Count of requests rejected for lack of throughput. Watch the trend, not isolated spikes.
Server-side latency
Time the service spent. If it is low but users are slow, the delay is in the network or the application.
Data and index usage
Storage growth and its split between data and index, useful for spotting indexing waste.
Provisioned versus consumed RU
Shows over- or under-provisioning and guides decisions about manual versus autoscale.
Diagnostic logs
Detailed per-request records with charges and partition information, queryable in a central log workspace.
A troubleshooting routine
Confirm the symptom
Is it throttling, high latency, or errors? Check the 429 count and the latency metrics to see which one is real.
Compare average to maximum utilization
If average consumption is low but the busiest partition is saturated, suspect a hot partition and examine key distribution.
Find the expensive operations
Use diagnostic logs to rank operations by RU charge and look for fan-out queries and large scans.
Check the client side
Look at connection mode, region choice, retry settings, and whether the application is far from its nearest region.
Fix the design before adding money
Raising RU/s is the quickest patch but rarely the real cure. Fix the key, index or query, then reduce capacity if it is no longer needed.
Alert on sustained throttling and on the busiest partition’s utilization, not on account-wide averages. Averages hide the very problem you are trying to catch.
20Cost Optimization
A well-designed Cosmos DB account can be reasonable; a careless one can be shocking.
Your bill is made up mainly of provisioned or consumed throughput, stored data, extra regions, backup beyond the included amount, and optional components such as the dedicated gateway and the analytical store. Because several of these multiply with each region, geographic reach is a real cost decision, not just a technical one.
Tune the indexing policy
Index only paths used by queries. This lowers write cost and storage.
Prefer point reads over queries
A point read is the cheapest possible access. Shape your identifiers so you can use it.
Keep items small
Cost scales with item size. Store large blobs such as images in object storage and keep only a reference in the item.
Match the throughput mode to the traffic
Autoscale for spiky load, manual for steady load, serverless for light or experimental use.
Use reserved capacity for steady baselines
Committing to a baseline for a year or more can bring a meaningful discount on predictable usage.
Expire what you do not need
Use time to live on temporary data so storage and index do not grow forever.
Cache hot reads
The integrated cache, or an application-level cache, removes repeated RU charges for popular items.
Free and trial options
A free tier grants a monthly allowance of throughput and storage on one account per subscription, which suits learning and small projects. Remember that deleting a container or the account is the only way to truly stop all charges in a learning environment, so clean up when you finish experimenting.
Leaving test containers at generous manual throughput over a long weekend, then adding regions “just to try it”. Charges accrue by the hour for every region and every container. Automate cleanup and set budget alerts from day one.
21Design Patterns and Anti-Patterns
Patterns are proven recipes; anti-patterns are proven ways to hurt yourself.
Patterns worth knowing
Aggregate per partition
Keep everything belonging to one business entity, such as a customer and their orders, in one partition so common questions are single-partition reads.
Materialized view through the change feed
When a second access pattern is needed, feed a second container with a different partition key rather than running fan-out queries.
Write sharding for hot keys
Add a small number of suffix values to a very busy key so that writes spread across several partitions, accepting that reads must gather them.
Tenant isolation with hierarchical keys
In multi-tenant software, use tenant as the first level so that large tenants can grow beyond one logical partition while queries stay targeted.
Idempotent event handling
Make every consumer of the change feed safe to run more than once, using identifiers to detect repeats.
Soft delete with expiry
Mark items as deleted and let time to live purge them later, so downstream consumers see the deletion and storage is eventually reclaimed.
Anti-patterns to avoid
Problem
A low-cardinality or time-based partition key, such as a status flag or today’s date, is chosen because it “looks natural”.
Why It’s Harmful
Traffic concentrates on a few partitions, causing throttling while most capacity sits unused, and logical partitions may reach their size limit.
Correct Approach
Choose a high-cardinality key aligned with queries, or build a synthetic or hierarchical key that spreads load evenly.
Problem
The database is treated as a relational one: many small containers, one per entity, with the application performing joins by issuing many separate requests.
Why It’s Harmful
Each request has latency and cost. The number of round trips multiplies, and each container must be provisioned separately.
Correct Approach
Model around access patterns, embed what is read together, and co-locate related item types in one container under a shared partition key.
Problem
Analytical reports are run directly against the live container with large scans and aggregates.
Why It’s Harmful
The scans consume RUs that user requests need, causing throttling at business hours and unpredictable cost.
Correct Approach
Move reporting to the analytical store or mirrored data, and keep the transactional container for transactional work.
22Best Practices Checklist and Common Mistakes
A short list to review before every production launch.
Do
- Choose the partition key from your real access patterns and test it with realistic data
- Use point reads wherever the id and key are known
- Tune the indexing policy to the queries you actually run
- Use the latest client libraries in direct mode, with a preferred region list
- Handle 429 responses with backoff and honor the retry hint
- Use managed identities and role-based access, and consider private endpoints
- Create alerts on throttling and on the busiest partition
- Rehearse failover and restore procedures
Avoid
- Creating one container per entity by habit
- Relying on cross-partition queries for primary screens
- Choosing strong consistency without a proven need
- Letting items grow without bound
- Sharing the account key widely
- Running reports on the live container
- Ignoring the RU charge returned with each response
- Assuming replication replaces backup
Create one long-lived client object per application and reuse it for the lifetime of the process. Creating a new client for every request wastes connections, slows startup of each call and can exhaust network resources under load.
A closing habit that separates seasoned teams from beginners: run a load test with realistic data volume and realistic key distribution before launch, and read the RU charges and partition metrics during that test. Problems found in a test cost an afternoon; problems found in production cost a weekend and a reputation.
23Real-World and Industry Examples
The same building blocks appear again and again across industries.
Retail and e-commerce
Product catalogs, shopping carts and order history need low latency under huge seasonal spikes. Autoscale handles sale-day surges, customer id keys the carts and orders, and the change feed updates search indexes and recommendation systems. Publicly cited retailers such as ASOS and Walmart have described using Cosmos DB for workloads of this kind.
Gaming
Player profiles, inventories, session state and leaderboards must respond in milliseconds worldwide. Player id as the key keeps each player’s data together, and multi-region replication places data near players. Microsoft has described using Cosmos DB inside its own gaming and communication services.
Internet of Things
Millions of devices send small readings continuously. Device id as the key spreads writes, time to live discards old raw readings, and the analytical store supports fleet-wide trends without touching the live path.
Financial services and payments
Transaction metadata, fraud signals and customer profiles benefit from multi-region resilience and tight security controls such as private endpoints, customer-managed keys and field-level encryption. Strong correctness is handled within partition boundaries and sagas.
Healthcare and life sciences
Patient-facing applications and device data need strict access control, auditing and regional data residency. Region selection and encryption options support these requirements.
AI assistants and copilots
Conversation history, user memory, knowledge chunks and their embeddings live together, with vector and hybrid search used to retrieve context for answers. Tenant-based hierarchical keys keep each customer’s data isolated and queries targeted.
Multi-tenant software as a service
Tenant-aware keys, per-tenant throughput visibility and careful handling of very large tenants allow one platform to serve both tiny and enormous customers fairly.
24Frequently Asked Questions
Quick answers to the questions that come up most often.
No. The NoSQL API stores flexible documents and scales out by splitting data into partitions. It does not offer arbitrary joins across containers or cross-partition transactions. It trades some relational conveniences for scale, low latency and global reach.
Not in place. Changing it means creating a new container with the desired key and migrating the data, which is a planned project. That is why the choice deserves careful analysis at the start.
Throughput is divided among physical partitions. If one partition receives far more traffic than the others, it can hit its share while the rest are idle. Check the busiest-partition utilization and the key distribution.
Session is the sensible default for most applications because users see their own changes and cost stays low. Choose stronger levels only for a proven business need, and weaker levels for non-critical, high-volume reads.
An RU is a blended measure of processor, memory and input and output work required for an operation. It lets you reason about cost and capacity with one number instead of several hardware figures.
Under weaker consistency levels, replication to other regions happens in the background and does not hold up the write. Under strong consistency, the write must be confirmed across regions, so distance adds to latency.
With multi-region writes, a conflict resolution policy decides. By default the later timestamp wins. You can choose a custom deciding property or capture conflicts for your own code to resolve.
Not to get started, because everything is indexed by default. For production, tune the policy so that only needed paths are indexed, and add composite indexes for multi-property sorting or filtering.
Yes, by using the analytical store or mirroring, which keep a separate columnar copy synchronized automatically, so scans do not consume your transactional throughput.
It can be for light or sporadic production workloads, but it has lower scale limits and has historically been restricted to a single region. Verify the current limits against your needs before relying on it.
Use continuous backup to enable point-in-time restore, restrict delete permissions through role-based access, and consider soft-delete patterns in your application. Replication alone will not protect you.
Yes. You can store embeddings in items, index them with a vector index, add full-text indexing, and combine them for hybrid search. This lets you retrieve relevant context for AI assistants from the same database that holds your operational data.
Treating it like a relational database: many containers, normalized data, and queries without the partition key. Start from access patterns and the partition key, and most other decisions fall into place.
25Summary and Key Takeaways
Azure Cosmos DB gives you a planet-scale, fully managed database with predictable low latency, elastic capacity, and a menu of consistency choices. Its power comes from a small set of ideas that work together: data is split by partition key into partitions that spread across machines, work is priced in Request Units, every property is indexed unless you say otherwise, copies live in many regions, and a change feed turns every write into an event. If you remember only one thing, remember that the design choices made at the start, especially the partition key and the data model, determine the speed, the reliability and the cost for the entire life of the system. Understand those, measure often, and the service rewards you with a platform that grows as far as your ambitions do.
Key Takeaways
- The partition key is the most important decision — it controls data spread, throughput usage and the cost of every query, and it is very hard to change later.
- Request Units unify cost — a point read is the cheapest operation, and designing for point reads and single-partition queries keeps bills and latency low.
- Model for your questions — embed what is read together and bounded in size, reference what grows without limit, and co-locate related item types under one key.
- Tune the index and the throughput mode — index only what you query, and choose manual, autoscale or serverless to fit the traffic pattern.
- Match consistency to the need — session is the right default for most applications, with stronger levels reserved for proven requirements.
- Plan for resilience and recovery — use multiple regions and zones for availability, continuous backup for mistakes, and rehearse both failover and restore.
- Secure and observe from day one — prefer identities over keys, use private networking, and alert on throttling and the busiest partition rather than on averages.