Prerequisites: Nên đọc RDS & Aurora trước để hiểu sự khác biệt SQL vs NoSQL.
DynamoDB là gì?
DynamoDB là fully managed NoSQL database của AWS với khả năng scale không giới hạn và latency cực thấp (single-digit milliseconds).
Khi nào dùng DynamoDB?
| Use case | DynamoDB | RDS/Aurora |
|---|---|---|
| Gaming leaderboards | ✅ Tốt nhất | ❌ Chậm |
| Session storage | ✅ Tốt nhất | ⚠️ Được |
| IoT data (millions writes/sec) | ✅ Tốt nhất | ❌ Không scale |
| Complex SQL queries | ❌ Không hỗ trợ | ✅ Tốt nhất |
| Transactions across tables | ⚠️ Hạn chế | ✅ Tốt nhất |
| Unknown query patterns | ❌ Cần biết trước | ✅ Linh hoạt |
Quy tắc đơn giản:
- DynamoDB: Biết trước access patterns, cần scale lớn, cần low latency
- RDS/Aurora: Query phức tạp, joins, transactions, không biết trước access patterns
Các khái niệm cốt lõi
1. Tables, Items, Attributes
DynamoDB Table: Users
┌──────────────────────────────────────────────────────────┐
│ Partition Key │ Sort Key │ Attributes... │
├───────────────┼───────────┼──────────────────────────────┤
│ user_123 │ profile │ {"name": "An", "age": 25} │ ← Item
│ user_123 │ settings │ {"theme": "dark", ...} │ ← Item
│ user_456 │ profile │ {"name": "Binh", "age": 30} │ ← Item
└──────────────────────────────────────────────────────────┘
- Table: Tập hợp các items (tương tự table trong SQL)
- Item: Một record (tương tự row trong SQL) - tối đa 400KB
- Attributes: Fields trong item (có thể khác nhau giữa các items!)
2. Primary Key - Quan trọng nhất!
DynamoDB có 2 loại primary key:
A. Partition Key only (Simple Primary Key)
Table: Products
Partition Key: product_id
┌─────────────┬──────────────────────────────┐
│ product_id │ Attributes │
├─────────────┼──────────────────────────────┤
│ PROD-001 │ {"name": "iPhone", ...} │
│ PROD-002 │ {"name": "MacBook", ...} │
└─────────────┴──────────────────────────────┘
Mỗi item có partition key UNIQUE
B. Partition Key + Sort Key (Composite Primary Key)
Table: Orders
Partition Key: customer_id
Sort Key: order_date
┌──────────────┬─────────────┬─────────────────────────┐
│ customer_id │ order_date │ Attributes │
├──────────────┼─────────────┼─────────────────────────┤
│ CUST-001 │ 2025-01-01 │ {"total": 100, ...} │
│ CUST-001 │ 2025-01-15 │ {"total": 250, ...} │
│ CUST-001 │ 2025-02-01 │ {"total": 75, ...} │
│ CUST-002 │ 2025-01-10 │ {"total": 500, ...} │
└──────────────┴─────────────┴─────────────────────────┘
Partition key + Sort key = UNIQUE
Có thể query tất cả orders của 1 customer (sorted by date!)
💡 Tip: Partition key quyết định data phân bố như thế nào. Chọn partition key có high cardinality (nhiều giá trị khác nhau) để tránh “hot partitions”.
Capacity Modes
DynamoDB có 2 capacity modes:
1. Provisioned Mode (Rẻ hơn, cần dự đoán)
Bạn chỉ định trước Read/Write Capacity Units (RCU/WCU):
1 RCU = 1 strongly consistent read/sec for item ≤ 4KB
= 2 eventually consistent reads/sec for item ≤ 4KB
1 WCU = 1 write/sec for item ≤ 1KB
Ví dụ tính toán:
- Item size: 8KB
- 100 strongly consistent reads/sec cần: 100 × (8/4) = 200 RCU
- 50 writes/sec cần: 50 × (8/1) = 400 WCU
2. On-Demand Mode (Đắt hơn, không cần dự đoán)
- Trả theo request
- Tự động scale
- Tốt cho workloads unpredictable
Terraform
# Provisioned mode
resource "aws_dynamodb_table" "orders" {
name = "${var.project_name}-orders"
billing_mode = "PROVISIONED"
read_capacity = 20
write_capacity = 10
hash_key = "customer_id"
range_key = "order_date"
attribute {
name = "customer_id"
type = "S"
}
attribute {
name = "order_date"
type = "S"
}
# Auto scaling (khuyến nghị cho production)
# Xem phần dưới
}
# On-demand mode
resource "aws_dynamodb_table" "sessions" {
name = "${var.project_name}-sessions"
billing_mode = "PAY_PER_REQUEST"
hash_key = "session_id"
attribute {
name = "session_id"
type = "S"
}
ttl {
attribute_name = "expires_at"
enabled = true
}
}
Secondary Indexes (GSI và LSI)
Indexes cho phép query data theo các attributes khác ngoài primary key.
Global Secondary Index (GSI)
- Partition key và sort key khác với table chính
- Có thể tạo bất cứ lúc nào
- Có RCU/WCU riêng
- Eventually consistent only
resource "aws_dynamodb_table" "orders" {
# ... table config ...
# GSI: Query orders by product
global_secondary_index {
name = "ProductIndex"
hash_key = "product_id"
range_key = "order_date"
projection_type = "ALL"
read_capacity = 10
write_capacity = 5
}
attribute {
name = "product_id"
type = "S"
}
}
Original table:
PK: customer_id, SK: order_date
GSI: ProductIndex
PK: product_id, SK: order_date
Query trên GSI:
"Lấy tất cả orders của product PROD-001 trong tháng 1"
Local Secondary Index (LSI)
- Same partition key với table chính, khác sort key
- Phải tạo khi tạo table (không thêm sau được!)
- Share RCU/WCU với table chính
- Có thể strongly consistent
resource "aws_dynamodb_table" "orders" {
# ... table config ...
hash_key = "customer_id"
range_key = "order_date"
# LSI: Query orders by status (same partition key)
local_secondary_index {
name = "StatusIndex"
range_key = "status"
projection_type = "ALL"
}
attribute {
name = "status"
type = "S"
}
}
Query trên LSI:
"Lấy tất cả orders của customer CUST-001 có status = 'pending'"
GSI vs LSI
| Feature | GSI | LSI |
|---|---|---|
| Partition key | Khác | Same |
| Tạo sau khi table exists | ✅ Có | ❌ Không |
| RCU/WCU | Riêng | Chung với table |
| Consistency | Eventually only | Strongly/Eventually |
| Giới hạn | 20 per table | 5 per table |
DynamoDB Streams
Capture changes (insert, update, delete) và trigger actions.
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ DynamoDB │───►│ DynamoDB Stream │───►│ Lambda │
│ Table │ │ (24h retention) │ │ Function │
└─────────────┘ └──────────────────┘ └─────────────┘
│
┌──────┴──────┐
│ - Send email │
│ - Update ES │
│ - Aggregate │
└─────────────┘
Terraform
resource "aws_dynamodb_table" "orders" {
# ...
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES" # Capture cả before và after
}
# Lambda trigger
resource "aws_lambda_event_source_mapping" "stream" {
event_source_arn = aws_dynamodb_table.orders.stream_arn
function_name = aws_lambda_function.process_order.arn
starting_position = "LATEST"
batch_size = 100
}
DynamoDB Accelerator (DAX)
In-memory cache cho DynamoDB với microsecond latency.
Without DAX:
App → DynamoDB (1-10ms)
With DAX:
App → DAX (microseconds) → DynamoDB (nếu cache miss)
Khi nào dùng DAX?
✅ Read-heavy workloads ✅ Cần microsecond latency ✅ Same query patterns repeated
❌ Write-heavy workloads ❌ Strongly consistent reads required ❌ Complex queries
Terraform
resource "aws_dax_cluster" "cache" {
cluster_name = "${var.project_name}-dax"
iam_role_arn = aws_iam_role.dax.arn
node_type = "dax.t3.small"
replication_factor = 3
subnet_group_name = aws_dax_subnet_group.main.name
security_group_ids = [aws_security_group.dax.id]
}
DynamoDB Transactions
Hỗ trợ ACID transactions across multiple items/tables.
# Python boto3 example
response = dynamodb.transact_write_items(
TransactItems=[
{
'Put': {
'TableName': 'Orders',
'Item': {'order_id': {'S': 'ORD-123'}, ...}
}
},
{
'Update': {
'TableName': 'Inventory',
'Key': {'product_id': {'S': 'PROD-001'}},
'UpdateExpression': 'SET quantity = quantity - :dec',
'ExpressionAttributeValues': {':dec': {'N': '1'}}
}
}
]
)
# Cả 2 operations succeed hoặc fail cùng nhau
⚠️ Lưu ý: Transactions cost 2x RCU/WCU compared to standard operations.
Global Tables (Multi-Region)
Active-active replication across AWS regions.
┌──────────────────┐ ┌──────────────────┐
│ Region: Tokyo │◄───────►│ Region: Sydney │
│ DynamoDB Table │ sync │ DynamoDB Table │
└──────────────────┘ └──────────────────┘
▲ ▲
│ write │ write
│ │
Users in Japan Users in Australia
Terraform
resource "aws_dynamodb_table" "global" {
name = "GlobalUsers"
billing_mode = "PAY_PER_REQUEST"
hash_key = "user_id"
attribute {
name = "user_id"
type = "S"
}
stream_enabled = true # Required for Global Tables
stream_view_type = "NEW_AND_OLD_IMAGES"
replica {
region_name = "ap-northeast-1" # Tokyo
}
replica {
region_name = "ap-southeast-2" # Sydney
}
}
Best Practices
1. Partition Key Design
# ❌ Bad: Low cardinality
# Partition key = "status" (chỉ có: pending, completed, cancelled)
# → Hot partition vì hầu hết items có status = "completed"
# ✅ Good: High cardinality
# Partition key = "user_id" hoặc "order_id"
# → Data phân bố đều
2. Write Sharding cho hot items
# Counter item bị hot (mọi người đều update)
# Thêm random suffix để phân bố writes
partition_key = f"page_views#{random.randint(0, 9)}"
# Query: phải aggregate từ 10 shards
total = sum([get_item(f"page_views#{i}") for i in range(10)])
3. TTL để tự động xóa data
resource "aws_dynamodb_table" "sessions" {
# ...
ttl {
attribute_name = "expires_at" # Unix timestamp
enabled = true
}
}
# Item sẽ tự động bị xóa sau expires_at (không tốn WCU!)
4. Sparse Indexes
# Chỉ index items có attribute "premium"
# Items không có attribute này sẽ không xuất hiện trong index
# → Index nhỏ hơn, RCU ít hơn
global_secondary_index {
name = "PremiumUsersIndex"
hash_key = "premium" # Chỉ users có attribute này
}
Practice Questions (SAA style)
1. Một ứng dụng gaming cần lưu trữ player scores với millions of writes per second. Điểm số cần query theo player_id và game_id. Thiết kế table nào phù hợp nhất?
A. RDS PostgreSQL với read replicas
B. DynamoDB với partition key = player_id, sort key = game_id
C. DynamoDB với partition key = game_id only
D. ElastiCache Redis
Đáp án: B - DynamoDB scales tốt cho high writes, composite key cho phép query cả theo player và game.
2. Một DynamoDB table có provisioned capacity 100 RCU. Mỗi item 8KB. Có thể thực hiện bao nhiêu strongly consistent reads per second?
A. 100
B. 50
C. 200
D. 25
Đáp án: B - 1 RCU = 1 strongly consistent read cho 4KB. Item 8KB cần 2 RCU. 100 RCU / 2 = 50 reads/sec.
3. Ứng dụng cần query DynamoDB theo 3 access patterns khác nhau mà không sử dụng được primary key. Giải pháp nào tốt nhất?
A. Tạo 3 tables khác nhau
B. Sử dụng Scan operation với filter
C. Tạo Global Secondary Indexes
D. Migrate sang Aurora
Đáp án: C - GSI cho phép query theo các attributes khác. Scan là expensive và không scalable.
Bài tiếp theo: API Gateway - Xây dựng REST/HTTP APIs cho serverless apps.