Cosmos DB Interview Questions and Answers Part 9 - Security, Backup and Analytics

Cosmos DB Interview Q&A · Part 9 of 10

In this post we are going to cover security, backup and analytics in Azure Cosmos DB. These are 7 real Cosmos DB interview questions for data engineers, asked the way they come up on a real project, and the last one is a scenario: a bank needs a secure Cosmos DB platform with point-in-time restore and reporting.

Security means how apps prove who they are, what they may do, and where the account can be reached from. Backup is how we get data back after a mistake, and analytics is how reports run without slowing the app. Interviewers ask because the details decide whether a lockdown or a restore actually works.

Part 9 of the Real Time Azure Cosmos DB Interview Questions and Answers series on TechBrothersIT.

The short answer

Say this first

Apps use Microsoft Entra ID with managed identities and Cosmos DB data plane roles, then keys are switched off (disableLocalAuth). A private endpoint with public access disabled keeps the account off the internet. Continuous backup gives self-service point-in-time restore, time to live expires old data, and Fabric mirroring serves reports without using the app's request units.

Let's set up the project

We run the official Azure Cosmos DB Linux emulator locally, and small Python scripts that use the azure-cosmos client, so every output is real. Most of this part is configuration: the emulator accepts any key and has no networking, backups or mirroring, so those features are shown as code for Azure. common.py has one helper, database, which makes a client with a key, the emulator key by default, and creates the shop database.

common.py
import os
from azure.cosmos import CosmosClient

URL = os.environ["COSMOS_ENDPOINT"]   # http://localhost:8081
KEY = os.environ["COSMOS_KEY"]        # the emulator's well-known key


def database(key=KEY):
    client = CosmosClient(URL, credential=key)
    return client.create_database_if_not_exists("shop")

Question 1: Leadership asks how applications should authenticate to Cosmos DB. Keys or Microsoft Entra ID?

Microsoft Entra ID, with Cosmos DB data plane roles. Keys still work, but a key is a shared secret for the whole account. It sits in config files, gets copied around, and has to be rotated.

Account keysMicrosoft Entra ID
The app holdsa shared secret for the accountno secret: a token for its own identity
Accessread-write or read-only, whole accounta data plane role: account, database or container
Rotationregenerate the key, redeploy the appsnothing to rotate

Each account has two key pairs, read-write and read-only. With Entra ID, the app gets a token for its own identity, and a role decides what that identity may do: on the account, on one database, or on one container. In code, the only change is the credential:

auth.py
# keys: two pairs per account, both shared secrets
#   primary, secondary                  read-write
#   primaryReadonly, secondaryReadonly  read-only
client = CosmosClient(URL, credential=READ_ONLY_KEY)

# Microsoft Entra ID: no secret, a token for the app identity
from azure.identity import DefaultAzureCredential
client = CosmosClient(URL, credential=DefaultAzureCredential())

When every app is on Entra ID, we switch keys off with disableLocalAuth set to true. Keep the order:

  • Assign the roles
  • Move the apps to Entra ID
  • Test
  • Then disable keys

Otherwise you lock out your own services.

keys-off.sh
# only after every app uses Entra ID:
az cosmosdb update -g rg-shop -n cosmos-shop \
  --disable-local-auth true
# Bicep: properties.disableLocalAuth: true

Now one honest note on local testing: the emulator doesn't check keys. anykey.py writes and reads customer Ana twice, once with the emulator key and once with a key I made up. We run python anykey.py.

anykey.py
import common
from azure.cosmos import PartitionKey

MADE_UP = "bWFkZS11cC1rZXktbm90LWZyb20tdGhlLXBvcnRhbA=="  # not a key
KEYS = [("emulator key", common.KEY), ("made-up key", MADE_UP)]
for label, key in KEYS:
    db = common.database(key)
    customers = db.create_container_if_not_exists(
        "customers", partition_key=PartitionKey(path="/customerId"))
    customers.upsert_item({"id": "c1", "customerId": "c1",
                           "name": "Ana"})
    item = customers.read_item("c1", partition_key="c1")
    print(f"{label:12} | write + read OK: {item['name']}")
Terminal
$ python anykey.py
emulator key | write + read OK: Ana
made-up key  | write + read OK: Ana

Both lines say write + read OK: Ana, the made-up key too. So a passing local test proves nothing about permissions.

