AI & assistant-friendly summary

This section provides structured content for AI assistants and search engines. You can cite or summarize it when referencing this page.

Summary

A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database.

Key Facts

  • A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database
  • A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database

Entity Definitions

DynamoDB
DynamoDB is an AWS service discussed in this article.
serverless
serverless is a cloud computing concept discussed in this article.

DynamoDB Single-Table Design Patterns for SaaS Applications

Serverless & Containers 7 min read

Quick summary: A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database.

Key Takeaways

  • A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database
  • A practical guide to DynamoDB single-table design for SaaS — covering access patterns, tenant isolation, GSI strategies, and the patterns that make DynamoDB the ideal serverless database
DynamoDB Single-Table Design Patterns for SaaS Applications
Table of Contents

DynamoDB is the default database for serverless applications on AWS — and for good reason. Single-digit millisecond latency at any scale, zero infrastructure management, on-demand capacity with automatic scaling, and a pricing model that starts at pennies and scales to enterprise workloads.

But DynamoDB’s design philosophy is fundamentally different from relational databases. SQL databases optimize for flexible querying over normalized data. DynamoDB optimizes for known access patterns over denormalized data. Getting this right — especially for multi-tenant SaaS applications — requires understanding single-table design.

Why Single-Table Design?

In a relational database, you create a table for each entity: users, orders, products, invoices. In DynamoDB, you can store all of these entity types in a single table using composite keys.

Why?

  1. DynamoDB pricing — You pay for read/write capacity per table. Multiple tables multiply your baseline costs and make capacity planning harder.
  2. Transaction scope — DynamoDB transactions work within a single table. Cross-table transactions require additional coordination.
  3. Access pattern efficiency — A single table with well-designed keys allows you to retrieve related entities in a single query instead of multiple queries across tables.
  4. Operational simplicity — One table to monitor, backup, and manage instead of dozens.

Key Design Concepts

Partition Key (PK) and Sort Key (SK)

Every DynamoDB item requires a partition key. Optionally, you can add a sort key. Together, they form the primary key:

  • Partition key (PK) — Determines which physical partition stores the item. All items with the same PK are stored together and can be queried together.
  • Sort key (SK) — Enables range queries within a partition. Items with the same PK are sorted by SK.

Example for a SaaS application:

PKSKEntityData
TENANT#acmePROFILETenantname, plan, created_at
TENANT#acmeUSER#user-001Useremail, role, last_login
TENANT#acmeUSER#user-002Useremail, role, last_login
TENANT#acmeORDER#ord-001Ordertotal, status, created_at
TENANT#acmeORDER#ord-002Ordertotal, status, created_at

Access patterns this supports:

  • Get tenant profile: PK = TENANT#acme, SK = PROFILE
  • List all users for a tenant: PK = TENANT#acme, SK begins_with USER#
  • List all orders for a tenant: PK = TENANT#acme, SK begins_with ORDER#
  • Get all data for a tenant: PK = TENANT#acme (returns everything)

Notice that the tenant ID is the partition key. This provides natural tenant isolation — a query for one tenant physically cannot return data from another tenant.

Global Secondary Indexes (GSIs)

GSIs provide alternative access patterns by projecting a different key structure over the same data. You can have up to 20 GSIs per table.

GSI overloading — Use generic attribute names like GSI1PK and GSI1SK so different entity types can reuse the same GSI for different access patterns:

PKSKGSI1PKGSI1SKEntity
TENANT#acmeUSER#user-001USER#user-001@acme.comTENANT#acmeUser
TENANT#acmeORDER#ord-001STATUS#pendingORDER#2026-04-10Order
TENANT#acmeORDER#ord-002STATUS#shippedORDER#2026-04-08Order

GSI1 access patterns:

  • Look up user by email: GSI1PK = USER#user-001@acme.com
  • List pending orders across a tenant: GSI1PK = STATUS#pending, GSI1SK begins_with ORDER#

SaaS Multi-Tenant Patterns

Pattern 1: Tenant-Prefixed Partition Key

The simplest and most common pattern. Every partition key starts with the tenant ID:

PK: TENANT#{tenant_id}#ORDER
SK: {order_id}

Advantages:

  • Strongest isolation — queries are physically scoped to a tenant’s data
  • No risk of cross-tenant data leakage
  • Simple to understand and implement

Disadvantage:

  • Cannot query across tenants without scanning (which is fine — cross-tenant queries should happen in an analytics pipeline, not the application)

Pattern 2: Tenant Isolation with IAM

Combine DynamoDB’s key design with IAM policies for defense-in-depth:

{
  "Effect": "Allow",
  "Action": ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:PutItem"],
  "Resource": "arn:aws:dynamodb:*:*:table/SaaSTable",
  "Condition": {
    "ForAllValues:StringLike": {
      "dynamodb:LeadingKeys": ["TENANT#${aws:PrincipalTag/tenant_id}*"]
    }
  }
}

This IAM policy restricts the Lambda function to only access items where the partition key starts with the tenant’s ID. Even if application code has a bug, IAM prevents cross-tenant access.

Pattern 3: Per-Tenant Tables

For enterprise SaaS with strict compliance requirements, you can create a separate DynamoDB table per tenant:

Table: saas-tenant-acme
Table: saas-tenant-globex
Table: saas-tenant-initech

