ASCENDING Blog Home
Jarvis AI
Cloud Services
Talent Solutions
Public Sector
About

Migrating DB Schema to DynamoDB: Design Process and Access Pattern

Read Time: 9 mins Publish Date:

M

Mythri.S

Contributor

Celeste Shao portrait

Celeste Shao

Contributing Writer · AI Specialist

Share on LinkedIn
On this page

Amazon DynamoDB is a NoSQL database service provided by Amazon Web Services (AWS) that allows storing and retrieving data in a flexible, scalable, and high-performance manner. The design process is a lot different from traditional relational database design. One popular way to use Amazon DynamoDB is through a single-table design with multiple entities. 

In this post, we will talk about a standard DynamoDB design process from The DynamoDB book. While Amazon DynamoDB provides us with great performance, scalability, and other benefits, the design and implementation are not straightforward. We will use three different worked use cases to illustrate our design practice at the end.

The Setup

The following design process will utilize a simple e-commerce website example to illustrate the design process which contains Users, Orders, Reviews, Products, and Categories information.

Identify the Entities and Relationships

The first step in designing the Amazon DynamoDB table is identifying the entities we need to store and their relationships. Users can have multiple orders and reviews, and categories can have various products.

EntityPKSK
UsersUSER#<UserName>USER#<UserName>
OrdersUSER#<UserName>ORDER#<OrderId>
CategoriesCATEGORY#<CategoryName>CATEGORY#<CategoryName>
ProductsCATEGORY#<CategoryName>PRODUCT#<ProductName>
ReviewsUSER#<UserName>REVIEW#<ReviewId>

The following secondary-index attributes support additional queries. Each order stores its status and a consistently formatted creation timestamp in GSI1SK; append the order ID to distinguish orders created at the same time.

EntityGSI1PKGSI1SK
OrdersORDERSTATUS#<Status>#<CreatedAt>#<OrderId>
ReviewsPRODUCT#<ProductName>REVIEW#<ReviewId>

GSI2 uses the item’s Type attribute as its partition key and the base-table SK as its sort key. Example type values are USER, ORDER, PRODUCT, REVIEW and CATEGORY.

Entity Relationship Diagram

Entity Relationship

Define the Access Pattern

We should define the access patterns that we need to support. For example, we need to retrieve all orders for a specific user, retrieve all reviews for a particular product, or retrieve all products in a specific category. 

Maintain a list of access patterns for each project. Use the following worksheet as a starting point, then add the queries required by your application.

#Access patternTable or index key condition
1Get a user by usernameBase table: PK = USER#<UserName>, SK = USER#<UserName>
2Get all usersGSI2: Type = USER
3Get all productsGSI2: Type = PRODUCT
4Get products in a categoryBase table: PK = CATEGORY#<CategoryName>, SK begins with PRODUCT#
5Get products by product nameGSI2: Type = PRODUCT, SK = PRODUCT#<ProductName>
6Get orders for a userBase table: PK = USER#<UserName>, SK begins with ORDER#
7Get all ordersGSI2: Type = ORDER
8Get orders in an order-ID rangeGSI2: Type = ORDER, SK between the two ORDER#<OrderId> bounds
9Get orders for a status and date rangeGSI1: GSI1PK = ORDER, GSI1SK between bounds within STATUS#<Status>#
10Get order information by order IDGSI2: Type = ORDER, SK = ORDER#<OrderId>
11Get reviews for a userBase table: PK = USER#<UserName>, SK begins with REVIEW#
12Get all reviewsGSI2: Type = REVIEW
13Get a review by review IDGSI2: Type = REVIEW, SK = REVIEW#<ReviewId>
14Get reviews for a productGSI1: GSI1PK = PRODUCT#<ProductName>, GSI1SK begins with REVIEW#
15Get all categoriesGSI2: Type = CATEGORY

These are logical key values; use expression attribute names and values when implementing the requests. Paginate queries rather than assuming a request returns every matching item.

Determine the Key Schema

The base table groups a user’s profile, orders and reviews under USER#<UserName>, while each category groups its category record and products under CATEGORY#<CategoryName>. The sort-key prefixes distinguish entity types within those groups.

Define the attributes

Store the fields needed by the application as well as the index keys. For example, an order needs its owner, order ID, status, creation timestamp, Type, GSI1PK and GSI1SK. Keep timestamp format and key prefixes consistent between writes and queries.

Define the secondary indexes

Create GSI1 with GSI1PK as its partition key and GSI1SK as its sort key. Create GSI2 with Type as its partition key and SK as its sort key. These are indexes on the same table, not separate secondary tables.

The constant type keys used here make the access patterns easy to demonstrate, but can concentrate traffic as volume grows. Review AWS partition-key design guidance and load-test the table and indexes before adopting this teaching schema for production.

Use Case 1: Find a user by username

The problem

A username stored only as a non-key attribute cannot be used as the partition-key condition of a Query. In this design, however, the username is part of both base-table key values, so a scan is unnecessary.

The solution

Use GetItem with PK = USER#<UserName> and SK = USER#<UserName> to retrieve the user profile directly. If an existing schema does not encode the username in its primary key, evaluate a secondary index for that access pattern.

The result

The application can retrieve the profile from a known username without scanning unrelated items. Define username normalization and uniqueness rules before constructing these keys.

Use Case 2: Query an order-ID range

The problem

The base table groups orders by user. A query across users therefore needs a different key path.

The solution

Query GSI2 with Type = ORDER and a sort-key range over ORDER#<OrderId>. To retrieve only one user’s orders, query the base table with PK = USER#<UserName> and the ORDER# prefix instead.

The result

The index supports a defined order-ID range without a full-table scan. String keys sort by UTF-8 bytes, so encode IDs consistently, for example with fixed-width numeric components. An ID range is not automatically a date range or a geographic grouping.

Use Case 3: Query orders by status and date

The problem

A sort key beginning with status groups each status separately. It cannot support one date-range query across every status unless another access pattern is designed for that purpose.

The solution

Query GSI1 with GSI1PK = ORDER and bounds within STATUS#<Status>#<CreatedAt>#<OrderId>. Both bounds must use the same status prefix. Choose the time interval and boundary values explicitly; use one consistent timezone and timestamp format in stored keys.

The result

The query returns orders for one status in the selected time range. To include multiple statuses, issue the required status-specific queries or design a separate index. A constant ORDER partition key does not by itself distribute a high-volume workload; evaluate sharding or another partitioning strategy when needed.

Verify the access patterns before migration

For each row in the worksheet, write an example item and the corresponding lookup or query. Check that the index attributes are present, the prefixes match and the returned items meet the use case. Test representative data volumes and pagination before migrating the application.

Useful Resources

M

About the author

Mythri.S

Contributor

Celeste Shao portrait

About the author

Celeste Shao

Contributing Writer · AI Specialist

Writes on production LLM systems — observability, evaluation, and the operational patterns that keep AI products reliable after they ship.

Keep reading