Amazon DynamoDB is a fully managed, serverless NoSQL key-value and document database. It delivers single-digit millisecond latency at any scale, with built-in horizontal sharding, multi-AZ replication, and optional global replication — and no servers, patches, or storage capacity to manage.
BatchGetItem, BatchWriteItem) and Query over Scan.LastEvaluatedKey to paginate).
import boto3
from boto3.dynamodb.conditions import Key
table = boto3.resource("dynamodb", region_name="us-west-2").Table("Orders")
table.put_item(Item={
"pk": "CUSTOMER#1042",
"sk": "ORDER#2026-04-21#A-482",
"total": 129.95,
"status": "SHIPPED",
})
resp = table.query(
KeyConditionExpression=Key("pk").eq("CUSTOMER#1042") & Key("sk").begins_with("ORDER#2026-04"),
)
for item in resp["Items"]:
print(item["sk"], item["status"], item["total"])
client = boto3.client("dynamodb", region_name="us-west-2")
client.transact_write_items(TransactItems=[
{"Update": {
"TableName": "Accounts",
"Key": {"pk": {"S": "ACCT#A"}},
"UpdateExpression": "SET balance = balance - :amt",
"ConditionExpression": "balance >= :amt",
"ExpressionAttributeValues": {":amt": {"N": "100"}},
}},
{"Update": {
"TableName": "Accounts",
"Key": {"pk": {"S": "ACCT#B"}},
"UpdateExpression": "SET balance = balance + :amt",
"ExpressionAttributeValues": {":amt": {"N": "100"}},
}},
])
LSI (Local Secondary Index) shares the partition key with the base table but has a different sort key, must be defined at table creation, and shares throughput with the base table — limited to 10 GB per partition key. GSI (Global Secondary Index) has independent partition and sort keys, can be added or dropped anytime, has its own throughput, and is eventually consistent. Use GSIs almost always.
Choose high-cardinality partition keys (user ID, not country code), avoid time-only keys for writes (shard with random suffix or hash prefix), and rely on adaptive capacity to handle short bursts. For genuinely uneven access patterns, write-shard the key (USER#123#0..15) and fan-out reads.
When you have known, bounded access patterns and want one network round-trip per query. Multiple entity types share one table with composite keys (pk: USER#123, sk: ORDER#...) so a single Query returns related items. Adds modeling complexity; not worth it for simple CRUD.
On-demand: zero capacity planning, instant scaling, pay per request — best for unpredictable, spiky, or new workloads. Provisioned: cheaper at steady traffic, supports reserved capacity discounts, requires auto-scaling configuration. Switch from on-demand to provisioned once traffic is predictable.
Ordered change log per partition of all item writes (24-hour retention). Consumed by Lambda for event-driven processing — denormalize into search indexes, fan out to other services, or trigger workflows. Each Stream record contains old and new images of the item.
Last-writer-wins based on the latest write timestamp at the originating region. There's no application-level conflict resolution hook. Design partition keys so concurrent writes to the same item from multiple regions are rare (e.g., region-affinitized customers).
For read-heavy workloads where p99 latency matters more than cost (microseconds vs. single-digit milliseconds), and where the working set fits in DAX cluster memory. DAX is a write-through cache; writes go to DynamoDB first, then update the cache. Doesn't help write-heavy workloads.
DynamoDB is the default choice on AWS when you need predictable performance at any scale and your access patterns can be modeled as key-value or document lookups.