Test on Azure

Test permissions on a real Azure account, not on the emulator.

Question 2: A pipeline needs access with no secrets in code. How do managed identities and Cosmos DB data plane roles work?

The pipeline runs with a managed identity, so Azure gives it tokens and there's no secret to store. Then we grant that identity a Cosmos DB data plane role.

  • Pipeline - managed identity: id-orders-pipeline
  • Microsoft Entra ID - issues a token
  • Cosmos DB account - data plane role on /dbs/shop

Those roles are native to Cosmos DB, separate from Azure role assignments, and not managed in the portal. There are two built-in roles:

Built-in data plane roleRole definition IDCan do
Cosmos DB Built-in Data Reader00000000-0000-0000-0000-000000000001read items, run queries, read the change feed
Cosmos DB Built-in Data Contributor00000000-0000-0000-0000-000000000002also create, replace and delete items

Built-in Data Reader, ending in 1, reads items, runs queries and reads the change feed. Built-in Data Contributor, ending in 2, can also create, replace and delete items.

We assign them with the Azure CLI, az cosmosdb sql role assignment create: the identity's principal ID, the role definition ID, and a scope. Here the scope is /dbs/shop, only the shop database. This is Azure-only, so it's code:

assign-role.sh
# 1. a user-assigned managed identity for the pipeline
az identity create -g rg-shop -n id-orders-pipeline
PRINCIPAL=$(az identity show -g rg-shop -n id-orders-pipeline \
  --query principalId -o tsv)
# 2. Built-in Data Contributor (...0002) on database shop only
az cosmosdb sql role assignment create -g rg-shop \
  --account-name cosmos-shop --scope "/dbs/shop" \
  --principal-id $PRINCIPAL \
  --role-definition-id 00000000-0000-0000-0000-000000000002

In Python, the client takes DefaultAzureCredential instead of a key. On Azure that's the managed identity, and on a laptop it's your own Azure CLI login:

pipeline.py
import os
from azure.cosmos import CosmosClient
from azure.identity import DefaultAzureCredential

# Azure: the managed identity (AZURE_CLIENT_ID picks
# the user-assigned one); a laptop: your az login
client = CosmosClient(os.environ["COSMOS_ENDPOINT"],
                      credential=DefaultAzureCredential())
orders = client.get_database_client("shop") \
               .get_container_client("orders")
orders.upsert_item({"id": "o1", "customerId": "c1"})
The trap

Data plane roles don't create containers or change throughput. That's the control plane. So a pipeline that creates its containers at startup fails. Create them at deploy time, in Bicep.

Question 3: Security wants no public access to the account. What networking options do you set up?

A private endpoint in the app's virtual network, a private DNS zone, and public network access disabled. Then the account is reachable only through a private IP inside your network.

  • App in vnet-shop - subnet snet-data
  • Private endpoint - a private IP, group ID Sql
  • Cosmos DB account - public network access: Disabled
  • Public internet - no route in

The private endpoint uses the group ID Sql, for the API for NoSQL. The DNS zone is privatelink.documents.azure.com, linked to the VNet, with one private IP for the global endpoint, plus one per region. A zone group keeps the records current when regions change. None of this runs on the emulator, so here are the CLI steps:

private-endpoint.sh
COSMOS_ID=$(az cosmosdb show -g rg-shop -n cosmos-shop \
  --query id -o tsv)
# 1. a private endpoint for the API for NoSQL: group ID Sql
az network private-endpoint create -g rg-shop -n pe-cosmos-shop \
  --vnet-name vnet-shop --subnet snet-data \
  --private-connection-resource-id $COSMOS_ID \
  --group-id Sql --connection-name cosmos-shop-sql
# 2. private DNS, linked to the VNet, kept current by a zone group
az network private-dns zone create -g rg-shop \
  -n privatelink.documents.azure.com
az network private-dns link vnet create -g rg-shop -n link-shop \
  -z privatelink.documents.azure.com -v vnet-shop -e false
az network private-endpoint dns-zone-group create -g rg-shop \
  --endpoint-name pe-cosmos-shop -n default --zone-name cosmos \
  --private-dns-zone privatelink.documents.azure.com
# 3. no public access at all
az cosmosdb update -g rg-shop -n cosmos-shop \
  --public-network-access DISABLED

