41 DynamoDB Best Practices for Design and Production

Written by Rafal Wilinski
Last updated: October 23rd, 2026
DynamoDB best practices start with access patterns: design keys for the requests your application actually makes, spread traffic across those keys, and keep items small. Use on-demand capacity as the starting point for most workloads, then measure before choosing provisioned capacity or commitment discounts. The checklist below covers application code, data modeling, security, recovery and cost.
1. Use the SDK v3 document client for JavaScript objects
Use DynamoDBDocumentClient from @aws-sdk/lib-dynamodb for native JavaScript values. The low-level client remains useful when you need DynamoDB's explicit type descriptors. AWS SDK for JavaScript v2 reached end of support on September 8, 2025; new code should not use AWS.DynamoDB.DocumentClient.
2. Store large binary assets in S3
Keep images, documents and other large objects in S3, with an object key and useful metadata in DynamoDB. Items have a fixed 400 KB limit, including attribute names and values. Plan cleanup for failed uploads and deleted records: a DynamoDB transaction cannot atomically commit an S3 write.
3. Use async/await and handle failures explicitly
SDK v3's client.send(command) returns a Promise. Await it and handle errors at a boundary that can decide whether to retry, reject the request or report a failure. The v2 .promise() suffix does not belong on v3 calls. Bound concurrency when processing many independent items.
4. Batch reads when you already know the keys
BatchGetItem retrieves up to 100 items across tables, with a 16 MB response limit. It is a key lookup, not a Query replacement. Responses can be partial and unordered: include primary keys in projections, associate results by key, and retry UnprocessedKeys with backoff rather than assuming every requested item arrived.
5. Project only the attributes the caller needs
Use ProjectionExpression rather than the legacy AttributesToGet parameter. Smaller responses reduce network transfer and decoding work, but projecting a base-table read does not reduce its read capacity charge. To reduce capacity consumption, change the items or index entries being read, rather than only the response shape.
6. Decide how to handle empty and undefined values
Empty strings and binary values are valid for non-key attributes. Empty sets and empty table or index keys are invalid. Do not enable convertEmptyValues to convert arbitrary falsy values: false and 0 are meaningful values. In v3, explicitly set marshallOptions.removeUndefinedValues if omission is the intended behavior, as the document-client migration guide explains.
7. Batch puts and deletes, with retries
BatchWriteItem accepts up to 25 put/delete operations and a 16 MB request; each stored item must still fit 400 KB. It supports neither updates nor per-item conditions. Check UnprocessedItems and retry only those operations. Individual writes are atomic, but the batch is not an all-or-nothing transaction.
8. Grant only the operations each workload needs
Scope IAM permissions to the tables, indexes and actions a service uses. A request handler that only needs GetItem and Query need not receive Scan or table-deletion permissions. Resource-based policies on tables and streams can support cross-account access; review them alongside identity policies and organizational controls.
9. Use Streams for asynchronous side effects
Use DynamoDB Streams for notifications, search projections and downstream processing after a successful write. Validate requirements that must hold before the write in the request path; a stream consumer cannot prevent an invalid item from first being stored. Consumers must tolerate repeated delivery, and their failure handling must keep errors from silently stalling processing. See DynamoDB Streams.
10. Treat filters as response filtering
A FilterExpression runs after DynamoDB reads items. It reduces returned results, not the read capacity already consumed. If a frequent request discards most items, model an index for that request. An empty filtered page can still have LastEvaluatedKey, so paginate until the continuation key is absent.
11. Bound parallel scans
Parallel Scan can shorten a maintenance job by reading segments concurrently. It can also consume capacity rapidly and interfere with application traffic. Limit worker concurrency, checkpoint each segment independently and monitor throttling. For full-table analytics, consider an export instead of reading the production table repeatedly.
12. Reuse SDK clients and connections
Create clients outside hot request loops and, in Lambda, outside the handler when practical. SDK v3 reuses TCP connections by default. The old AWS_NODEJS_CONNECTION_REUSE_ENABLED=1 recommendation was for SDK v2; it is not a prerequisite for v3 connection reuse.
13. Use transactions for invariants across items
TransactWriteItems supports up to 100 unique items and 4 MB of aggregate item data, within one account and Region. Use it when an order and its inventory reservation must succeed together. Transactions cost more capacity than ordinary writes; prefer a conditional single-item update when that is sufficient. MRSC global tables do not support transactions.
14. Diagnose throttling before increasing capacity
Inspect ThrottlingReasons, including the affected resource ARN. A reason such as IndexWriteKeyRangeThroughputExceeded points to a hot index key range; raising the base table's capacity alone may not help. Use the matching CloudWatch metrics, such as WriteKeyRangeThroughputThrottleEvents and WriteMaxOnDemandThroughputThrottleEvents. Contributor Insights' throttled-keys mode helps identify affected keys without continuously collecting every accessed key.
15. Choose the appropriate VPC endpoint
A DynamoDB gateway endpoint connects supported VPC traffic without a NAT gateway and has no endpoint charge. Interface endpoints use AWS PrivateLink and support additional network arrangements, including access from on-premises networks, with separate charges. DynamoDB Streams uses interface endpoints, not gateway endpoints. Endpoint policies supplement IAM permissions.
16. Alias attribute names in expressions
Use ExpressionAttributeNames for reserved words and names containing characters that expression syntax treats specially. For example, map #status to status, then write #status = :status. Generate aliases consistently in query-building code so a newly introduced attribute name does not unexpectedly break an otherwise valid request.
17. Cache where the freshness contract allows it
A cache helps when requests repeatedly read the same data and stale results are acceptable for a defined period. Decide invalidation behavior before adding DAX or an application cache. Measure hit rate, latency and total cost; there is no universal percentage saving. Strongly consistent reads and highly variable keys may not benefit from the same strategy.
18. Start most workloads with on-demand capacity
On-demand is the default recommendation for most DynamoDB workloads, especially when demand is unknown or variable. The November 2024 price reduction changed older cost comparisons substantially. It removes capacity scheduling, but does not remove quotas, hot keys or the need to plan for sudden large traffic increases.
19. Switch to provisioned only after comparing measured costs
Steady, well-utilized workloads can cost less with provisioned capacity and auto scaling. Include unused capacity, scaling delays, peaks and engineering effort in the comparison. Staying on-demand can be the correct long-term choice. Compare Region-specific costs with the DynamoDB pricing calculator rather than assuming every mature table should switch.
20. Choose global-table consistency deliberately
Use global tables when regional copies solve latency or availability requirements. MREC asynchronously replicates writes and resolves concurrent conflicts with last-writer-wins behavior. MRSC provides strongly consistent reads across replica Regions, with different latency and feature constraints, including no TTL, transactions or LSIs. Read the global tables guide before choosing the mode; it cannot be changed afterward.
21. Define timestamps and deletion semantics
Application-managed createdAt and updatedAt fields can help with troubleshooting and synchronization. A deletedAt field supports soft deletion only if every relevant reader excludes deleted items. It does not create a complete audit history, prevent overwrites or reduce storage by itself. Define when the application permanently removes those records.
22. Maintain aggregates for known questions
For a frequently requested order total, maintain a summary item instead of repeatedly scanning all orders. A Streams consumer can update derived totals asynchronously, but must handle duplicate events and failed processing. Use a transaction or conditional write when the aggregate must be correct at commit time; use an analytics system for open-ended questions.
23. Add write shards only with a read plan
A heavily written logical key can be split into suffixes such as EVENT#2026-10-23#03. Random suffixes distribute writes but require fan-out reads; calculated suffixes can make lookup predictable. Choose the shard strategy from expected traffic and document how readers merge results. Sharding adds application work, so first confirm the actual hot key.
24. Compress large attributes selectively
Compression can keep a payload below the item-size ceiling and reduce billed bytes. It also adds CPU work and makes the compressed content unavailable to normal attribute filters or indexes. Keep searchable fields outside the compressed binary attribute. Measure the resulting item size and reserve room for future metadata; compression is not a guarantee that every input will fit.
25. Use consistent timestamp formats
Use a Number containing Unix epoch seconds for TTL. Other dates can use numbers or normalized ISO 8601 strings. For lexicographic date ordering, keep timezone and precision consistent; mixed offsets or formats can sort incorrectly. A separate TTL field avoids confusing a human-readable timestamp with the expiration format DynamoDB requires.
26. Test locally, then test service-specific behavior
DynamoDB Local is useful for CRUD, expressions and data-model tests without cloud calls. It does not reproduce every production behavior: quotas, throttling, global replication, PITR and native vector search need service-level validation. Keep a small AWS integration suite for the features your application depends on.
27. Enable PITR and deletion protection
PITR keeps a configurable 1 to 35 days of recovery points, with 35 days as the default. A restore creates a new table, so rehearse application cutover and restore table settings afterward. Deletion protection guards against deleting the table, not against deleting its items. Configure both explicitly, including on replicas; the PITR guide covers recovery behavior.
28. Use PartiQL without assuming relational behavior
PartiQL offers SQL-compatible syntax for DynamoDB operations, but it does not introduce joins or a relational query optimizer. A statement without a suitable key predicate can still scan a table. Review the access path and consumed capacity just as you would for a native Query or Scan request.
29. Move broad analytics off the request path
Export data to S3 for Athena or another analytical engine instead of scanning the live table for every report. Exports require PITR and do not consume table read capacity, but export and destination charges apply. Managed zero-ETL integrations with Redshift and SageMaker Lakehouse provide alternatives; account for their refresh delay rather than treating them as synchronous views.
30. Use TTL for cleanup, not precise deadlines
TTL normally deletes eligible items within a few days. Enforce expiration in application reads and authorization checks because expired items can remain present. The original TTL deletion consumes no write capacity; MREC replicas incur replicated-delete charges. MRSC does not support TTL. See TTL setup and timing.
31. Prefer key-based reads for application requests
Use GetItem for a complete primary key and Query for a partition key with optional sort-key conditions. Reserve Scan for work that genuinely needs to inspect a table or index. A filter cannot turn a Scan into an efficient key lookup. Include pagination and result-size expectations in every access-pattern specification.
32. Distribute traffic, not just stored items
A table can contain millions of distinct keys while nearly all requests target one popular item. A physical partition supports up to 3,000 read units and 1,000 write units per second; item size and consistency determine requests per unit. Adaptive capacity helps, but cannot make a single hot item support unlimited traffic. Inspect GSI key distributions too.
33. Make item types explicit in single-table designs
An entityType attribute makes mixed records easier to validate, debug and evolve. It does not itself make queries more efficient: the keys and indexes still determine what DynamoDB reads. Document required fields and version changes for each entity type, especially when multiple services write to the same table.
34. Design indexes for specific access patterns
GSIs provide alternative keys and eventually consistent reads. LSIs share the table's partition key, can support strong reads, and must be created with the table. Multi-attribute GSIs accept up to four partition-key and four sort-key attributes; all partition components require equality, and sort conditions work left to right. They reduce synthetic-key code without changing the base table's key schema.
35. Name indexes for maintainable code
Generic index names can suit an overloaded single-table design; descriptive names can clarify a dedicated access pattern. Neither choice removes schema migrations. Keep index names behind a data-access layer, document key meanings and review projected attributes. Every additional GSI adds storage and write work, and a throttled GSI can throttle base-table writes.
36. Enforce tenant boundaries with IAM and keys
Where supported by the operation, dynamodb:LeadingKeys can restrict access to allowed partition-key values. Design tenant-prefixed keys consistently and test every permitted operation and index. Do not grant an operation such as a broad Scan and assume a leading-key condition will turn it into a tenant-specific query.
37. Split oversized entities into related items
Store independently read or updated parts under a shared partition key and distinct sort keys, such as PROFILE and ADDRESS#HOME. Each item must still fit 400 KB. Splitting changes atomicity and read cost; use transactions only for the pieces that must change together. Large attachments usually belong in S3.
38. Use sort-key prefixes for hierarchical reads
Within an appropriate partition, a sort key such as COMPANY#A#DEPT#SALES#TEAM#WEST supports begins_with queries at successive hierarchy levels. Keep delimiters and escaping consistent. This pattern does not query across arbitrary partition keys, and the prefix order determines which hierarchical requests it can answer efficiently.
39. Put conditions on the write itself
Use the singular API parameter ConditionExpression to make the decision and write atomic. For example, attribute_not_exists(pk) prevents overwriting an existing item, while a version condition implements optimistic concurrency. Reading first and then writing without a condition leaves a race in which another writer can change the item between calls.
40. Use an inverted index for reverse relationships
A GSI can swap the base partition and sort keys to answer the other direction of a relationship, such as users belonging to a team and teams containing a user. Verify that the reversed keys have adequate distribution. Reverse lookups through a GSI are eventually consistent, even if the original write used a transaction.
41. Set cost controls and prepare for peaks
Set on-demand maximum throughput when throttling excess traffic is preferable to unbounded request growth. These are best-effort throughput targets, not hard dollar budgets; add billing alerts. Before a scheduled launch, inspect warm throughput and pre-warm the table and relevant indexes if needed, accounting for the one-time charge.
Compare Standard-IA when storage dominates costs; it lowers storage prices while raising read and write prices. Reserved capacity applies to eligible provisioned Standard capacity. Database Savings Plans offer a separate commitment option for eligible on-demand and provisioned throughput, with reserved capacity applied first rather than stacking both discounts on the same usage. Revisit the current AWS pricing terms before committing.
Frequently Asked Questions
What are the most important DynamoDB best practices?
Should I always switch from on-demand to provisioned capacity?
Do filters reduce DynamoDB read costs?
Can DynamoDB scale without good partition keys?
Dynobase is a Professional GUI Client for DynamoDB
Start your 7-day free trial today