DEV Community

Said Olano
Said Olano

Posted on

AWS DynamoDB: Mastering NoSQL Database Design (2026-09-04 23:30)

AWS DynamoDB: Mastering NoSQL Database Design

Amazon DynamoDB is a fully managed, serverless NoSQL database designed for applications that require single-digit millisecond performance at any scale. Unlike traditional relational databases, DynamoDB demands a fundamentally different approach to data modeling. This post explores the core concepts and best practices for designing effective DynamoDB schemas.

Understanding the DynamoDB Data Model

At its core, DynamoDB organizes data into tables, which contain items (rows), and each item is composed of attributes (columns). The key differentiator is how you access data—everything revolves around your primary key.

Primary Key Types

DynamoDB supports two types of primary keys:

  1. Partition Key (Simple Primary Key): A single attribute that uniquely identifies each item.
  2. Composite Key (Partition Key + Sort Key): Two attributes where the partition key determines data distribution and the sort key orders items within a partition.
{
  "PK": "USER#12345",
  "SK": "PROFILE",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "createdAt": "2024-01-15T10:30:00Z"
}
Enter fullscreen mode Exit fullscreen mode

The Golden Rule: Access Patterns First

Unlike relational modeling where you normalize data and figure out queries later, DynamoDB requires you to define your access patterns before designing your schema. Ask yourself:

  • What are the queries my application will run?
  • How frequently is each query executed?
  • What data needs to be returned together?

Example Access Patterns

For an e-commerce application:

Access Pattern Key Condition
Get user profile PK = USER#<id>, SK = PROFILE
Get user's orders PK = USER#<id>, SK begins_with ORDER#
Get order details PK = ORDER#<id>

Single-Table Design

One of DynamoDB's most powerful (and initially confusing) patterns is single-table design—storing multiple entity types in one table. This reduces the number of round trips and leverages DynamoDB's efficient key-based retrieval.

[
  { "PK": "USER#123", "SK": "PROFILE", "type": "user" },
  { "PK": "USER#123", "SK": "ORDER#001", "type": "order", "total": 99.99 },
  { "PK": "USER#123", "SK": "ORDER#002", "type": "order", "total": 149.50 }
]
Enter fullscreen mode Exit fullscreen mode

By querying PK = USER#123, you retrieve the user and all their orders in a single operation.

Secondary Indexes

When your access patterns require querying by non-key attributes, secondary indexes come to the rescue.

Global Secondary Index (GSI)

A GSI allows querying with a completely different partition and sort key. It's stored separately and eventually consistent.

import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('AppData')

# Query using a GSI to find orders by status
response = table.query(
    IndexName='StatusIndex',
    KeyConditionExpression='#status = :status',
    ExpressionAttributeNames={'#status': 'status'},
    ExpressionAttributeValues={':status': 'SHIPPED'}
)
Enter fullscreen mode Exit fullscreen mode

Local Secondary Index (LSI)

An LSI shares the same partition key but uses a different sort key. It must be created at table creation time and offers strong consistency.

Capacity Modes

DynamoDB offers two billing models:

  • On-Demand: Pay per request. Ideal for unpredictable or spiky workloads.
  • Provisioned: Specify read/write capacity units. More cost-effective for steady, predictable traffic. Combine with auto-scaling for flexibility.

Best Practices

  1. Avoid hot partitions: Distribute writes evenly by choosing high-cardinality partition keys.
  2. Keep items small: Items have a 400KB limit; large items increase costs and latency.
  3. Use sparse indexes: Only items with the indexed attribute appear in the index, saving space.
  4. Leverage batch operations: Use BatchGetItem and BatchWriteItem to reduce network overhead.
  5. Enable TTL: Automatically expire stale data to control storage costs.
# Example of a batch write
with table.batch_writer() as batch:
    for item in items:
        batch.put_item(Item=item)
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls to Avoid

  • Applying relational thinking: Resist the urge to normalize everything.
  • Scanning tables: Scan operations read the entire table and are expensive—always prefer Query.
  • Ignoring partition key design: Poor key selection leads to throttling and uneven performance.

Conclusion

DynamoDB rewards developers who invest time upfront in understanding their access patterns. By embracing denormalization, single-table design, and thoughtful key selection, you can build applications that scale seamlessly to millions of requests per second. The paradigm shift from relational thinking can be challenging, but the payoff in performance and operational simplicity is substantial.

Start small, model your access patterns explicitly, and iterate as your application evolves.

Top comments (0)