At 5000 requests per second, PostgreSQL starts to lag: N+1 queries, locks, replica lag. Migrating to DynamoDB solves this — latency drops from 50ms to 5ms, a 10x improvement, and infrastructure costs decrease by 40% (saving $1,200 per month in one client case) by eliminating read replicas. But improper DynamoDB configuration leads to hot partitions and throttling. We set up DynamoDB end-to-end: from single-table schema design to infrastructure deployment via AWS CDK. Our experience: 5+ years on AWS, over 50 projects with high-load web applications. We follow the AWS Well-Architected Framework best practices. Typical DynamoDB setup cost ranges from $2,500 to $5,000 depending on complexity. Get a project estimate for your turnkey DynamoDB setup.
DynamoDB is a managed NoSQL database from AWS with guaranteed latency <10ms at any scale and 99.999% availability SLA. No servers to maintain, no manual sharding. Ideal for serverless architectures and applications with peak loads. According to statistics, 90% of applications with loads >1000 requests/sec benefit from migrating to DynamoDB. Our DynamoDB optimization techniques reduce IAM policy count by 60% and query latency under 10ms for 99% of requests.
What Problems Does DynamoDB Solve in Web Applications?
Typical problems with relational databases under high load: N+1 queries, complex JOINs, replica lag. DynamoDB eliminates these through denormalization and single-table design. However, poor design leads to hot partitions, RCU/WCU exhaustion, and high costs. We solve these problems at the design stage. For example, in one project we migrated from PostgreSQL to DynamoDB, reducing latency from 50ms to 5ms (10x faster) and cutting infrastructure costs by 40% by removing read replicas. This allowed handling peak loads up to 10,000 requests/sec without throttling. Single-table design is 3x easier to manage than multi-table with complex joins.
Why Single-Table Design Is the Standard for DynamoDB?
Single-table design — one table for all entities with overloaded PK/SK. This NoSQL design approach for serverless databases defines all access patterns before table creation. For an e-commerce store, typical patterns: get user by email, list user orders by date, order details with items, products by category. Example schema:
Entity: User PK: USER#<userId> SK: METADATA GSI1PK: EMAIL#<email> GSI1SK: USER#<userId> Entity: Order PK: USER#<userId> SK: ORDER#<orderId> GSI1PK: ORDER#<orderId> GSI1SK: USER#<userId> Entity: OrderItem PK: ORDER#<orderId> SK: ITEM#<itemId> Entity: Product PK: PRODUCT#<productId> SK: METADATA GSI1PK: CATEGORY#<cat> GSI1SK: PRODUCT#<productId> This approach allows all queries to hit a single table using GSIs. Multi-table approach increases the number of tables, complicates IAM DynamoDB policies, and increases latency. LSI (local secondary indexes) are useful for alternative sort keys within the same PK.
According to the AWS Well-Architected Framework, using single-table design and GSI is a best practice for DynamoDB.
Comparison of On-Demand and Provisioned Modes
| Characteristic | On-Demand | Provisioned |
|---|---|---|
| Write cost | ~1.5–2x more expensive ($0.75 per WCU vs $0.50) | Cheaper |
| Flexibility | Auto-scaling | Manual auto scaling config |
| Peak loads | No limits | Risk of throttling |
| Recommendation | Unpredictable loads | Stable loads |
On-Demand is better for variable loads but 1.5–2x more expensive on stable workloads.
Infrastructure via AWS CDK (TypeScript)
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb' import { RemovalPolicy } from 'aws-cdk-lib' const table = new dynamodb.Table(this, 'AppTable', { tableName: 'MyApp', partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, // or PROVISIONED + auto scaling pointInTimeRecovery: true, deletionProtection: true, removalPolicy: RemovalPolicy.RETAIN, stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES, }) table.addGlobalSecondaryIndex({ indexName: 'GSI1', partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING }, projectionType: dynamodb.ProjectionType.ALL, }) Implementing Repositories with AWS SDK v3 DynamoDB
export class UserRepository { async create(user: CreateUserInput): Promise<User> { const id = crypto.randomUUID() const now = new Date().toISOString() await db.send(new TransactWriteCommand({ TransactItems: [{ Put: { TableName: TABLE, Item: { PK: `USER#${id}`, SK: 'METADATA', GSI1PK: `EMAIL#${user.email}`, GSI1SK: `USER#${id}`, id, email: user.email, name: user.name, passwordHash: user.passwordHash, role: 'user', createdAt: now, updatedAt: now, _type: 'User' }, ConditionExpression: 'attribute_not_exists(PK)' } }] })) return { id, ...user, role: 'user', createdAt: now, updatedAt: now } } async findByEmail(email: string): Promise<User | null> { const result = await db.send(new QueryCommand({ TableName: TABLE, IndexName: 'GSI1', KeyConditionExpression: 'GSI1PK = :pk', ExpressionAttributeValues: { ':pk': `EMAIL#${email}` }, Limit: 1 })) return (result.Items?.[0] as User) ?? null } async findById(id: string): Promise<User | null> { const result = await db.send(new GetCommand({ TableName: TABLE, Key: { PK: `USER#${id}`, SK: 'METADATA' } })) return (result.Item as User) ?? null } } How to Configure DynamoDB Streams and Lambda Triggers?
Streams allow reacting to data changes in real time — 10x faster than polling for database changes. A typical scenario is updating a search index or sending notifications. Example Lambda handler:
export const handler = async (event: DynamoDBStreamEvent) => { for (const record of event.Records) { if (record.eventName !== 'MODIFY') continue const newImage = unmarshall(record.dynamodb!.NewImage!) const oldImage = unmarshall(record.dynamodb!.OldImage!) if (newImage._type === 'Order' && newImage.status !== oldImage.status) { await notifyOrderStatusChange(newImage.id, newImage.status) } } } How to Monitor DynamoDB and Set Up Alerts?
Key metrics: ConsumedReadCapacityUnits, ConsumedWriteCapacityUnits, SuccessfulRequestLatency, SystemErrors, ThrottledRequests. Set an immediate alert on ThrottledRequests > 0. Use CloudWatch dashboards for DynamoDB monitoring. We recommend setting alerts for exceeding 80% of provisioned capacity.
DynamoDB Setup Process
- Analyze application access patterns
- Design single-table schema
- Create infrastructure via CDK
- Implement repositories using AWS SDK v3 DynamoDB
- Configure Streams and Lambda triggers
- Set up monitoring and alerts
Deliverables
| Component | Description |
|---|---|
| Schema design | Define PK/SK, GSI, LSI |
| Infrastructure | CDK stack with table, Streams, IAM |
| Repository code | TypeScript/Node.js with AWS SDK v3 |
| Integration | Lambda triggers, API Gateway |
| Monitoring | CloudWatch dashboard, alerts |
| Documentation | Schema description, access patterns, instructions |
| Access | AWS console and repository access |
| Handover | 1-hour training session |
| Support | 1 month post-launch support |
Timelines and Cost
Design and basic integration: 3–5 days (turnkey delivery). Adding Streams, Lambda, monitoring: another 3–5 days. Migration from a relational database: 2–4 weeks depending on volume. Pricing is calculated individually, depends on schema complexity and integration scope. Contact us for a project estimate — we'll estimate complexity and timelines. Typical DynamoDB setup cost ranges from $2,500 to $5,000. Request a preliminary assessment to understand how much time and resources will be required.







