Skip to main content

Why Single-Table Design?

In DynamoDB, you design for access patterns, not normalized relations. Single-table design:
  • Reduces costs - One table, one set of capacity units
  • Enables transactions - TransactWriteItems across entities in same table
  • Simplifies operations - One table to monitor, backup, and manage
  • Improves performance - Related data often fetched in single query
Single-table design requires upfront access pattern analysis. Adding new access patterns later may require table restructuring or new GSIs.

Entity Definitions

User Entity

Email Entity

Relationship

A user can have multiple email addresses. Exactly one must be marked as primary. The primary email must be verified.

Table Schema

Table Name: UserServiceTable

Primary Key Structure

Global Secondary Index (GSI1)

Purpose: Look up users and emails by email address

Complete Table Layout

The PROFILE record does not have GSI1PK/GSI1SK. Only EMAIL records are indexed in GSI1. This prevents duplicate results when querying by email address and ensures clean email uniqueness checks.

Access Patterns

Operation: GetItem
Key: PK = USER#{userId}, SK = PROFILE
Use case: Load user profile for authenticated user
Operation: Query
Key: PK = USER#{userId}, SK begins_with EMAIL#
Use case: List all email addresses for user settings page
Operation: Query on GSI1
Key: GSI1PK = EMAIL#{normalizedEmail}
Use case: Look up user during login, check email uniqueness
Operation: Query on GSI1 (limit 1)
Key: GSI1PK = EMAIL#{normalizedEmail}
Use case: Validate email uniqueness before adding
Operation: Query
Key: PK = USER#{userId}
Use case: Load complete user data (profile + all emails) in single query

Email Normalization

Emails are normalized before storage and lookup:
  1. Convert to lowercase
  2. Trim whitespace
Gmail-specific normalization (removing dots and plus-addressing) is intentionally omitted. This is a business decision that should be discussed with stakeholders. Some users rely on plus-addressing for inbox filtering, and removing it could break their workflows. If abuse becomes an issue, implement it as a configurable option per organization.

Data Integrity Constraints

Email Uniqueness

DynamoDB has no native UNIQUE constraint. We enforce uniqueness through:
  1. Query GSI1 before insert to check if email exists
  2. Conditional write with attribute_not_exists(PK) on the email record
  3. Race condition handling via optimistic concurrency

Primary Email Invariant

Exactly one email per user must be primary. Enforced via TransactWriteItems:

Optimistic Locking

Concurrent updates are handled via version number:
If another request updated the record, the condition fails with ConditionalCheckFailedException.

Capacity Planning

Start with on-demand capacity. After 2-4 weeks of production data, analyze CloudWatch metrics to determine if provisioned capacity with auto-scaling would be more cost-effective.

GSI Capacity

GSI has separate capacity from the base table. Monitor GSI throttling independently:
  • ConsumedReadCapacityUnits for GSI1
  • ConsumedWriteCapacityUnits for GSI1
  • ThrottledRequests for GSI1