Khác với RDS và Aurora, DynamoDB là một cơ sở dữ liệu NoSQL. DynamoDB được sử dụng cho các ứng dụng cần hiệu năng cao và độ trễ cực thấp (vài millisecond). Cùng tìm hiểu kỹ hơn trong bài viết này.
Trong bài này:
1. Các Khái niệm Cơ bản
-
DynamoDB là CSDL NoSQL, thuộc loại Key-Value Store. Tức là dữ liệu được lưu trữ dạng cặp khóa-giá trị, giống như dictionary trong Python, hay object trong JavaScript.
-
Mỗi bản ghi (item) trong DynamoDB gồm một Khoá Chính (Primary Key) và tập hợp các thuộc tính (attribute) khác. Trong đó khóa chính là duy nhất và được dùng để truy xuất giá trị của bản ghi. Các bản ghi phải có cùng khoá chính, nhưng có thể có các thuộc tính khác nhau, không cần tuân theo một cấu trúc cố định nào.
- 2 loại khoá chính:
- Khoá Chính Đơn (Partition Key): một thuộc tính duy nhất làm khoá chính.
- Khoá Chính Kép (Composite Key): chọn ra 2 thuộc tính, 1 là Partition Key và 1 là Sort Key. Partition Key có vai trò xác định phân vùng lưu trữ (partition), còn Sort Key được dùng để sắp xếp các bản ghi có cùng Partition Key (nhưng buộc phải khác Sort Key để đảm bảo tính duy nhất của khoá chính).
-
Bảng (table) là tập hợp các bản ghi có cùng khoá chính. Bản chất là làm phẳng dữ liệu, để trống các thuộc tính không có giá trị.
- DynamoDB là dịch vụ serverless, người dùng không cần quan tâm đến việc quản lý máy chủ, mở rộng quy mô, hay bảo trì.
2. Ví dụ
Để bạn đọc dễ nắm được các khái niệm, mình sẽ trình bày cách tạo bảng, ghi dữ liệu, đọc dữ liệu, cập nhật dữ liệu, và truy vấn dữ liệu trong DynamoDB bằng AWS CLI. Ngoài ra cũng có thể thực hiện trên giao diện web, hoặc SDK của các ngôn ngữ lập trình khác nhau.
2.1. Tạo bảng
Đầu tiên, ta tạo một bảng có tên AwsCobanOrders với khoá chính kép, với Partition Key là OrderType và Sort Key là Date:
aws dynamodb create-table \
--table-name AwsCobanOrders \
--attribute-definitions \
AttributeName=OrderType,AttributeType=S \
AttributeName=Date,AttributeType=S \
--key-schema \
AttributeName=OrderType,KeyType=HASH \
AttributeName=Date,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region us-east-1
Trong đó, hai tham số quan trọng nhất là:
--attribute-definitionskhai báo các thuộc tính sẽ được dùng làm khóa chính, với tên và kiểu dữ liệu (Slà String,Nlà Number). Không cần khai báo các thuộc tính khác. Ở đây ta liệt kêOrderTypevàDate.--key-schemachỉ định thuộc tính nào đóng vai trò gì trong khóa chính:KeyType=HASHlà Partition Key (bắt buộc).Hashở đây là hàm băm, giá trị của Partition Key sẽ được đưa vào băm để xác định phân vùng lưu trữ (trình bày ở phần sau).KeyType=RANGElà Sort Key (có thể không có).
Nếu chỉ sử dụng khoá chính đơn (Partition Key), lệnh trên sẽ ngắn hơn:
aws dynamodb create-table \
--table-name AwsCobanOrders \
--attribute-definitions \
AttributeName=OrderType,AttributeType=S \
--key-schema \
AttributeName=OrderType,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-1
2.2. Ghi Dữ liệu
Để thêm bản ghi vào bảng, ta dùng lệnh put-item:
aws dynamodb put-item \
--table-name AwsCobanOrders \
--item '{
"OrderType": {"S": "Electronics"},
"Date": {"S": "2026-01-01"},
"Status": {"S": "Pending"},
"Amount": {"N": "500"}
}' \
--region us-east-1
aws dynamodb put-item \
--table-name AwsCobanOrders \
--item '{
"OrderType": {"S": "Electronics"},
"Date": {"S": "2025-11-11"},
"Status": {"S": "Delivered"}
}' \
--region us-east-1
aws dynamodb put-item \
--table-name AwsCobanOrders \
--item '{
"OrderType": {"S": "Footwears"},
"Date": {"S": "2026-02-11"},
"Note": {"S": "Deliver by end of February"}
}' \
--region us-east-1
Bản ghi được đưa vào tham số --item dưới dạng JSON, có thêm các thuộc tính khác ngoài khoá chính. Dễ thấy, ngoài Partition Key là OrderType và Sort Key là Date, các bản ghi có thể chứa các thuộc tính khác nhau. Điều này thể hiện cấu trúc linh hoạt của NoSQL.
Hai bản ghi đầu tiên có cùng OrderType là Electronics, nhưng Date khác nhau. Việc này vẫn hợp lệ vì ta đang dùng khoá chính kép. Còn nếu chỉ dùng khoá chính đơn, thì sẽ không thể có hai bản ghi với cùng OrderType, vì sẽ vi phạm tính duy nhất của khoá chính. Trong trường hợp này, lệnh put-item thứ hai sẽ ghi đè bản ghi đầu tiên.
Dưới đây là bảng dữ liệu sau khi đã thêm 3 bản ghi:
| OrderType | Date | Status | Amount | Note |
|---|---|---|---|---|
| Electronics | 2026-01-01 | Pending | 500 | |
| Electronics | 2025-11-11 | Delivered | ||
| Footwears | 2026-02-11 | Deliver by end of February |
2.3. Đọc Dữ liệu
DynamoDB là một Key-Value Store, nên để đọc dữ liệu, ta phải cung cấp đủ giá trị của khoá chính. Nếu dùng khoá chính đơn, chỉ cần cung cấp giá trị của Partition Key. Nếu dùng khoá chính kép, cần cung cấp cả Partition Key và Sort Key.
Để đọc một bản ghi cụ thể, ta dùng lệnh get-item như sau:
aws dynamodb get-item \
--table-name AwsCobanOrders \
--key '{
"OrderType": {"S": "Electronics"},
"Date": {"S": "2026-01-01"}
}' \
--region us-east-1
Kết quả trả về các thuộc tính của bản ghi dưới dạng JSON:
{
"Item": {
"Status": {
"S": "Pending"
},
"OrderType": {
"S": "Electronics"
},
"Amount": {
"N": "500"
},
"Date": {
"S": "2026-01-01"
}
}
}
2.4. Cập nhật Dữ liệu
Trong Key-Value Store, Key là bất biến, vì cập nhật Key tương đương với tạo bản ghi mới.
Nên chỉ có thể cập nhật các thuộc tính không phải khoá chính của bản ghi. Cụ thể, với AWS CLI, ta dùng lệnh update-item.:
aws dynamodb update-item \
--table-name AwsCobanOrders \
--key '{
"OrderType": {"S": "Electronics"},
"Date": {"S": "2026-01-01"}
}' \
--update-expression "SET #S = :val" \
--expression-attribute-names '{"#S": "Status"}' \
--expression-attribute-values '{":val": {"S": "Delivered"}}' \
--region us-east-1
Trong đó:
--update-expressionchỉ định cách cập nhật thuộc tính. Ở đây,SET #S = :valcó nghĩa là gán giá trị:valcho thuộc tính#S(đây là 2 biến sẽ được định nghĩa sau).--expression-attribute-namesđịnh nghĩa tên thuộc tính. Ở đây ta đặt#SlàStatus. Việc không dùng trực tiếp mà đặt biến là để tránh xung đột với các từ khoá của DynamoDB.--expression-attribute-valuesđặt giá trị của:val, ở đây vẫn là kiểu String (S) với giá trị mới làDelivered.
Kết quả, ta sẽ thấy giá trị của thuộc tính Status được cập nhật từ “Pending” thành “Delivered”:
{
"Item": {
"Amount": {
"N": "500"
},
"OrderType": {
"S": "Electronics"
},
"Status": {
"S": "Delivered"
},
"Date": {
"S": "2026-01-01"
}
}
}
2.5. Truy vấn Dữ liệu
Khác với đọc dữ liệu đơn lẻ bằng get-item, để truy vấn nhiều bản ghi dựa trên giá trị của Partition Key và điều kiện trên Sort Key, ta dùng lệnh query. Lưu ý, không thể truy vấn dựa trên các thuộc tính thông thường. Nếu muốn truy vấn theo các thuộc tính này, cần dùng Scan hoặc tạo Secondary Index.
Ví dụ, trong bảng đã tạo có 2 đơn hàng loại Electronics, 1 trong năm 2025, 1 trong năm 2026. Để truy vấn tất cả đơn hàng loại Electronics trong năm 2026, dùng lệnh sau:
aws dynamodb query \
--table-name AwsCobanOrders \
--key-condition-expression "OrderType = :type AND begins_with(#D, :year)" \
--expression-attribute-names '{"#D": "Date"}' \
--expression-attribute-values '{
":type": {"S": "Electronics"},
":year": {"S": "2026"}
}' \
--region us-east-1
Kết quả trả về bản ghi trong năm 2026:
{
"Items": [
{
"Amount": {
"N": "500"
},
"OrderType": {
"S": "Electronics"
},
"Status": {
"S": "Delivered"
},
"Date": {
"S": "2026-01-01"
}
}
],
"Count": 1,
"ScannedCount": 1,
"ConsumedCapacity": null
}
2.6. Scan Dữ liệu
Như đã đề cập, query chỉ có thể truy vấn dựa trên giá trị của Partition Key và điều kiện trên Sort Key. Nếu muốn truy vấn dựa trên các thuộc tính thông thường, ta phải dùng scan. Lệnh scan sẽ quét toàn bộ bảng để tìm các bản ghi thỏa mãn điều kiện.
Ví dụ, để tìm tất cả đơn hàng có thuộc tính Status (không phải khoá chính) là Delivered, ta dùng lệnh sau:
aws dynamodb scan \
--table-name AwsCobanOrders \
--filter-expression "contains(#S, :status)" \
--expression-attribute-names '{"#S": "Status"}' \
--expression-attribute-values '{
":status": {"S": "Delivered"}
}' \
--region us-east-1
Kết quả:
{
"Items": [
{
"Date": {
"S": "2025-11-11"
},
"Status": {
"S": "Delivered"
},
"OrderType": {
"S": "Electronics"
}
}
],
"Count": 1,
"ScannedCount": 3,
"ConsumedCapacity": null
}
Ở đây, kết quả trả về ScannedCount = 3, tức toàn bộ 3 bản ghi trong bảng đã được quét.
Do đó, scan sẽ tốn nhiều thời gian và chi phí hơn query, nên ít khi được sử dụng, đặc biệt khi bảng rất lớn.
3. Khoá Chính trong DynamoDB
Phần này ta đi sâu hơn một chút về cách hoạt động của khoá chính.
3.1. Khoá Chính Đơn (Partition Key)
Trường hợp chỉ sử dụng khoá chính đơn (Partition Key), DynamoDB lưu trữ và truy xuất dữ liệu dựa trên giá trị của Partition Key.
- Để ghi dữ liệu, DynamoDB sẽ đưa giá trị của Partition Key vào một hàm băm (hash function) để xác định phân vùng (partition) trên ổ cứng mà bản ghi sẽ được lưu.
- Để đọc dữ liệu, DynamoDB cũng sẽ đưa giá trị của Partition Key vào hàm băm để xác định phân vùng chứa bản ghi, rồi truy xuất bản ghi từ phân vùng đó. Việc dùng hàm băm giúp ngay lập tức xác định phân vùng chứa bản ghi, nên việc truy xuất dữ liệu cực kỳ nhanh (độ trễ vài millisecond).
Ví dụ, ở hình trên, ta cần ghi một bản ghi có Partition Key là Electronics. DynamoDB sẽ đưa Electronics vào hàm băm, đầu ra của hàm băm là một chuỗi nhị phân, và dựa vào đó để xác định phân vùng lưu trữ. Mỗi phân vùng có thể chứa nhiều bản ghi với các giá trị Partition Key khác nhau, nhưng tất cả đều được xác định bởi hàm băm. Để tối ưu hiệu năng, nên chọn thuộc tính có nhiều giá trị khác nhau làm Partition Key, để phân bố đều dữ liệu, tránh tình trạng hot partition, tức một phân vùng chứa quá nhiều bản ghi, gây chậm truy xuất dữ liệu.
3.2. Khoá Chính Kép (Composite Key)
Trường hợp sử dụng khoá chính kép, ngoài Partition Key, ta còn có Sort Key. Partition Key vẫn được đưa vào hàm băm để xác định phân vùng. Các bản ghi có cùng Partition Key (gọi là item collection) thường sẽ được đặt gần nhau và sắp xếp theo thứ tự tăng dần của Sort Key.
- Khi ghi dữ liệu, DynamoDB vẫn đưa giá trị của Partition Key vào hàm băm để xác định phân vùng, rồi sắp xếp và lưu bản ghi vào phân vùng đó.
- Khi đọc dữ liệu, nếu cung cấp đủ giá trị của khoá chính (cả Partition Key và Sort Key), DynamoDB sẽ truy xuất trực tiếp bản ghi đó. Nếu chỉ cung cấp giá trị của Partition Key, tất cả bản ghi có cùng Partition Key sẽ được trả về.
Ở hình trên, ta có 3 bản ghi với cùng Partition Key là Electronics, nhưng Sort Key khác nhau. Khi truy vấn với cả Partition Key Electronics và Sort Key 2026-01-01, bản ghi cụ thể sẽ được trả về.
3.3. Lưu ý khi Thiết kế Partition Key
Việc chọn Partition Key là quyết định quan trọng nhất khi thiết kế DynamoDB, ảnh hưởng trực tiếp đến khả năng mở rộng (scalability), hiệu suất và chi phí của hệ thống. Một số lưu ý khi thiết kế Partition Key:
-
Quan trọng nhất, chọn thuộc tính có độ phân tán cao (high cardinality): có càng nhiều giá trị khác nhau càng tốt. Vì DynamoDB sử dụng hàm băm cho Partition Key để phân bổ dữ liệu vào các phân vùng vật lý, nên nếu Partition Key càng đa dạng, dữ liệu càng được chia đều, tránh việc một phân vùng bị quá tải (hot partition). Ví dụ, nên chọn
UserId,OrderId,Email, thay vìGenderhayCountry, vì các giá trị này thường có số lượng hạn chế. -
Chọn thuộc tính hay dùng để truy vấn: vì bắt buộc phải cung cấp Partition Key khi truy vấn, nên tốt nhất nên chọn thuộc tính thường xuyên cần dùng để truy vấn.
-
Write Sharding: nếu không thể chọn được thuộc tính có độ phân tán cao, có thể áp dụng kỹ thuật write sharding, tức thêm một phần ngẫu nhiên vào giá trị của Partition Key để tăng độ phân tán. Việc này giúp phân bổ dữ liệu đều hơn, nhưng việc truy vấn sẽ phức tạp hơn.
4. Câu hỏi Ôn tập
Tài liệu tham khảo
- Giới thiệu về DynamoDB
- Các Thành phần Quan trọng của DynamoDB
- Partition trong DynamoDB
- Thiết kế Partition Key
Tiếp theo, hãy tìm hiểu về cách tính hiệu năng và chi phí của DynamoDB (Read/Write Capacity Unit), và cách sử dụng Index.