DEV Community

Said Olano
Said Olano

Posted on

AWS DynamoDB: Mastering NoSQL Database Design (2026-08-30 17:54)

AWS DynamoDB: NoSQL Database Design

Amazon DynamoDB is a fully managed, serverless NoSQL database that delivers single-digit millisecond performance at virtually any scale. But unlocking that performance requires a fundamentally different design mindset than the relational databases many engineers are accustomed to. This post walks through the core concepts and best practices for designing effective DynamoDB tables.

Relational vs. NoSQL Thinking

In relational databases, you normalize data first and figure out queries later. In DynamoDB, you invert this: you must understand your access patterns before you design your schema. DynamoDB is optimized for known, repeatable queries—not ad hoc analytics.

If you find yourself needing flexible, unpredictable queries, DynamoDB may be the wrong tool. But for high-scale applications with well-defined access patterns, it excels.

Core Building Blocks

Primary Keys

Every DynamoDB item is uniquely identified by a primary key, which comes in two forms:

  • Partition key (simple): A single attribute that determines the physical partition where data is stored.
  • Partition key + sort key (composite): Enables multiple items under the same partition key, sorted by the sort key.

The partition key is hashed to distribute data across partitions. Choosing a high-cardinality partition key is critical to avoid "hot partitions" that throttle throughput.

import boto3

dynamodb = boto3.resource('dynamodb')

table = dynamodb.create_table(
    TableName='Orders',
    KeySchema=[
        {'AttributeName': 'PK', 'KeyType': 'HASH'},   # Partition key
        {'AttributeName': 'SK', 'KeyType': 'RANGE'}   # Sort key
    ],
    AttributeDefinitions=[
        {'AttributeName': 'PK', 'AttributeType': 'S'},
        {'AttributeName': 'SK', 'AttributeType': 'S'}
    ],
    BillingMode='PAY_PER_REQUEST'
)
Enter fullscreen mode Exit fullscreen mode

Single-Table Design

One of DynamoDB's most powerful (and counterintuitive) patterns is single-table design—storing multiple entity types in one table. This reduces round trips and leverages DynamoDB's ability to fetch related items in a single query.

Consider an e-commerce application with users and orders. Instead of two tables, we use generic PK and SK attributes:

PK SK Attributes
USER#123 PROFILE#123 name, email
USER#123 ORDER#1001 total, status
USER#123 ORDER#1002 total, status

Now, a single query fetches a user and all their orders:

from boto3.dynamodb.conditions import Key

response = table.query(
    KeyConditionExpression=Key('PK').eq('USER#123') &
                           Key('SK').begins_with('ORDER#')
)
Enter fullscreen mode Exit fullscreen mode

The begins_with condition on the sort key lets us slice related items efficiently.

Secondary Indexes

When you need to query on attributes other than the primary key, use secondary indexes.

Global Secondary Index (GSI)

A GSI has a completely different partition and sort key from the base table. It's stored separately and updated asynchronously (eventually consistent).

GlobalSecondaryIndexes=[
    {
        'IndexName': 'StatusIndex',
        'KeySchema': [
            {'AttributeName': 'status', 'KeyType': 'HASH'},
            {'AttributeName': 'createdAt', 'KeyType': 'RANGE'}
        ],
        'Projection': {'ProjectionType': 'ALL'}
    }
]
Enter fullscreen mode Exit fullscreen mode

Local Secondary Index (LSI)

An LSI shares the partition key of the base table but uses an alternate sort key. LSIs must be created at table creation time and support strong consistency.

Rule of thumb: Prefer GSIs for flexibility. Use LSIs only when you need strong consistency on an alternate sort key.

Overloading Keys and Indexes

To support many access patterns without proliferating indexes, DynamoDB practitioners often overload GSIs. By using generic index attribute names (e.g., GSI1PK, GSI1SK), a single index can serve multiple entity types and query patterns.

This maximizes the value of your (limited) indexes while keeping your data model compact.

Capacity and Cost Considerations

DynamoDB offers two capacity modes:

  • On-demand: Pay per request. Ideal for unpredictable or spiky workloads.
  • Provisioned: Reserve read/write capacity units (RCUs/WCUs). Cheaper for steady, predictable traffic; supports auto-scaling.

Every read and write consumes capacity based on item size. Design tips to control cost:

  • Keep items small (max 400 KB per item).
  • Use ProjectionExpression to fetch only needed attributes.
  • Avoid scans; they read every item in the table.

Common Anti-Patterns

  • Hot partitions: Using low-cardinality partition keys (like a boolean or status flag).
  • Overusing Scan: Always prefer Query with a key condition.
  • Storing large blobs: Offload big objects to S3 and store references in DynamoDB.
  • Treating it like SQL: Attempting joins or complex aggregations client-side at scale.

Conclusion

DynamoDB rewards upfront design discipline. By mapping your access patterns first, embracing single-table design, and thoughtfully leveraging secondary indexes, you can build systems that scale seamlessly with pred

Top comments (0)