DynamoDB
DynamoDB là key-value và document database serverless của AWS: độ trễ vài ms ở mọi quy mô nếu bạn chọn đúng partition key. Bài này đi qua partition key, sort key, single-table design, GSI và LSI, hai chế độ capacity, hot partition, Streams, TTL, transaction và cách nghĩ về chi phí.
DynamoDB là gì và mô hình cốt lõi
Amazon DynamoDB là database NoSQL fully managed trên cloud của Amazon: bạn có thể tăng giảm throughput của bảng một cách động mà không tự vận hành server.1 Grokking xếp DynamoDB vào nhóm key-value database: dữ liệu lưu theo cặp key-value, dễ chia partition và scale ngang.2
Tên gọi gợi nhớ thiết kế Dynamo mà Grokking dùng làm nền cho chương Key-value Store: consistent hashing, virtual node, replication, quorum r + w > n, vector clock.3 Bài này không khẳng định dịch vụ DynamoDB hiện nay cài đặt đúng từng chi tiết đó; thư viện chưa có sách về kiến trúc bên trong của dịch vụ.4
Mô hình dữ liệu:
- Table chứa các item (giống row nhưng linh hoạt hơn: item là tập thuộc tính có tên, có thể lồng nhau như JSON và chứa cả một aggregate).5 Giới hạn kích thước item là số liệu của sản phẩm, hãy tra tài liệu hiện hành.4
- Mỗi item có primary key, gồm:
- Các thuộc tính khác không cần schema. Hai item trong cùng bảng có thể có thuộc tính hoàn toàn khác nhau (schema định nghĩa theo từng item).2
Nhóm các item có cùng PK gọi là item collection. Đây là ý tưởng trung tâm: một lần Query trả các item có partition key chỉ định, sort key nằm trong khoảng cho trước, thoả filter tuỳ chọn, theo thứ tự sort key.6
| Thao tác | Mô tả |
|---|---|
GetItem | Lấy một item theo PK (+ SK) |
Query | Đọc các item cùng PK, lọc theo khoảng SK; có filter expression và phân trang bằng LastEvaluatedKey |
Scan | Đọc toàn bảng, nên tránh trong đường request (khuyến nghị của khoá học) |
PutItem / UpdateItem / DeleteItem | PutItem tạo hoặc thay cả item; UpdateItem sửa từng thuộc tính, tạo item nếu cần; hỗ trợ ghi có điều kiện |
TransactWriteItems | Ghi nguyên tử nhiều item (tính năng của sản phẩm) |
Mô tả Query, PutItem, UpdateItem theo Microservices Patterns.76 Các thao tác batch, transaction và chi phí tương đối của từng thao tác là kiến thức sản phẩm chưa có sách kiểm chứng.4
Kiến trúc bên dưới
Sơ đồ trên là hình dung của khoá học về cách một dịch vụ key-value được phân partition và nhân bản; chi tiết nội bộ của DynamoDB chưa có sách trong thư viện.4 Những ý có nguồn:
- Partition key được dùng để chọn partition của item: đây là scale theo trục Z (chia dữ liệu theo key).6
- Một hệ kiểu Dynamo nhân bản mỗi key ra n node và dùng r + w > n để đọc thấy lần ghi mới nhất; giảm r hoặc w đổi lấy tốc độ nhưng có thể đọc dữ liệu cũ.8 DynamoDB cho chọn đọc eventually consistent hay strongly consistent, ở mức khái niệm tương tự.4
- Một key nóng chỉ chịu được tải của một nhóm node, nên thiết kế key phải trải đều tải.9 Giới hạn cụ thể mỗi partition của sản phẩm cần tra tài liệu.4
Data model: partition key, sort key và single-table design
Chọn key bắt đầu từ access pattern
Giống Cassandra, DynamoDB yêu cầu bạn liệt kê câu truy vấn trước: Microservices Patterns nhận xét thao tác truy cập dữ liệu của DynamoDB kém mạnh hơn RDBMS nên query thường quyết định schema.5 Ví dụ một hệ thống thương mại điện tử nhỏ cần:
- Lấy hồ sơ user.
- Lấy 20 đơn hàng mới nhất của user.
- Lấy một đơn hàng và các dòng hàng của nó.
- Tìm đơn hàng theo trạng thái để xử lý (ví dụ
PENDING).
Single-table design
Thay vì mỗi entity một bảng, ta đặt nhiều loại entity vào một bảng, dùng PK/SK dạng chuỗi có tiền tố:
| PK | SK | Loại | Thuộc tính khác |
|---|---|---|---|
USER#42 | PROFILE | User | name, email |
USER#42 | ORDER#2026-11-02#A91 | Order (bản tóm tắt) | total, status |
USER#42 | ORDER#2026-10-28#A77 | Order (bản tóm tắt) | total, status |
ORDER#A91 | ITEM#1 | Dòng hàng | sku, qty |
ORDER#A91 | ITEM#2 | Dòng hàng | sku, qty |
- Query 1:
GetItem(PK=USER#42, SK=PROFILE). - Query 2:
Query(PK=USER#42, SK begins_with "ORDER#"), sắp giảm dần. Vì SK chứa ngày theo định dạng ISO, thứ tự chuỗi chính là thứ tự thời gian. - Query 3:
Query(PK=ORDER#A91). - Query 4: cần một secondary index (xem dưới).10
Lợi ích: một request lấy được nhiều loại item liên quan, giống một join đã tính sẵn. Cái giá: mô hình khó đọc với người mới, và thêm access pattern mới về sau có thể phải backfill dữ liệu. Single-table design là thực hành riêng của cộng đồng DynamoDB, chưa có sách trong thư viện;4 điều sách có là việc lưu cả aggregate trong một item và chọn partition key + sort key theo query.5
Code mẫu (Node.js, AWS SDK v3)
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({ region: "ap-southeast-1" }));
// Tạo đơn hàng, chỉ thành công nếu chưa tồn tại (chống ghi trùng khi retry)
await ddb.send(new PutCommand({
TableName: "shop",
Item: { PK: "USER#42", SK: "ORDER#2026-11-02#A91", status: "PENDING", total: 350000 },
ConditionExpression: "attribute_not_exists(PK)",
}));
// 20 đơn mới nhất của user
const { Items, LastEvaluatedKey } = await ddb.send(new QueryCommand({
TableName: "shop",
KeyConditionExpression: "PK = :pk AND begins_with(SK, :prefix)",
ExpressionAttributeValues: { ":pk": "USER#42", ":prefix": "ORDER#" },
ScanIndexForward: false,
Limit: 20,
}));
// LastEvaluatedKey dùng làm cursor cho trang sau (ExclusiveStartKey)
ConditionExpression là công cụ quan trọng nhất cho tính đúng. Microservices Patterns dùng conditional update để bỏ qua sự kiện trùng (idempotency) và nhắc DynamoDB có cơ chế optimistic locking để tránh lost update.117 Dùng cho version = :expected hay stock >= :qty là áp dụng cùng cơ chế (ví dụ của khoá học).
GSI, LSI, Streams, TTL và transaction
Secondary index
Bảng chỉ truy cập nhanh theo primary key. Để có query theo thuộc tính khác (ví dụ lấy các đơn của một consumer theo thời gian), Microservices Patterns dùng một global secondary index với khoá (consumerId, orderCreationTime), không cần duy nhất. Index tự cập nhật khi bảng đổi và, khác index RDBMS, có thể mang thêm thuộc tính không thuộc khoá để query khỏi phải đọc bảng.10
Với access pattern 4 ở trên, ta thêm thuộc tính GSI1PK = STATUS#PENDING, GSI1SK = <ngày tạo> cho các đơn hàng đang chờ, rồi tạo index trên hai thuộc tính này. Khi đơn chuyển sang SHIPPED, xoá GSI1PK để item rời khỏi index (sparse index, kỹ thuật của cộng đồng DynamoDB).4
Sự khác nhau giữa GSI và LSI (thời điểm tạo, số lượng tối đa, mức consistency, capacity) là chi tiết sản phẩm, chưa có sách trong thư viện nên bài không liệt kê.4
Lưu ý: Index có PK ít giá trị (như STATUS#PENDING) dồn tải vào ít partition. Hướng xử lý là thêm hậu tố shard như STATUS#PENDING#7 và đọc song song các shard (cùng ý với sharded counters ở Grokking ch.24).12
DynamoDB Streams
Streams chứa chuỗi thay đổi theo thứ tự thời gian (tạo, sửa, xoá) trên các item của bảng trong 24 giờ gần nhất; ứng dụng đọc stream để, ví dụ, phát thành event.13 Dùng để:
- Kích hoạt xử lý phía sau: gửi email khi đơn hàng được tạo, cập nhật bộ đếm.
- Đồng bộ sang Elasticsearch hoặc data lake. Đây là một dạng Change Data Capture: Microservices Patterns liệt kê DynamoDB streams cùng Debezium và Databus trong nhóm cách đọc thay đổi từ database.13
- Làm nền cho outbox pattern: ghi item và để stream phát event, không cần dual write.13
TTL và transaction
TTL tự xoá item hết hạn và transaction ghi nguyên tử nhiều item đều là tính năng của sản phẩm, thư viện chưa có sách mô tả; hạn mức và chi phí cần tra tài liệu hiện hành.4 Nguyên tắc chung: dùng transaction cho các bất biến nhỏ như "trừ tồn kho và tạo đơn cùng lúc" (Flash sale, Đặt vé sự kiện), và đừng dùng để thay thế một hệ quan hệ. Microservices Patterns lưu ý nhiều NoSQL không hỗ trợ transaction phân tán, nên khi cần nhất quán nhiều service phải dùng saga.14
Capacity mode, hot partition và chi phí
Capacity mode
Microservices Patterns nói DynamoDB cho phép tăng giảm throughput capacity của bảng một cách động.1 Thư viện chưa có sách về đơn vị đo, các chế độ capacity, quota hay giá nên bài không đưa con số.4 Nguyên tắc chọn (ước lượng của khoá học, cần so sánh với bảng giá hiện hành):
- Traffic khó đoán, sản phẩm mới: chọn chế độ trả theo request.
- Traffic ổn định, dự đoán được: chế độ đặt trước capacity thường rẻ hơn.
- Kích thước item ảnh hưởng chi phí ghi: tách phần lớn, ít đổi (mô tả dài, ảnh) ra object storage.
- Mỗi secondary index thêm chi phí ghi, vì ghi bảng chính phải cập nhật cả index.10
Hot partition
Hot partition xảy ra khi một PK nhận phần lớn traffic: một sản phẩm flash sale, một người nổi tiếng, một bộ đếm toàn cục. Vì partition key quyết định node lưu, dồn vào một key là dồn vào một nhóm node, dù tổng capacity của bảng còn dư.9
Cách xử lý:
- Write sharding:
PRODUCT#99#0...PRODUCT#99#9, ghi ngẫu nhiên, đọc thì gộp. Đây là ý tưởng sharded counter của Grokking: chia một counter nóng thành nhiều shard rồi cộng lại khi đọc.12 Xem Scale ghi. - Cache đọc bằng Redis hoặc cache khác cho key đọc nhiều.
- Gom ghi trong bộ nhớ hoặc qua Kafka rồi ghi tổng định kỳ, thay vì tăng counter từng lượt.
Mốc đối chiếu: Grokking dùng khoảng 10.000 QPS cho một server key-value và độ trễ cùng datacenter cỡ 0,5 ms (số tham khảo, không phải giới hạn của DynamoDB).15
Khi nào dùng và khi nào không
| Tình huống | DynamoDB | Lựa chọn khác |
|---|---|---|
| Đã ở AWS, access pattern rõ, cần scale mà không vận hành | Hợp (managed, key-value)2 | Cassandra nếu muốn tự host |
| Session, giỏ hàng, hồ sơ user, idempotency key | Hợp (use case của key-value DB theo Grokking)2 | Redis nếu chỉ tạm thời |
| Tải thất thường, serverless | Hợp (giả định của khoá học) | Postgres cần quản lý connection |
| Multi-region active-active | Hợp, chấp nhận giải quyết xung đột kiểu last-write-wins16 | Cassandra multi-DC |
| Báo cáo, query ad-hoc, join | Không hợp: thao tác truy cập kém mạnh hơn RDBMS5 | PostgreSQL, warehouse |
| Tìm kiếm full-text | Không hợp | Elasticsearch qua Streams |
| Item rất lớn | Không hợp (giả định của khoá học) | Object storage + metadata trong DynamoDB |
| Cần tránh vendor lock-in | Cân nhắc | Cassandra |
Vận hành ở quy mô nhỏ và lớn
Thư viện chưa có sách về vận hành DynamoDB (backup, auto scaling, multi-region, công cụ giám sát), nên phần này chỉ nêu nguyên tắc.4
Quy mô nhỏ. Bắt đầu với chế độ trả theo request, một bảng, một hai index. Bật backup từ đầu và test bằng bản chạy local.
Quy mô lớn. Những việc cần làm:
- Chuyển bảng tải ổn định sang capacity đặt trước nếu rẻ hơn.
- Giám sát throttling và key nóng nhất.
- Multi-region: nhớ xung đột được giải quyết theo last-write-wins.16
- Phân tích bằng export thay vì quét bảng production.
- Đặt TTL cho dữ liệu tạm để không trả tiền lưu trữ mãi mãi.
Mẹo: Với serverless, giữ client database ở ngoài handler để tái sử dụng kết nối. Đây là mẹo của khoá học.
Điểm nói chuyện trong phỏng vấn
Trong phỏng vấn: Khi chọn DynamoDB, viết ra PK và SK cho từng access pattern, nêu GSI nào phục vụ query nào, và chỉ ra key nào có nguy cơ nóng cùng cách shard nó. Người phỏng vấn đánh giá cao việc bạn nhận ra rủi ro dồn tải vào một partition và biết cách shard key.12
Checklist:
- Liệt kê access pattern, rồi mới đặt PK/SK.5
- Dùng
ConditionExpressioncho idempotency và chống race condition.11 - Hot key: write sharding, cache, gom ghi.
- Streams cho side effect và đồng bộ sang hệ khác.13
- TTL cho dữ liệu tạm. Transaction cho bất biến nhỏ (giới hạn cụ thể tra tài liệu).
- Chọn capacity mode theo hình dạng traffic, ước lượng chi phí theo kích thước item.
Các bài toán hay dùng DynamoDB hoặc key-value store tương tự:
- URL shortener:
GetItemtheo short code, đọc rất nhiều. - Đặt vé sự kiện và Flash sale: conditional write và transaction để không bán quá.
- Rate limiter: bộ đếm theo key với TTL (thường đi cùng Redis).
- Dropbox: metadata file, nội dung lưu S3.
- Notification system: trạng thái gửi, idempotency key.
- Tinder: lưu lượt swipe theo user.
Nguồn trích dẫn
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4 Implementing a CQRS view with AWS DynamoDB, đoạn mở đầu (tr. 242). ↩ ↩2
-
Grokking Modern System Design · ch.9 Databases · mục "Key-value database" (trong "Types of NoSQL databases"). ↩ ↩2 ↩3 ↩4
-
Grokking Modern System Design · ch.10 Key-value Store · thiết kế key-value store kiểu Dynamo. ↩
-
Chưa có nguồn trong thư viện: thư viện không có sách về kiến trúc nội bộ, giới hạn, giá hay tính năng chi tiết của DynamoDB (single-table design, GSI/LSI, TTL, transaction, capacity mode); phần này chỉ nêu khái niệm cơ bản, không đưa số liệu của sản phẩm. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4.2 Data modeling and query design with DynamoDB, mục "Designing the ftgo-order-history table" (tr. 244–245). ↩ ↩2 ↩3 ↩4 ↩5
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4.2, mục "Defining an index for the findOrderHistory query" (tr. 245–246): partition key, sort key, thao tác query(). ↩ ↩2 ↩3 ↩4 ↩5
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4.2, mục "Paginating the query results" và "Updating orders" (tr. 247–248). ↩ ↩2
-
Grokking Modern System Design · ch.10 Key-value Store · mục "Usage of r and w" (r + w > n). ↩
-
Grokking Modern System Design · ch.9 Databases · mục "Sharding", "Hash-based sharding". ↩ ↩2
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4.2, mục "Defining an index for the findOrderHistory query" và "Implementing the findOrderHistory query" (tr. 246–247). ↩ ↩2 ↩3
-
Microservices Patterns (file .zip, tải về giải nén) · ch.7 · §7.4.2, mục "Detecting duplicate events" (tr. 248–249). ↩ ↩2
-
Grokking Modern System Design · ch.24 Sharded Counters · mục "High-level solution sketch" và "Detailed design". ↩ ↩2 ↩3
-
Microservices Patterns (file .zip, tải về giải nén) · ch.3 · §3.3.7 Transactional messaging, mục "Transaction log tailing" (DynamoDB streams) và "Polling publisher" cùng outbox (tr. 97–100). ↩ ↩2 ↩3 ↩4
-
Microservices Patterns (file .zip, tải về giải nén) · ch.4 · §4.1.2 The trouble with distributed transactions: một số NoSQL như MongoDB và Cassandra không hỗ trợ chúng. ↩
-
Grokking Modern System Design · ch.5 Back-of-the-envelope Calculations · mục "Standard numbers to remember". ↩
-
Grokking Modern System Design · ch.9 Databases · mục "Handle conflicts", "Last-write-wins". ↩ ↩2