DynamoDB Guide
A practical guide to designing fast, scalable DynamoDB tables, indexes, costs, migrations, and production operations on AWS.
Amazon DynamoDB is a managed NoSQL database built for applications that need predictable performance at scale. It is often used for serverless backends, high-traffic APIs, event-driven systems, IoT platforms, ecommerce workloads, financial ledgers, gaming profiles, and any product where latency and availability matter more than relational joins.
DynamoDB can be excellent. It can also become expensive, awkward, or slow if the data model is copied from a relational database. The difference usually comes down to design. DynamoDB rewards teams that understand their access patterns before they create tables.
What DynamoDB is good at
DynamoDB is a fully managed key-value and document database from AWS. You do not manage database servers, storage volumes, patching, clustering, or failover. AWS handles the operational base layer, while your team designs the table structure, indexes, access patterns, capacity mode, data retention, and application integration.
It is a strong fit when you need:
- Single-digit millisecond reads and writes at high scale.
- A managed database that works well with AWS Lambda and serverless architectures.
- Flexible JSON-like items rather than rigid relational rows.
- Horizontal scale without manually sharding databases.
- High availability across multiple Availability Zones.
- Event-driven workflows using DynamoDB Streams.
- Global applications using DynamoDB Global Tables.
It is not the right answer for every database problem. If your workload depends on ad hoc joins, complex reporting queries, heavy relational integrity, or interactive analytics, a relational database, data warehouse, OpenSearch, or lakehouse may be a better primary store. Many strong AWS architectures use DynamoDB for the operational workload and send events into another system for analytics.
The design shift: query-first modelling
Relational design often starts with entities: users, orders, invoices, products, payments. DynamoDB design starts with questions: what does the application need to read and write, and how often?
Before building a DynamoDB table, list the access patterns. For example:
- Get a customer profile by customer ID.
- List recent orders for a customer.
- Find an order by order ID.
- Show all unpaid invoices for an account.
- Fetch the latest status event for a shipment.
- Update a user's subscription state.
Each access pattern should map to a primary key lookup, a range query, or an index query. If a feature needs to scan a large table to find matching records, the model probably needs more work.
Primary keys, partition keys, and sort keys
Every DynamoDB table has a primary key. That key can be simple or composite.
A simple primary key uses only a partition key. This is useful when each item is accessed directly by ID, such as a session token, API key, or feature flag.
A composite primary key uses a partition key and a sort key. This is where DynamoDB becomes much more flexible. The partition key groups related items together, while the sort key controls ordering and filtering within that group.
For example, a table might use:
PK = CUSTOMER#123SK = ORDER#2026-08-09#987
That structure makes it efficient to fetch all orders for one customer, query a date range, or retrieve one specific order if the application knows the key. The exact format depends on the product, but the principle is consistent: design keys around reads.
Single-table design: useful, but not magic
Single-table design means storing multiple entity types in one DynamoDB table. A customer, an order, a payment, and a support ticket can live in the same table if they share access patterns. This can reduce round trips, simplify transactions, and make related data cheap to query.
Single-table design is not a badge of honour. It is a technique. For some systems, one table is elegant. For others, two or three tables are clearer and safer. The goal is not to minimise table count. The goal is to make the application's reads and writes efficient, understandable, and maintainable.
A practical single-table model often uses prefixed keys such as USER#, ORDER#, ORG#, and INVOICE#. These prefixes make item types obvious, prevent accidental key collisions, and help developers reason about the table.
Indexes: GSIs and LSIs
DynamoDB indexes give you alternative ways to query the same data.
Global secondary indexes
A global secondary index, or GSI, has its own partition key and optional sort key. It lets the application query items by attributes other than the table's primary key. For example, an orders table might use the main key for customer queries and a GSI for order status queries.
GSIs are powerful, but they are not free. They add write cost, storage cost, and operational complexity. A write to the base table may also update one or more indexes. If an index is rarely used, it may not justify the cost.
Local secondary indexes
A local secondary index, or LSI, uses the same partition key as the base table but a different sort key. LSIs must be created when the table is created. They are useful for alternate ordering within the same partition, but they are less flexible than GSIs.
Most teams use GSIs more often than LSIs. The important point is to design indexes from real access patterns rather than adding them reactively every time a new query appears.
Capacity modes and cost control
DynamoDB has two main capacity modes: on-demand and provisioned.
On-demand capacity is simple. You pay for the reads and writes you use. This is usually a good starting point for new products, spiky workloads, prototypes, and systems where traffic is hard to predict.
Provisioned capacity lets you set read and write capacity in advance. It can be cheaper for steady, predictable workloads, especially when paired with auto scaling or reserved capacity. It also demands more planning, because under-provisioning can throttle requests and over-provisioning wastes money.
Cost control in DynamoDB is mostly about data modelling discipline. Common cost problems include:
- Scanning tables instead of querying keys.
- Creating indexes that duplicate large attributes unnecessarily.
- Writing large items when only a small attribute changes.
- Using strongly consistent reads when eventual consistency is enough.
- Keeping old data forever instead of using TTL or archiving.
- Letting hot partitions force retries and wasted capacity.
A good DynamoDB design can be extremely cost-effective. A poor one can become expensive long before the product feels large.
Hot partitions and uneven traffic
DynamoDB partitions data behind the scenes. If too much traffic hits the same partition key, that key can become hot. A common example is using one partition key for all recent events, all open jobs, or all records for a very large tenant.
Hot partitions are usually a modelling problem. The fix may involve spreading writes across calculated shards, adding time buckets, changing the partition key, or splitting a large tenant's workload more carefully. The right solution depends on the read pattern. Randomising keys can help writes, but it can make reads harder if the application later needs to query all related data.
Consistency, transactions, and correctness
DynamoDB supports eventually consistent reads and strongly consistent reads. Eventually consistent reads are cheaper and often enough for feeds, dashboards, logs, and many user-facing screens. Strongly consistent reads are useful when the application must immediately read the latest write from the same Region.
DynamoDB also supports transactions across multiple items. Transactions are useful for workflows such as account updates, inventory checks, idempotency records, and multi-item state changes. They should be used deliberately. They cost more than standard writes and can add contention if many requests update the same items.
Correctness often depends on conditional writes. For example, an application can create an order only if an idempotency key does not already exist, update a balance only if the current version matches, or reserve inventory only if the available quantity is above zero. These patterns are essential for reliable distributed systems.
DynamoDB Streams and event-driven systems
DynamoDB Streams capture item-level changes and make them available to consumers such as AWS Lambda. This is useful when changes in the database should trigger other work: sending notifications, updating a search index, writing audit records, refreshing projections, or publishing events to another service.
Streams are often the bridge between a fast operational store and the rest of the platform. The database handles the transaction, and downstream systems react to the change. This pattern can work well, but it needs careful error handling, retry behaviour, idempotency, and observability.
TTL, backups, and data lifecycle
DynamoDB Time to Live, usually called TTL, lets you mark items for automatic expiry. It is useful for sessions, temporary verification codes, cache records, short-lived workflow state, and data that only needs to exist for a limited period.
Production systems should also use backups. DynamoDB supports point-in-time recovery, which can restore a table to a specific second within the recovery window. This is a practical safety net against accidental writes, bad deployments, and data corruption bugs.
Backups do not replace a data lifecycle strategy. If the application keeps every event, audit entry, and inactive record forever, storage and index costs will grow. Older data can often move to S3 for reporting, compliance, or long-term archive.
Security and access control
DynamoDB security usually starts with IAM. Applications should have the minimum permissions they need, scoped to the right tables, indexes, and actions. In multi-service systems, each service should have its own role rather than sharing broad credentials.
Encryption at rest is built in. Network access can be tightened using VPC endpoints where appropriate. Sensitive attributes may still need application-level encryption, especially when the data has strict internal access rules.
For regulated or high-risk environments, the design should include audit logging, least-privilege IAM, change control, backup testing, and a clear incident response process.
Common DynamoDB mistakes
- Starting with a relational model. Normalised tables and join-heavy thinking usually lead to slow or expensive DynamoDB access.
- Using scans in production paths. Scans have their place in maintenance jobs, but they rarely belong in customer-facing requests.
- Adding too many indexes. Every index has a cost. Indexes should exist because a known access pattern needs them.
- Ignoring item size. Large items consume more capacity and can make indexes more expensive.
- Forgetting observability. Throttling, latency, consumed capacity, stream failures, and retry rates should be monitored.
- Missing idempotency. Distributed systems retry. DynamoDB designs should expect duplicate requests and handle them safely.
Migration planning
Migrating to DynamoDB is not just a database export. It is a modelling exercise. Teams need to identify access patterns, design keys, create indexes, write transformation logic, test performance, and plan cutover carefully.
A typical migration plan includes:
- Document the current reads, writes, reports, and background jobs.
- Separate operational access patterns from analytics requirements.
- Design the DynamoDB table and index structure.
- Build a migration script or streaming replication path.
- Run load tests using realistic data volumes and traffic shapes.
- Verify correctness, idempotency, and rollback options.
- Cut over in stages where possible.
The safest migrations are boring. They have test data, rehearsal runs, clear metrics, and a rollback path. DynamoDB can scale quickly, but application assumptions need to be tested before production traffic arrives.
When DynamoDB fits serverless architecture
DynamoDB works especially well with AWS Lambda, API Gateway, EventBridge, Step Functions, and SQS. This combination lets teams build systems with low operational overhead. The application can handle bursts without a permanent fleet of database servers or application workers.
Serverless does not remove architecture work. It moves the work into event design, permissions, retries, observability, and data modelling. DynamoDB is often the central state store in that architecture, so its keys and indexes shape the whole system.
How Eight Mile can help
Eight Mile helps teams design, build, and modernise cloud software. DynamoDB projects often touch several areas at once: backend APIs, AWS infrastructure, serverless application design, Terraform, Docker, CI/CD, workflow automation, and legacy system migration.
We can help with DynamoDB table design, access pattern mapping, AWS architecture, cost optimisation, migration planning, production hardening, observability, and integration with services such as Lambda, API Gateway, EventBridge, SQS, and Step Functions. For teams with an existing system, we can review the current database model, identify bottlenecks, and plan a safer path to a scalable AWS architecture.
If the scope is still unclear, that is normal. A short discovery session is often enough to decide whether DynamoDB is the right primary database, a supporting store, or the wrong tool for the job.
Final thoughts
DynamoDB is fast, managed, and reliable when the model fits the workload. The hard part is not creating the table. The hard part is knowing what the table needs to answer, how the keys should be shaped, and where the edges of the system should be.
Design from the access patterns, keep indexes intentional, monitor real traffic, and treat cost as part of the architecture. That is where DynamoDB starts to pay off.
Need help designing, migrating, or optimising a DynamoDB workload? Contact Eight Mile.