Let's put the options side by side:

OptionWhat it does
IP firewalllisted IPs or CIDR ranges; others get 403
Service endpointVNet rules for chosen subnets
Private endpointa private IP in your VNet
Public access offoverrides IP and VNet rules

Public network access disabled takes precedence over any IP or virtual network rule. If you can't go fully private yet, the IP firewall allows listed addresses, blocked requests get a 403, and changes can take up to 15 minutes.

Test networking in a real subscription, from inside the virtual network.

The mistake I see

Ticking accept connections from within public Azure datacenters. That allows all requests from Azure, including other customers' subscriptions. It is not a private network.

Question 4: Someone deletes a container by mistake. How do periodic and continuous backups help you restore?

With periodic backup, the default, you open a support request and the data comes back into a new account. With continuous backup, you restore it yourself, to a point in time.

Periodic (default)Continuous
Backupsa full backup every 4 hours, latest 2 keptrestore to any point in the retention
Retentioninterval and retention configurable7-day, 30-day or 35-day tier
Restoresupport request, into a new accountself-service: same account or a new account
Costtwo backups free, extra ones charged7-day tier: no backup storage charge; restores charged
Switchcan move to continuousone-way: no way back to periodic

Periodic takes a full backup every four hours and keeps the latest two. Continuous mode comes in a 7, 30 or 35 day tier. The 7-day tier has no backup storage charge, but restores are always charged. You can move from periodic to continuous, never back.

Now the mistake. mistake.py creates the returns container with three items, deletes it, and then does the app's next read. We run python mistake.py.

mistake.py
import common
from azure.cosmos import PartitionKey
from azure.cosmos.exceptions import CosmosResourceNotFoundError

db = common.database()
returns = db.create_container_if_not_exists(
    "returns", partition_key=PartitionKey(path="/customerId"))
for i in (1, 2, 3):
    returns.upsert_item({"id": f"r{i}", "customerId": f"c{i}"})
print("returns:", len(list(returns.read_all_items())), "items")
db.delete_container("returns")                  # the mistake
try:
    returns.read_item("r1", partition_key="c1")  # the app's next read
except CosmosResourceNotFoundError as e:
    print(type(e).__name__, e.status_code)
    print(" ", str(e).split("\n")[0])
Terminal
$ python mistake.py
returns: 3 items
CosmosResourceNotFoundError 404
  (NotFound) Database/Collection shop.returns does not exist

The container had 3 items, and then the read fails with CosmosResourceNotFoundError 404: Database/Collection shop.returns does not exist. The emulator has no backups, so the restore is code.

With continuous backup, we restore the deleted container into the same account at a time just before the delete, or restore to a new account. We pass disable TTL, or restored items past their time to live expire again:

restore.sh
# continuous: the deleted container, back in the same account
az cosmosdb sql container restore -g rg-shop -a cosmos-shop \
  -d shop -n returns --restore-timestamp 2026-10-30T08:59:00+0000 \
  --disable-ttl True
# or into a new account: database shop, container returns only
az cosmosdb restore -g rg-shop --account-name cosmos-shop \
  --target-database-account-name cosmos-shop-restore \
  --restore-timestamp 2026-10-30T08:59:00+0000 --location eastus \
  --databases-to-restore name=shop collections=returns
# periodic to continuous: one-way
az cosmosdb update -g rg-shop -n cosmos-shop \
  --backup-policy-type Continuous --continuous-tier Continuous7Days
Keep it in Bicep

A restore to a new account doesn't bring back firewall rules, private endpoints or role assignments. On periodic, raise the retention and call support right away.

Question 5: A teammate asks how to expire old data automatically. How does time to live work?

Turn time to live on for the container. Items then expire a number of seconds after their last write, and are purged in the background. An item's own ttl field overrides the default, and -1 means never.

Container defaultTtlWhat happens
not setnothing expires: an item's ttl is ignored
-1TTL on, no default: items opt in with their own ttl
n secondsitems expire n seconds after their last write

Let's try it. ttl.py creates the carts container with a default of 5 seconds. Cart 1 uses the default, cart 2 sets 2 seconds, and cart 3 sets -1. A second container, carts_no_ttl, has time to live off, and cart 4 sets 2 seconds there. Then it waits 7 seconds and does a point read of each cart. We run python ttl.py.

