Lesson 10 of 21
DynamoDB Basics for Developers
Learn DynamoDB fundamentals — tables, primary keys, queries, scans, GSIs, single-table design, and using boto3 in Python.
What you'll learn
- ✓DynamoDB data model: tables, items, attributes, primary keys
- ✓Query vs Scan and when to use each
- ✓Global and Local Secondary Indexes
- ✓CRUD operations with boto3
Prerequisites
- •Basic understanding of databases
- •Python basics for the boto3 examples
DynamoDB is AWS’s fully managed NoSQL database. It handles scaling, replication, and backups automatically. You get single-digit millisecond latency at any scale — but you must design your access patterns upfront.
Core concepts
- Table: a collection of items (like a SQL table)
- Item: a single record (like a row), stored as JSON
- Attribute: a field on an item (like a column)
- Primary key: uniquely identifies each item
Primary key types
Partition key only: a single attribute that DynamoDB hashes to determine storage location.
Table: Users
PK: userId
Partition key + sort key (composite): combines two attributes. Items with the same partition key are stored together and sorted by the sort key.
Table: Orders
PK: customerId
SK: orderDate#orderId
Creating a table with boto3
import boto3
dynamodb = boto3.resource("dynamodb")
table = dynamodb.create_table(
TableName="Users",
KeySchema=[
{"AttributeName": "userId", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "userId", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
table.wait_until_exists()
CRUD operations
Put (create/replace)
table = dynamodb.Table("Users")
table.put_item(Item={
"userId": "user-001",
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"tags": ["admin", "active"],
})
Get
response = table.get_item(Key={"userId": "user-001"})
item = response.get("Item")
Update
table.update_item(
Key={"userId": "user-001"},
UpdateExpression="SET age = :age, #n = :name",
ExpressionAttributeNames={"#n": "name"},
ExpressionAttributeValues={":age": 31, ":name": "Alice Smith"},
)
Delete
table.delete_item(Key={"userId": "user-001"})
Query vs Scan
Query fetches items by partition key (and optionally filters by sort key). It is fast and efficient.
from boto3.dynamodb.conditions import Key
response = table.query(
KeyConditionExpression=Key("customerId").eq("cust-001")
& Key("orderDate").begins_with("2026-06")
)
items = response["Items"]
Scan reads every item in the table. It is slow and expensive — avoid in production.
response = table.scan(
FilterExpression=Key("age").gt(25)
)
Global Secondary Indexes (GSI)
A GSI lets you query by a different attribute than the primary key.
table = dynamodb.create_table(
TableName="Orders",
KeySchema=[
{"AttributeName": "orderId", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "orderId", "AttributeType": "S"},
{"AttributeName": "customerId", "AttributeType": "S"},
],
GlobalSecondaryIndexes=[{
"IndexName": "CustomerIndex",
"KeySchema": [
{"AttributeName": "customerId", "KeyType": "HASH"},
],
"Projection": {"ProjectionType": "ALL"},
}],
BillingMode="PAY_PER_REQUEST",
)
Query the GSI:
response = table.query(
IndexName="CustomerIndex",
KeyConditionExpression=Key("customerId").eq("cust-001")
)
Batch operations
with table.batch_writer() as batch:
for i in range(100):
batch.put_item(Item={
"userId": f"user-{i:03d}",
"name": f"User {i}",
})
Conditional writes
table.put_item(
Item={"userId": "user-001", "name": "Alice", "version": 1},
ConditionExpression="attribute_not_exists(userId)",
)
This fails if the item already exists — useful for preventing overwrites.
Summary
DynamoDB gives you predictable performance at any scale, but demands that you design your keys and indexes around your access patterns. Use composite keys for one-to-many relationships, GSIs for alternate query patterns, and avoid scans in production. Start with PAY_PER_REQUEST billing and switch to provisioned capacity when your patterns stabilize.
Progress is saved locally to your browser.