Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← MongoDB & NoSQL Design

NoSQL Fundamentals

  • SQL vs NoSQL Trade-offs
  • Wide-Column Modeling with Cassandra
  • DynamoDB Key Design & Single-Table Modeling
  • Redis Data Structures & Use Cases
Chaturmind
← MongoDB & NoSQL Design

NoSQL Fundamentals

  • SQL vs NoSQL Trade-offs
  • Wide-Column Modeling with Cassandra
  • DynamoDB Key Design & Single-Table Modeling
  • Redis Data Structures & Use Cases
HomeLearnDatabasesMongoDB & NoSQL DesignNoSQL Fundamentals
✓ FreeAdvanced· 6 min read

DynamoDB Key Design & Single-Table Modeling

PK/SK design, item collections, GSIs and sparse indexes, hot partitions, and conditional writes.

Published September 24, 2026


DynamoDB Key Design & Single-Table Modeling

Amazon DynamoDB is a fully managed key-value and document database. It promises single-digit-millisecond latency at practically any scale, but only for access patterns you designed the keys for. Like Cassandra, it rewards access-pattern-first modeling, and the central skill is designing keys, not tables.

The building blocks

Every item (row) has a primary key, in one of two forms:

  • Partition key only (PK): a pure key-value lookup.
  • Partition key + sort key (PK + SK): items sharing a partition key form an item collection, stored together and sorted by the sort key.

The operations that matter:

OperationWhat it doesCost profile
GetItemFetch one item by its full keyCheapest, O(1)
QueryFetch items from one partition key, optionally filtered by a sort-key condition (=, <, between, begins_with)Efficient: reads only the matching range
ScanRead the whole tableExpensive and slow. Avoid on hot paths

A FilterExpression on a Query is applied after the items are read, so you still pay for everything read. Filters trim responses, they don't make reads efficient. Efficiency comes only from keys.

Designing keys with generic names

Advanced designs use generic attribute names (PK, SK), and encode the entity type into the key values. That lets different kinds of items share one table, and one partition:

PKSKOther attributes
CUSTOMER#c42PROFILEname, email, tier
CUSTOMER#c42ORDER#2026-03-01#o981status, total
CUSTOMER#c42ORDER#2026-03-14#o1002status, total
ORDER#o981ITEM#1sku, qty, price
ORDER#o981ITEM#2sku, qty, price

This supports several access patterns with plain Queries:

  • Customer profile: GetItem(PK = CUSTOMER#c42, SK = PROFILE).
  • A customer's orders, newest first: Query(PK = CUSTOMER#c42, SK begins_with "ORDER#"), reading backwards (ScanIndexForward = false). Putting the date in the sort key makes the order chronological.
  • The profile plus recent orders in one request: Query(PK = CUSTOMER#c42) returns both kinds of item from the same collection. This is effectively a pre-joined result.
  • An order's line items: Query(PK = ORDER#o981, SK begins_with "ITEM#").

This is single-table design: related entities live in one table, keyed so that the reads that go together are stored together. It minimizes round trips, and it's why DynamoDB can serve complex screens with one or two requests.

Global secondary indexes (GSIs) for other access paths

When you need to query by a different attribute, add a GSI: a copy of selected attributes, re-keyed by different partition and sort keys, maintained automatically and asynchronously.

Example: "find orders by status, for the operations dashboard". Add attributes GSI1PK = STATUS#SHIPPED and GSI1SK = 2026-03-14#o1002 to order items, and create a GSI on them. Then Query(GSI1, GSI1PK = STATUS#SHIPPED) lists shipped orders by date.

Things to know about GSIs:

  • They're eventually consistent. A just-written item may not appear in the index for a moment.
  • Every write to the base table that touches indexed attributes also costs a write on each affected GSI.
  • Sparse indexes are a powerful trick: only items that have the GSI attributes appear in the index. Set GSI2PK only on orders that need manual review, and the index becomes a small, efficient queue of exactly those orders.

Hot partitions and write sharding

Throughput is spread across physical partitions by partition key. A single partition key receiving a disproportionate share of traffic (a viral product, one huge tenant, "today's" date as a key) hits per-partition limits, and gets throttled, even when the table's total capacity is plenty.

Mitigations:

  • Choose high-cardinality partition keys, such as user IDs or order IDs, rather than a status or a date.
  • Write sharding: append a suffix (VOTES#item42#0 … #9), spread writes across the suffixes, and sum across them when reading.
  • Cache very hot reads in front of the table (DAX, or an application cache).

Transactions, conditions and idempotency

  • Condition expressions make single-item writes safe under concurrency: attribute_not_exists(PK) to create only if absent, or version = :expected for optimistic locking.
  • TransactWriteItems applies up to 100 writes across items and tables atomically, for example decrementing stock and creating an order together. Transactions cost roughly twice as much as normal writes, so use them where atomicity is required.
  • Pass a ClientRequestToken with transactions, so retries after timeouts are idempotent.

When single-table design is, and isn't, worth it

Single-table design shines when access patterns are well known and stable, and latency at scale matters: e-commerce order flows, gaming profiles, SaaS tenant data. Its costs are real, though:

  • The key design is hard to read without documentation. Keep an access-pattern table next to the schema.
  • New, unforeseen query patterns may need new GSIs, or a data migration.
  • Analytics and ad-hoc queries don't fit. Stream changes to a warehouse with DynamoDB Streams.

For early-stage products whose queries change weekly, a relational database, or a simpler one-table-per-entity DynamoDB design, is often the more pragmatic starting point.

Follow-up questions this topic invites — and their answers

Q: What's the difference between Query and Scan? A: Query reads items from a single partition key, optionally narrowed by sort-key conditions, so it touches only the relevant items. Scan reads the entire table, then optionally filters. It's slow and expensive, and should stay out of request paths.

Q: Why put multiple entity types in one table? A: Items that are read together can share a partition key, so one Query returns them all (for example, a customer's profile and recent orders). That replaces joins and multiple round trips, which DynamoDB doesn't support efficiently.

Q: What is a sparse index? A: A GSI whose key attributes exist on only some items. Only those items appear in the index, which makes it a compact, efficient way to query a subset, such as "orders awaiting review", without scanning everything.

Q: How do you avoid hot partitions? A: Use high-cardinality partition keys, spread heavy write traffic for a single logical key across several suffixed keys, and cache hot reads. Avoid keys such as the current date or a status value that concentrate traffic.

Q: Are GSIs strongly consistent? A: No. Global secondary indexes are updated asynchronously, and support only eventually consistent reads. If a read must reflect a write immediately, query the base table by its primary key, which supports strongly consistent reads.

Previous

Wide-Column Modeling with Cassandra

Next

Redis Data Structures & Use Cases

AI Tutor

Lesson: DynamoDB Key Design & Single-Table Modeling

Quick actions

AI responses can be inaccurate. Verify critical information.