ttl.py
import time, common
from azure.cosmos import PartitionKey
from azure.cosmos.exceptions import CosmosResourceNotFoundError

db = common.database()
carts = db.create_container_if_not_exists("carts", default_ttl=5,
    partition_key=PartitionKey(path="/customerId"))   # TTL on: 5 s
plain = db.create_container_if_not_exists("carts_no_ttl",
    partition_key=PartitionKey(path="/customerId"))   # TTL off
carts.upsert_item({"id": "cart-1", "customerId": "c1"})   # default
carts.upsert_item({"id": "cart-2", "customerId": "c2", "ttl": 2})
carts.upsert_item({"id": "cart-3", "customerId": "c3", "ttl": -1})
plain.upsert_item({"id": "cart-4", "customerId": "c4", "ttl": 2})
print("defaultTtl | carts:", carts.read()["defaultTtl"],
      "| carts_no_ttl:", plain.read().get("defaultTtl"))
time.sleep(7)
CHECK = [(carts, "cart-1", "c1"), (carts, "cart-2", "c2"),
         (carts, "cart-3", "c3"), (plain, "cart-4", "c4")]
for co, cart, cust in CHECK:
    try:
        co.read_item(cart, partition_key=cust)       # a point read
        print(f"{co.id:12} {cart}: still there")
    except CosmosResourceNotFoundError as e:
        print(f"{co.id:12} {cart}: expired ({e.status_code})")
Terminal
$ python ttl.py
defaultTtl | carts: 5 | carts_no_ttl: None
carts        cart-1: expired (404)
carts        cart-2: expired (404)
carts        cart-3: still there
carts_no_ttl cart-4: still there

The defaults read back as 5 and None. cart-1 and cart-2 are expired (404). cart-3 is still there, and so is cart-4: an item's ttl is ignored when the container has time to live off. That's the mistake I see most.

Next, badttl.py tries to create a cart with a ttl of 0, then -5. We run python badttl.py.

badttl.py
import common
from azure.cosmos.exceptions import CosmosHttpResponseError

carts = common.database().get_container_client("carts")
for ttl in [0, -5]:
    try:
        carts.create_item({"id": "cart-9", "customerId": "c9",
                           "ttl": ttl})
    except CosmosHttpResponseError as e:
        print("ttl", ttl, "->", e.status_code, str(e).split("\n")[0])
Terminal
$ python badttl.py
ttl 0 -> 400 (BadRequest) The specified document 'ttl' value is invalid. Valid values are -1 or a
  non-zero positive 32-bit integer.
ttl -5 -> 400 (BadRequest) The specified document 'ttl' value is invalid. Valid values are -1 or a
  non-zero positive 32-bit integer.

Both get a 400: Valid values are -1 or a non-zero positive 32-bit integer.

Point reads

We prove expiry with read_item, not a query. On Azure, expired items leave query results right away, but on the emulator, queries can still return them for a few minutes.

Question 6: Analysts want to run heavy reports without hurting the app. What are the analytics options?

Today the answer is Fabric mirroring. It's generally available, and it replicates inserts, updates and deletes into OneLake as Delta tables, in near real time. Replication doesn't use the request units you provisioned, so reports can't throttle the app.

Analysts use the SQL analytics endpoint, Spark or Power BI. Mirroring needs continuous backup, and the 7-day tier is recommended because it's free. It doesn't support accounts with multiple write regions.

Synapse Link and the analytical store are no longer supported for new projects, so don't start there. The other path is your own pipeline on the change feed:

Fabric mirroringYour own change feed pipeline
Statusgenerally availableyour own code
Request unitsreplication doesn't use your provisioned RUseach change feed read costs RUs
Lands inOneLake as Delta tables, near real timewherever you write it
Needscontinuous backup (7-day tier recommended)checkpoints; deletes need all versions and deletes mode
Limitsno multi-region-write accounts, no CMK on OneLakeyou own retries and schema changes

The emulator has no mirroring, so a local JSONL file stands in for the analytics copy. export.py writes six orders and exports them to orders.jsonl without system properties. report.py reads only the file. We run python export.py, head -n 2 orders.jsonl and python report.py.

export.py
import json, common
from azure.cosmos import PartitionKey

