NoSQL access patterns: keys, hot partitions and read consistency
Design DynamoDB keys from read and write requirements. Compare direct lookups, secondary indexes and scans, then reason about hot tenants, stale results and conditional updates.
TL;DR: Start a NoSQL design with the operations the application must perform and the consistency each one needs. Choose keys that make those operations bounded, then check how traffic concentrates and what happens when an alternate view lags.
Write the queries before choosing the keys
“NoSQL” includes several data models and consistency contracts. A document database, a key-value store and a wide-column system are not interchangeable implementations of one promise. This example uses DynamoDB to make the decisions concrete; another product needs its own query, index and transaction rules checked.
Consider a fictional repair-service application. A customer opens one repair request, lists their recent requests, and checks whether a submitted payment reference was accepted. An operations worker lists overdue requests across customers. These are four different access patterns. A design that makes the first lookup cheap can still make the operational view expensive.
For a simple composite-key design, use a partition key such as CUSTOMER#c17 and a sort key such as REPAIR#2026-09-20#r81. This groups a customer's requests and gives them a sortable prefix. An exact repair lookup needs both keys; if callers know only r81, supply a separate lookup path or choose a different primary layout. Do not hide that extra read behind the phrase “single-table design.”
| Required operation | Candidate access path | Question to resolve |
|---|---|---|
| Customer's recent repairs | Customer partition plus sort-key range | Are timestamp strings consistently encoded? |
| One repair by full identity | Exact primary-key lookup | Does the caller actually know both keys? |
| Overdue repairs across customers | Reviewed secondary index or materialized view | How stale may this queue be? |
| Payment submission result | Authoritative operation record | Is an immediate strong read required? |
DynamoDB's Query key conditions require an equality condition on the partition key in this simple key model, with supported sort-key constraints. A filter applied after reading does not turn a broad read into a selective key lookup. Pagination is still required when a response reaches its size boundary, including when filtering leaves a page with few results.
Keep an alternate view separate from the authoritative check
DynamoDB supports strongly consistent reads for tables and local secondary indexes, while global secondary indexes use eventual consistency. Its read-consistency reference defines that scope. A successful table write followed immediately by an empty GSI result does not prove the write failed. Adding a “strong read” flag to a GSI query does not change the index's contract.
For the repair workflow, an eventually updated index can propose overdue candidates. Before assigning a worker, use a conditional update on the authoritative record so the assignment succeeds only while the request remains eligible. That prevents a stale candidate from silently overwriting a newer state. The appropriate condition and transaction scope depend on the business rule; reading a current value and later writing unconditionally leaves a race between the two steps.
Test the key contract with a small local fixture
This Python model checks ordering and selection only. It is not a DynamoDB emulator and establishes nothing about distributed consistency or service throughput.
items = [
{"pk": "CUSTOMER#c17", "sk": "REPAIR#2026-09-20#r81"},
{"pk": "CUSTOMER#c18", "sk": "REPAIR#2026-09-21#r82"},
{"pk": "CUSTOMER#c17", "sk": "REPAIR#2026-09-22#r83"},
]
def recent(customer):
matches = [x for x in items if x["pk"] == "CUSTOMER#" + customer]
return sorted(matches, key=lambda x: x["sk"], reverse=True)
assert [x["sk"].split("#")[-1] for x in recent("c17")] == ["r83", "r81"]
assert recent("missing") == []
Change one timestamp to an inconsistent format and inspect the ordering. Lexicographic keys work because the encoding preserves the desired order, not because strings have an intrinsic understanding of dates. Define timezone, precision and tie-breaking rules before creating the real key format.
Check the busiest key, including index keys
A high total table capacity can coexist with one overloaded customer key. The partition-key guidance emphasizes distribution of activity across table and index keys. An index partition called OPEN can collect traffic from every tenant even when the primary table is well distributed.
For a hypothetical 10,000-event-per-second workload, suppose one customer supplies 7,000 events. Distributing the remaining customers does not remove that concentration. Time buckets or write sharding may help, but they require the reader to query and merge the relevant buckets. Price that fan-out and verify ordering before choosing the extra complexity. More partitions do not automatically make a required multi-item invariant atomic.
Self-check: immediately after creating a repair, the customer's confirmation screen queries an eventually consistent index and shows “not found.” Should it create the repair again with a new ID? No. Retain the original operation identity and confirm against the appropriate authoritative path, with a pending state if necessary. An index's delayed visibility must not become a duplicate business operation.
Normalization and read models examines the ownership of duplicated facts, distinguishing current profile data from historical transaction prices.