DynamoDB via SDK Best Practices
This is the checklist to run before and after building on DynamoDB from the SDK. Each item is a rule stated positively with a one-line rationale.
Busque em todas as páginas da documentação
This is the checklist to run before and after building on DynamoDB from the SDK. Each item is a rule stated positively with a one-line rationale.
Work top to bottom - the groups run roughly in dependency order, from key design outward to capacity and cost. Most rules apply whether you provision from the SDK or from IaC.
USER# and ORDER# keep item types distinct, sortable, and self-describing in a shared table.Model keys around the query you will run most.
# --- Python (boto3) ---
from boto3.dynamodb.conditions import Key
# Access pattern "orders for a customer" -> Query on the partition key.
resp = table.query(KeyConditionExpression=Key("pk").eq("CUSTOMER#c-1"))// --- TypeScript (AWS SDK v3) ---
import { QueryCommand } from "@aws-sdk/lib-dynamodb";
// Access pattern "orders for a customer" -> Query on the partition key.
const resp = await doc.send(new QueryCommand({
TableName: "App",
KeyConditionExpression: "pk = :p",
ExpressionAttributeValues: { ":p": "CUSTOMER#c-1" },
}));FilterExpression runs after the read and still bills for scanned items; a KeyConditionExpression does not.ExclusiveStartKey until it is absent, or you silently drop data.ProjectionExpression trims payload; for large items it saves real bandwidth.ConsistentRead for read-after-write correctness.boto3.resource / lib-dynamodb beat hand-writing {"S": ...} typed values.attribute_not_exists for safe inserts and a version check for optimistic locking prevent lost updates.TransactWriteItems costs about double - use it only when several items must change together.BatchWriteItem (or boto3's batch_writer) cuts round trips; resend UnprocessedItems.ClientRequestToken on transactions and idempotent upserts so retries do not double-apply.Guard an insert so a retry or race never overwrites existing data.
# --- Python (boto3) ---
# Insert-only: fails with ConditionalCheckFailedException if it already exists.
table.put_item(
Item={"pk": "USER#u-1", "sk": "PROFILE", "email": "ada@x.io"},
ConditionExpression="attribute_not_exists(pk)",
)// --- TypeScript (AWS SDK v3) ---
import { PutCommand } from "@aws-sdk/lib-dynamodb";
// Insert-only: fails with ConditionalCheckFailedException if it already exists.
await doc.send(new PutCommand({
TableName: "App",
Item: { pk: "USER#u-1", sk: "PROFILE", email: "ada@x.io" },
ConditionExpression: "attribute_not_exists(pk)",
}));expiresAt attribute and let DynamoDB delete expired items for free instead of scanning to clean up.USER#u-1#2026) before it nears the 10 GB partition limit.Turn on TTL so expired sessions clean themselves up.
# --- Python (boto3) ---
import boto3, time
ddb = boto3.client("dynamodb", region_name="us-east-1")
# 1. Enable TTL on the 'expiresAt' attribute (Unix epoch seconds).
ddb.update_time_to_live(
TableName="Sessions",
TimeToLiveSpecification={"Enabled": True, "AttributeName": "expiresAt"},
)
# 2. Write an item that expires in one hour.
boto3.resource("dynamodb").Table("Sessions").put_item(
Item={"sid": "s-1", "expiresAt": int(time.time()) + 3600},
)// --- TypeScript (AWS SDK v3) ---
import { DynamoDBClient, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
const base = new DynamoDBClient({ region: "us-east-1" });
const doc = DynamoDBDocumentClient.from(base);
// 1. Enable TTL on the 'expiresAt' attribute (Unix epoch seconds).
await base.send(new UpdateTimeToLiveCommand({
TableName: "Sessions",
TimeToLiveSpecification: { Enabled: true, AttributeName: "expiresAt" },
}));
// 2. Write an item that expires in one hour.
await doc.send(new PutCommand({
TableName: "Sessions",
Item: { sid: "s-1", expiresAt: Math.floor(Date.now() / 1000) + 3600 },
}));PAY_PER_REQUEST needs no tuning and costs nothing idle; switch to provisioned once traffic is steady.Min/Max, and scale each GSI too.ProvisionedThroughputExceededException is retried with backoff by the SDK; persistent throttling means raise capacity.RequestId / $metadata.requestId so AWS support can trace a call.Designing from access patterns first. List the exact reads and writes with their runtime inputs, then choose keys and indexes so each becomes a Query on a single partition. Almost every other best practice follows from this.
Pick a high-cardinality partition key so load spreads across many partitions. If a low-cardinality key is unavoidable, add a shard suffix and fan reads across the shards. Monitor throttling to catch hotspots early.
Query reads only items under one partition key and stays cheap at any size. Scan reads the entire table and filters afterward, so its cost grows with the data. A Scan on a request path signals a modeling problem.
No. Filters run after DynamoDB reads and meters the items, so they trim what is returned but not what you pay for. Narrow with a KeyConditionExpression or a GSI to actually reduce cost.
Set a Unix-epoch timestamp attribute and enable TTL on it. DynamoDB deletes expired items automatically at no capacity cost, replacing a scan-and-delete job. Deletion is asynchronous, so filter out expired items in queries if needed.
Only when several items must change atomically - a transfer, a uniqueness constraint across items, an invariant spanning entities. They cost about double the write capacity, so a single conditional write is preferable when it suffices.
Start with on-demand for new or unpredictable traffic - no tuning, nothing when idle. Move to provisioned with auto scaling once metrics show steady, forecastable load, where reserved capacity is meaningfully cheaper.
Attach condition expressions: attribute_not_exists for safe inserts, and a version-number check for optimistic locking. For multi-item invariants use TransactWriteItems, and add a ClientRequestToken to make retries idempotent.
Run integration tests against DynamoDB Local (Docker or JAR) for real API behavior, and use moto or client mocks for fast unit tests. Both run offline and free, so CI never needs a live, billed table.
Mostly yes. Access-pattern-first design, sparse GSIs, TTL, auto scaling, tagging, and rate-based alarms are architectural habits that hold whether you create the table from the SDK or from CloudFormation, CDK, or Terraform.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Revisado por Chris St. John·Última atualização: 23 de jul. de 2026