ORDERS = [("o01", "c1", "paid", 120.5), ("o02", "c2", "paid", 35.25),
          ("o03", "c1", "shipped", 18.75), ("o04", "c3", "paid", 6.5),
          ("o05", "c2", "cancelled", 22.5),
          ("o06", "c3", "shipped", 99.9)]
orders = common.database().create_container_if_not_exists(
    "orders", partition_key=PartitionKey(path="/customerId"))
for oid, cust, status, total in ORDERS:
    orders.upsert_item({"id": oid, "customerId": cust,
                        "status": status, "total": total})
rows = orders.query_items("SELECT * FROM c ORDER BY c.id",
                          enable_cross_partition_query=True)
with open("orders.jsonl", "w") as f:     # stands in for the lake copy
    for r in rows:
        clean = {k: v for k, v in r.items() if not k.startswith("_")}
        f.write(json.dumps(clean) + "\n")
print("exported:", sum(1 for _ in open("orders.jsonl")), "orders")
report.py
import json
from collections import defaultdict

totals, counts = defaultdict(float), defaultdict(int)
with open("orders.jsonl") as f:         # the copy, not the container
    for line in f:
        order = json.loads(line)
        totals[order["status"]] += order["total"]
        counts[order["status"]] += 1
for status in sorted(totals):
    print(f"{status:10} orders {counts[status]}",
          f" total {totals[status]:.2f}")
Terminal
$ python export.py
exported: 6 orders
$ head -n 2 orders.jsonl
{"id": "o01", "customerId": "c1", "status": "paid", "total": 120.5}
{"id": "o02", "customerId": "c2", "status": "paid", "total": 35.25}
$ python report.py
cancelled  orders 1  total 22.50
paid       orders 3  total 162.25
shipped    orders 2  total 118.65

We get exported: 6 orders, and the first two lines, o01 and o02, are plain business fields. The report shows paid orders 3 total 162.25, plus cancelled orders 1 total 22.50 and shipped orders 2 total 118.65.

It costs RUs

This export queries the container, and that costs request units. Mirroring is what avoids that in a real project.

Scenario 7: A bank needs a secure Cosmos DB platform with point-in-time restore and reporting. What do you set up?

I build it in four layers, all in Bicep, so dev, test and production get the same identity, network and backup settings.

Identity first: keys disabled, managed identities, and data plane roles scoped to the database each app needs.

Network: a private endpoint and public network access disabled. Minimum TLS 1.2 is mandatory anyway, since August 2025.

Backup: continuous, on the 30-day tier, so operations can restore to any point in that window without a ticket. If the bank wants customer-managed keys, continuous backup needs a user-assigned managed identity to reach Key Vault, and the mirrored copy in OneLake can't use those keys.

Reporting: Fabric mirroring, built on that same continuous backup, so reports never touch the app's request units. A private account sets up mirroring through a virtual network data gateway. And keep one write region: mirroring doesn't support multi-region writes.

Here's the account in Bicep, as code for Azure:

cosmos-bank.bicep
resource acct 'Microsoft.DocumentDB/databaseAccounts@2025-04-15' = {
  name: 'cosmos-bank'
  location: resourceGroup().location
  kind: 'GlobalDocumentDB'
  properties: {
    databaseAccountOfferType: 'Standard'
    locations: [
      {
        locationName: resourceGroup().location
        failoverPriority: 0
        isZoneRedundant: true
      }
    ]
    disableLocalAuth: true            // keys off: Entra ID only
    publicNetworkAccess: 'Disabled'   // private endpoint only
    minimalTlsVersion: 'Tls12'
    backupPolicy: {
      type: 'Continuous'              // point-in-time restore
      continuousModeProperties: {
        tier: 'Continuous30Days'
      }
    }
  }
}

So the whole checklist:

  • Identity - keys off, managed identities, roles per database
  • Network - private endpoint, public access off, TLS 1.2
  • Backup - continuous 30 days: restore without a ticket
  • Reporting - Fabric mirroring, set up via a VNet data gateway
  • Drills - test restores; reapply network rules and roles
Drills

Test restores on a schedule. A restore to a new account doesn't bring back network rules or role assignments, and the Bicep template puts them back.

Key points to remember

  • Microsoft Entra ID + managed identities; keys off with disableLocalAuth
  • Data plane roles: Built-in Data Reader and Built-in Data Contributor, assigned with the CLI
  • Private endpoint + public network access Disabled
  • Continuous backup (7, 30, 35 days) for self-service restore; periodic needs a support request
  • Time to live on the container; Fabric mirroring for reports, not Synapse Link

