Azure DP-900 study materials
Azure DP-900 study resources

Product Catalog and a Recommendation Graph in Cosmos DB

Azure DP-900 · Put your knowledge to work in context.

All case studies

Product Catalog and a Recommendation Graph in Cosmos DB

Tailspin Toys sells 25 million product SKUs. Each catalog document is JSON, and the fields differ by category: toys carry age ranges and safety flags, electronics carry voltage and nested warranty objects. Reads outnumber writes 95 to 5, and 60% of reads come from the US, Germany, and Japan, where shoppers abandon a page if p95 latency exceeds 40 ms. The existing mobile app already uses MongoDB drivers and the team does not want to rewrite its data access code. A new marketing feature must answer 'which products are frequently bought with this one' by traversing purchase relationships up to three hops. Documents average 6 KB. The toys category holds 45% of all documents and is also the most heavily read.

1. Which Cosmos DB API or APIs would you use? Address the fact that the existing app uses MongoDB drivers and that a graph traversal feature is required, and state the constraint that makes this a genuine trade-off.

Read the worked answer

Constraint that forces the decision: an Azure Cosmos DB account has exactly one API type. You cannot put MongoDB collections and a Gremlin graph in the same account. Anything that mixes them means two accounts, two bills, two sets of provisioned throughput, and no cross-account joins.

Option A - MongoDB API only, with relationships as documents (recommended). The mobile app keeps its drivers and query code, which is a schedule and risk saving that dwarfs the elegance of a native graph. Store purchase relationships as small edge documents (fromProduct, toProduct, count). Then precompute the top-N 'frequently bought with' list for each product in a nightly job and write it into the product document or a sibling document, so the feature becomes a single-partition point read at single-digit milliseconds. Three hops is also a bounded number - you can compute it offline where latency does not matter.

Option B - MongoDB API account plus a Gremlin API account. Choose this only if the traversal is genuinely exploratory: analysts writing unpredictable multi-hop queries, variable depth, changing relationship types. Then Gremlin's native traversal is worth the second account and the operational overhead.

Judgement: precomputed answers beat live traversal whenever the question is predictable and the answer is read far more often than it changes. Here it is 95-to-5 read-heavy and the question is fixed, so Option A wins unless marketing confirms they need ad hoc graph exploration.

2. Select a consistency level for the storefront. Explain why strong and eventual are both wrong, and how you would get lower latency for catalog browsing without weakening the cart and checkout flow.

Read the worked answer

Rule out strong. It would put a cross-region round trip on every write and read, which cannot coexist with a 40 ms p95 target across three continents. It is also unavailable if the account is configured for multi-region writes. The latency budget alone disqualifies it.

Rule out eventual as the account default. A shopper who adds an item to a cart and then loads the cart must see that item; a shopper must not see a sold-out item as available or a stale price at checkout. Eventual breaks read-your-own-writes, which is a correctness problem, not a performance one.

Choose session consistency. It guarantees read-your-own-writes within a session at single-region read latency, which is exactly the cart-to-checkout flow, and it works with multi-region writes using the session token. That is the correct account default.

Now the nuance that gets the 40 ms p95: consistency can be relaxed per request, never strengthened. So keep the account at session and have the catalog browsing path pass a weaker consistency header (eventual or consistent prefix) because a product description page does not need read-your-writes and benefits from reading the nearest replica. Reserve session for cart, pricing, and checkout. One account, two behaviours, chosen per request by what the operation actually needs.

3. Evaluate /category versus /productId as the partition key. What goes wrong with the obvious choice, and what does /productId cost you when the graph feature is added?

Read the worked answer

/category is the trap. Toys holds 45% of 25 million documents - roughly 11 million documents and tens of gigabytes funnelled into one logical partition. That single partition absorbs a disproportionate share of the read traffic, hits the per-logical-partition storage ceiling, and produces 429 throttling on the most popular pages while the other partitions sit idle. The read distribution is the problem, not just the write distribution.

/productId is the right default. It is high-cardinality, spreads writes evenly, and matches the dominant access pattern - fetch one product by ID becomes a single-partition point read, which is the cheapest and fastest operation Cosmos DB offers. For roughly 6 KB documents the item itself stays comfortably inside one logical partition.

The cost appears with the graph. Edge documents also need a partition key. Partition edges by source product and a three-hop traversal fans out across many partitions, turning one query into many cross-partition calls with the associated request-unit multiplication. Two mitigations: (a) if you followed the precompute approach, the traversal happens in a nightly batch job where cross-partition cost is a batch cost rather than a user-facing latency problem; (b) if relationships must be traversed live at scale, consider a hierarchical partition key (for example category at the first level, product at the second) so that category-scoped queries stay single-partition without collapsing everything into one hot key. Verify current service limits for hierarchical keys before committing the design.