Understanding Single Table Design with boto3-assist

Learn how boto3-assist makes DynamoDB single-table design simple and maintainable. Discover the benefits of storing all your entities in one table and how to leverage composite keys for maximum performance.

If you've worked with DynamoDB, you've probably heard about single-table design—the practice of storing all your application's data in a single table rather than creating separate tables for each entity type. While this may seem counterintuitive to those coming from relational databases, it's DynamoDB's recommended best practice and offers significant advantages in terms of performance, cost, and scalability.

In this post, I'll explain the fundamentals of single-table design and show you how boto3-assist makes implementing this pattern straightforward and maintainable.

Why Single Table Design?

Traditional relational database thinking encourages us to create separate tables for each entity type. However, DynamoDB works differently. Here's why single-table design is the recommended approach:

Performance

All related data can be retrieved in a single query, reducing the number of round trips to the database. Instead of making separate requests for an order and its items, you can fetch everything at once.

Cost

Fewer tables mean fewer provisioned resources and lower overall costs. With single-table design, you only pay for one table's read/write capacity instead of multiple tables.

Atomic Transactions

You can perform transactions across multiple entity types within the same table, ensuring data consistency without complex coordination.

Better Data Modeling

Single-table design forces you to think about your access patterns upfront, which leads to more efficient queries and better application architecture.

Traditional vs. Single Table Approach

Traditional Multi-Table Approach:

Users Table:     user_id, name, email
Orders Table:    order_id, user_id, total
Products Table:  product_id, name, price

Single Table Design:

AppTable:  pk, sk, ... (all entity attributes)

All your users, orders, and products live in one table, differentiated by their partition and sort keys.

The Foundation: Composite Keys (pk/sk)

In single-table design, the partition key (pk) and sort key (sk) are crucial. They determine:

  • Where your data is stored (partition key)
  • How your data is organized within that partition (sort key)
  • What query patterns you can support

Key Structure Pattern

Keys in boto3-assist follow a structured pattern using the # delimiter:

entityType#entityId

For example:

  • user#alice - Represents user Alice
  • product#abc-456 - Represents product with ID abc-456
  • order#xyz-789 - Represents order with ID xyz-789

Simple Entity Storage with boto3-assist

Let's start with the simplest case: storing a single entity type. Here's how to define a Product model:

from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey

class Product(DynamoDBModelBase):
    def __init__(self, id=None, name=None, price=0.0):
        super().__init__()
        self.id = id
        self.name = name
        self.price = price
        self.__setup_indexes()
    
    def __setup_indexes(self):
        # PRIMARY KEY: Get product by ID
        primary = DynamoDBIndex()
        primary.partition_key.attribute_name = "pk"
        primary.partition_key.value = lambda: DynamoDBKey.build_key(
            ("product", self.id)
        )
        primary.sort_key.attribute_name = "sk"
        primary.sort_key.value = lambda: DynamoDBKey.build_key(
            ("product", self.id)
        )
        self.indexes.add_primary(primary)
        
        # GSI0: List all products
        self.indexes.add_secondary(
            DynamoDBIndex(
                index_name="gsi0",
                partition_key=DynamoDBKey(
                    attribute_name="gsi0_pk",
                    value=lambda: DynamoDBKey.build_key(("products", ""))
                ),
                sort_key=DynamoDBKey(
                    attribute_name="gsi0_sk",
                    value=lambda: DynamoDBKey.build_key(("name", self.name))
                )
            )
        )

What gets stored in DynamoDB:

{
  "pk": "product#abc-123",
  "sk": "product#abc-123",
  "id": "abc-123",
  "name": "Widget",
  "price": 29.99
}

Access pattern: Get product by ID

model = Product(id="abc-123")
response = db.get(model=model, table_name=table_name, do_projections=False)

Now you can query all products:

from boto3.dynamodb.conditions import Key

key_condition = Key("gsi0_pk").eq("products")
response = db.query(
    key=key_condition,
    index_name="gsi0",
    table_name=table_name
)

Why boto3-assist Makes This Easy

Without a library like boto3-assist, implementing single-table design involves:

  • Manually constructing composite keys for every operation
  • Writing repetitive serialization code
  • Managing index definitions across your codebase
  • Handling DynamoDB's Decimal type conversions

With boto3-assist:

  • Declarative index definitions right in your model class
  • Automatic key generation using lambda functions
  • Type-safe serialization/deserialization with the map() method
  • Automatic Decimal conversion - no more TypeError: Float types are not supported
  • Clean, readable code that clearly expresses your data model

Common Patterns Summary

Pattern Partition Key Sort Key Use Case
Single Item entity#id entity#id Get specific entity
One-to-Many parent#id child#id Get parent with children
All Items Static value (via GSI) entity#attribute List all of entity type

What's Next?

In this post, we covered the fundamentals of single-table design and how boto3-assist simplifies the implementation. In my next post, "Modeling One-to-Many Relationships in DynamoDB", I'll show you the real power of single-table design: retrieving an order and all its items in a single query by sharing partition keys.

Get Started Today

boto3-assist is open source and available on PyPI:

pip install boto3-assist

Check out the GitHub repository for more examples and comprehensive documentation.


About the Author: Eric Wilson is the founder of Geek Cafe, specializing in AWS serverless architectures and Python development. He created boto3-assist to simplify common patterns in AWS development.

Geek Cafe LogoGeek Cafe

Your trusted partner for cloud architecture, development, and technical solutions. Let's build something amazing together.

Quick Links

© 2025 Geek Cafe LLC. All rights reserved.

Research Triangle Park, North Carolina

Version: 8.7.2