When to use: Tenants requiring dedicated encryption keys (per-tenant KMS keys), separate backup policies, or regulatory data isolation. See our SaaS multi-tenancy architecture guide for the full silo vs pool analysis.

When to avoid: More than 50 tenants. Managing hundreds of tables becomes an operational burden.

Common Access Patterns

Pattern: Hierarchical Data

SaaS applications often have hierarchical relationships: Tenant → Project → Task → Comment.

PKSK
TENANT#acmePROJECT#proj-001
TENANT#acmePROJECT#proj-001#TASK#task-001
TENANT#acmePROJECT#proj-001#TASK#task-001#COMMENT#cmt-001
TENANT#acmePROJECT#proj-001#TASK#task-002

Queries:

  • All projects for a tenant: PK = TENANT#acme, SK begins_with PROJECT#
  • All tasks in a project: PK = TENANT#acme, SK begins_with PROJECT#proj-001#TASK#
  • All comments on a task: PK = TENANT#acme, SK begins_with PROJECT#proj-001#TASK#task-001#COMMENT#

Pattern: Time-Series Data

For activity logs, audit trails, or event streams:

PKSK
TENANT#acme#ACTIVITY#2026-042026-04-10T14:30:00Z#evt-001
TENANT#acme#ACTIVITY#2026-042026-04-10T15:45:00Z#evt-002
TENANT#acme#ACTIVITY#2026-032026-03-28T09:15:00Z#evt-003

The month is included in the partition key to prevent hot partitions. Querying a specific month is efficient; querying across months requires multiple queries (which is acceptable for time-series data).

If you need to find items by attribute value (e.g., “find all users with role=admin”):

GSI with role as the key:

GSI2PKGSI2SKData
TENANT#acme#ROLE#adminUSER#user-001
TENANT#acme#ROLE#memberUSER#user-002

Query: GSI2PK = TENANT#acme#ROLE#admin returns all admin users for that tenant.

Cost Optimization

On-Demand vs Provisioned

ModeBest ForPricing
On-demandVariable traffic, new applications, developmentPay per read/write ($1.25 per million writes, $0.25 per million reads)
ProvisionedSteady-state traffic, predictable workloadsPer RCU/WCU per hour + auto-scaling
Provisioned + ReservedConsistent high-volume productionUp to 77% discount with 1-year commitment

For most SaaS startups: Start with on-demand mode. Switch to provisioned with auto-scaling when your traffic patterns become predictable (typically after 3-6 months of production data).

Reducing Read Costs

  • Eventually consistent reads — Half the cost of strongly consistent reads. Use for data that can tolerate slight staleness (dashboards, reports, activity feeds).
  • DAX (DynamoDB Accelerator) — In-memory cache for microsecond read latency. Cost-effective when your read-to-write ratio exceeds 10:1.
  • Projection expressions — Only retrieve the attributes you need instead of the full item. Reduces consumed RCUs for items with many attributes.

Reducing Storage Costs

  • TTL (Time to Live) — Automatically delete expired items at no cost. Essential for session data, temporary tokens, and audit logs with retention periods.
  • Standard-IA table class — 60% lower storage cost for infrequently accessed data. Useful for archive or compliance tables.

For comprehensive AWS cost optimization, including DynamoDB right-sizing and capacity planning, talk to our team.

DynamoDB Streams for Event-Driven SaaS

DynamoDB Streams captures a time-ordered sequence of item-level changes:

DynamoDB Table → DynamoDB Streams → Lambda → (downstream actions)

SaaS use cases:

  • Audit trail — Stream all changes to an audit log table or S3 for compliance
  • Real-time notifications — Trigger notifications when order status changes
  • Cross-service synchronization — Replicate data to OpenSearch for full-text search or to a data lake for analytics
  • Billing event capture — Track usage events for metered billing

Common Mistakes

Mistake 1: Designing for Flexibility Instead of Access Patterns

If you design your DynamoDB table like a relational database (one entity per table, normalized data, ad-hoc queries), you will fight DynamoDB’s strengths. Start with your access patterns, then design your keys to support them. If you cannot define your access patterns, DynamoDB may not be the right database for that workload.

Mistake 2: Hot Partitions

If all your queries hit the same partition key (e.g., a single tenant that represents 80% of your traffic), that partition becomes a bottleneck. Design your keys to distribute traffic. For hot tenants, consider adding a suffix to the partition key (e.g., TENANT#acme#SHARD#1, TENANT#acme#SHARD#2) and scatter-gather across shards.

Mistake 3: Scanning Instead of Querying

A Scan operation reads the entire table — expensive and slow. If you find yourself scanning, you are missing an index. Every access pattern should be a Query (O(1) partition lookup + sort key range) or GetItem (exact primary key lookup).

Mistake 4: Not Using Transactions

DynamoDB supports ACID transactions across up to 100 items in a single table. If you need to update an order and its inventory atomically, use TransactWriteItems instead of separate writes that can partially fail.

Getting Started

DynamoDB single-table design requires a mindset shift from relational databases, but the operational benefits — zero infrastructure management, predictable performance, and seamless scaling — make it the natural choice for serverless SaaS applications on AWS.

For serverless application design with DynamoDB, Lambda, and API Gateway, see our AWS Serverless Architecture Services.

Contact us to design your serverless data architecture →

Ready to discuss your AWS strategy?

Our certified architects can help you implement these solutions.

Recommended Reading

Explore All Articles »