The full script

This is a Python script that writes every file and runs every command above in order against the Cosmos DB emulator: run pip install azure-cosmos first, set COSMOS_ENDPOINT and COSMOS_KEY, and run it in an empty folder on a freshly started emulator.

Show the full script that writes the files and runs every step
Full script - writes the files and runs every step against the Cosmos DB emulator (pip install azure-cosmos), run it with Python
# ========================================================================
# Cosmos DB Interview Q&A #9 - Security, backup and analytics
# TechBrothersIT. Names and sample data are invented for teaching.
# Every step below was run on the official Azure Cosmos DB Linux emulator (vNext, Docker image)
# with azure-cosmos 4.17.1; the outputs in the video and the blog post are the real outputs.
# To run it yourself:
#   1. docker run -p 8081:8081 -p 1234:1234 mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-latest
#      and wait until its log says "Now listening on: http://0.0.0.0:8081".
#   2. pip install azure-cosmos   (scripts that use azure.cosmos.aio also need: pip install aiohttp)
#   3. Set COSMOS_ENDPOINT=http://localhost:8081 and COSMOS_KEY to the emulator key
#      (C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==),
#      then run python CosmosDB_Interview_QA_09_Code.py. The scripts create their own databases and containers.
# Run it in an empty folder and on a freshly started emulator: it creates its files (and any local data
# files) there, and a second run on old data gives different output.
# Steps that restart the emulator say so in a comment: stop and start it by hand at that point.
# ========================================================================

import os, subprocess, time

def write(path, text):
    if text is None:
        if os.path.exists(path): os.remove(path)
        return
    os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(text.strip("\n") + "\n")

def run(cmd, expect_error=False, allow_error=False, env=None):
    print("\n$ " + cmd, flush=True)
    rc = subprocess.run(cmd, shell=True, env=dict(os.environ, **(env or {}))).returncode
    if rc != 0 and not (expect_error or allow_error):
        raise SystemExit("stopped: this command was expected to succeed")

# Step 0: the starting files
write('common.py', """\
import os
from azure.cosmos import CosmosClient

URL = os.environ["COSMOS_ENDPOINT"]   # http://localhost:8081
KEY = os.environ["COSMOS_KEY"]        # the emulator's well-known key


def database(key=KEY):
    client = CosmosClient(URL, credential=key)
    return client.create_database_if_not_exists("shop")
""")

# Step 1: The emulator accepts any key: a made-up key writes and reads too
write('anykey.py', """\
import common
from azure.cosmos import PartitionKey

MADE_UP = "bWFkZS11cC1rZXktbm90LWZyb20tdGhlLXBvcnRhbA=="  # not a key
KEYS = [("emulator key", common.KEY), ("made-up key", MADE_UP)]
for label, key in KEYS:
    db = common.database(key)
    customers = db.create_container_if_not_exists(
        "customers", partition_key=PartitionKey(path="/customerId"))
    customers.upsert_item({"id": "c1", "customerId": "c1",
                           "name": "Ana"})
    item = customers.read_item("c1", partition_key="c1")
    print(f"{label:12} | write + read OK: {item['name']}")
""")
run('python anykey.py')

# Step 2: A container is deleted by mistake: what the app sees next
write('mistake.py', 'import common\nfrom azure.cosmos import PartitionKey\nfrom azure.cosmos.exceptions import CosmosResourceNotFoundError\n\ndb = common.database()\nreturns = db.create_container_if_not_exists(\n    "returns", partition_key=PartitionKey(path="/customerId"))\nfor i in (1, 2, 3):\n    returns.upsert_item({"id": f"r{i}", "customerId": f"c{i}"})\nprint("returns:", len(list(returns.read_all_items())), "items")\ndb.delete_container("returns")                  # the mistake\ntry:\n    returns.read_item("r1", partition_key="c1")  # the app\'s next read\nexcept CosmosResourceNotFoundError as e:\n    print(type(e).__name__, e.status_code)\n    print(" ", str(e).split("\\n")[0])\n')
run('python mistake.py')

# Step 3: Time to live: container default, per-item ttl, ttl -1, and a container with TTL off (point reads)
write('ttl.py', """\
import time, common
from azure.cosmos import PartitionKey
from azure.cosmos.exceptions import CosmosResourceNotFoundError

db = common.database()
carts = db.create_container_if_not_exists("carts", default_ttl=5,
    partition_key=PartitionKey(path="/customerId"))   # TTL on: 5 s
plain = db.create_container_if_not_exists("carts_no_ttl",
    partition_key=PartitionKey(path="/customerId"))   # TTL off
carts.upsert_item({"id": "cart-1", "customerId": "c1"})   # default
carts.upsert_item({"id": "cart-2", "customerId": "c2", "ttl": 2})
carts.upsert_item({"id": "cart-3", "customerId": "c3", "ttl": -1})
plain.upsert_item({"id": "cart-4", "customerId": "c4", "ttl": 2})
print("defaultTtl | carts:", carts.read()["defaultTtl"],
      "| carts_no_ttl:", plain.read().get("defaultTtl"))
time.sleep(7)
CHECK = [(carts, "cart-1", "c1"), (carts, "cart-2", "c2"),
         (carts, "cart-3", "c3"), (plain, "cart-4", "c4")]
for co, cart, cust in CHECK:
    try:
        co.read_item(cart, partition_key=cust)       # a point read
        print(f"{co.id:12} {cart}: still there")
    except CosmosResourceNotFoundError as e:
        print(f"{co.id:12} {cart}: expired ({e.status_code})")
""")
run('python ttl.py')

# Step 4: Invalid ttl values are rejected on create
write('badttl.py', 'import common\nfrom azure.cosmos.exceptions import CosmosHttpResponseError\n\ncarts = common.database().get_container_client("carts")\nfor ttl in [0, -5]:\n    try:\n        carts.create_item({"id": "cart-9", "customerId": "c9",\n                           "ttl": ttl})\n    except CosmosHttpResponseError as e:\n        print("ttl", ttl, "->", e.status_code, str(e).split("\\n")[0])\n')
run('python badttl.py')

# Step 5: Export a container to a local JSONL file (a stand-in for an analytics copy) and report from the copy
write('export.py', 'import json, common\nfrom azure.cosmos import PartitionKey\n\nORDERS = [("o01", "c1", "paid", 120.5), ("o02", "c2", "paid", 35.25),\n          ("o03", "c1", "shipped", 18.75), ("o04", "c3", "paid", 6.5),\n          ("o05", "c2", "cancelled", 22.5),\n          ("o06", "c3", "shipped", 99.9)]\norders = common.database().create_container_if_not_exists(\n    "orders", partition_key=PartitionKey(path="/customerId"))\nfor oid, cust, status, total in ORDERS:\n    orders.upsert_item({"id": oid, "customerId": cust,\n                        "status": status, "total": total})\nrows = orders.query_items("SELECT * FROM c ORDER BY c.id",\n                          enable_cross_partition_query=True)\nwith open("orders.jsonl", "w") as f:     # stands in for the lake copy\n    for r in rows:\n        clean = {k: v for k, v in r.items() if not k.startswith("_")}\n        f.write(json.dumps(clean) + "\\n")\nprint("exported:", sum(1 for _ in open("orders.jsonl")), "orders")\n')
write('report.py', """\
import json
from collections import defaultdict

totals, counts = defaultdict(float), defaultdict(int)
with open("orders.jsonl") as f:         # the copy, not the container
    for line in f:
        order = json.loads(line)
        totals[order["status"]] += order["total"]
        counts[order["status"]] += 1
for status in sorted(totals):
    print(f"{status:10} orders {counts[status]}",
          f" total {totals[status]:.2f}")
""")
run('python export.py')
run('head -n 2 orders.jsonl')
run('python report.py')
Next in this series

Part 10: Real-World Manager Scenarios

The sample data on this page is invented for teaching. Every terminal output is the real output of the command above it, run on the official Azure Cosmos DB Linux emulator (vNext) with azure-cosmos 4.17.1. Features the emulator does not support are shown as code. Part of the Real Time Azure Cosmos DB Interview Questions and Answers series on TechBrothersIT. Features, limits and their status (GA or preview) are current as of October 2026; check the official Azure Cosmos DB documentation (learn.microsoft.com/azure/cosmos-db) for anything you quote in an interview.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.