
DynamoDB Quick Look cho người mới
1. Mở đầu
Dạo gần đây mình dùng Amazon DynamoDB khá nhiều trong các dự án, đặc biệt là những bài toán GenAI cần làm POC hoặc quick demo cho khách hàng. Lý do chọn ban đầu rất thực dụng: mình cần một nơi lưu trạng thái mà không phải dựng và giữ một máy chủ database chạy suốt ngày. Một Lambda nhỏ, một API, một bảng DynamoDB ở chế độ on-demand là đủ để đưa luồng thử nghiệm lên cloud nhanh, trong khi chi phí tăng theo mức sử dụng thực tế.
Nhưng càng dùng, mình càng thấy giá trị của DynamoDB không chỉ nằm ở chuyện lợi dụng một db service có free tier tôt để dựng “POC cho rẻ”. Điều đáng học hơn là cách dịch vụ này hoạt động và cách nó khiến aws bán được db ở dạng "serverless" mà không lo bị lỗ . Dynamodb buộc mình mô tả trước ứng dụng sẽ đọc và ghi dữ liệu như thế nào ngược với Db sql truyền thống buộc chúng ta thiết kế cấu trúc bảng cho tối ưu lệnh join chứ không ràng buộc về độ phức tạp hay tham số của query. Bài này là một quick look, tuy không thể biến biến người mới thành chuyên gia DynamoDb trong 1 lần đọc nhưng sẽ giúp các bạn tạo một mental model đủ chính xác để đọc tài liệu, thiết kế một bảng nhỏ và biết khi nào nên dừng lại để chọn công cụ DB khác.
Ta sẽ dùng một ví dụ xuyên suốt tên Chat application:
- bảng lưu hồ sơ người dùng,
- phiên chat,
- tin nhắn,
- trạng thái job
- và khóa idempotency cho một ứng dụng GenAI. Phần vector search của hệ thống vẫn thuộc về một vector store; file lớn vẫn thuộc về object storage. DynamoDB chỉ giữ operational state mà ứng dụng cần truy cập nhanh và có đường đọc rõ ràng.
2. Bộ từ vựng DynamoDB
Hãy bắt đầu từ ba khái niệm nhỏ nhất.
Table là tập hợp dữ liệu, ví dụ bảng ChatState.
Item là một bản ghi được nhận diện duy nhất trong table.
Attribute là một phần dữ liệu bên trong item.
Nếu quen SQL, bạn có thể tạm liên hệ table với bảng, item với row và attribute với column, nhưng chỉ nên coi đó là hình ảnh liên tưởng cho dễ hiểu chứ không phải hai mô hình hoàn toàn giống nhau.
Một item có thể có
- scalar attribute như chuỗi, số, binary, boolean hoặc null;
- document attribute như list và map; hoặc set.
- Một map có thể chứa nested attribute, chẳng hạn
settings.language = "vi". - Mỗi table có một primary key theo một trong hai dạng.
- Dạng đơn chỉ có partition key; giá trị partition key phải duy nhất trong toàn table.
- Dạng ghép có partition key và sort key; khi đó cặp hai giá trị mới phải duy nhất. Nhiều item được phép có cùng partition key nếu sort key khác nhau.

Một partition là đơn vị lưu trữ vật lý nội bộ do DynamoDB quản lý và nhân bản.
Một item collection là nhóm item trong table hoặc local secondary index có cùng partition key. Secondary index tạo thêm một cách tra cứu bằng key khác.
## Key và cách dữ liệu được đặt

Primary Key trong DynamoDb có giống PK của csdl có quan hệ hay không ? Primary Key trong DynamoDB chỉ giống SQL ở điểm dùng để định danh dữ liệu, nhưng cách nghĩ rất khác.
Trong SQL, ta có thể tạo bảng user, bảng message, rồi dùng user_id làm khóa ngoại để lấy message của user đó.
Trong DynamoDB, ta phải thiết kế key theo cách app sẽ đọc dữ liệu, ví dụ PK = USER#X và SK = MESSAGE#<id> để lấy nhanh tất cả message của user X.
Nếu sau này muốn query kiểu “lấy tất cả message gửi hôm nay”, thiết kế trên sẽ không query trực tiếp tốt vì dữ liệu đang được gom theo user, không phải theo ngày.
Vì vậy, với DynamoDB cần biết trước các kiểu query quan trọng rồi mới thiết kế Primary Key.
Khi lưu một dòng dữ liệu, DynamoDB sẽ nhìn vào partition key để quyết định dữ liệu đó nên được đặt ở đâu trong hệ thống lưu trữ của AWS. Bạn có thể hiểu partition key giống như “địa chỉ phân khu” của dữ liệu.
Nếu bảng có thêm sort key, thì những dữ liệu có cùng partition key sẽ được để gần nhau, rồi được sắp xếp theo sort key. Nhờ vậy DynamoDB có thể đọc nhanh một nhóm dữ liệu liên quan, ví dụ toàn bộ đơn hàng của một khách hàng theo thời gian.
Vì vậy, key trong DynamoDB không chỉ là code định danh cho data. Nó còn quyết định data được nằm ở đâu trong storage luôn. Bên dưới, mỗi partition là một phần lưu trữ do AWS quản lý, chạy trên SSD và được tự động sao chép qua nhiều Availability Zone để tăng độ bền và tính sẵn sàng.

Đây là khác biệt tư duy lớn so với relational database. Với relational model, ta thường chuẩn hóa entity và quan hệ trước, sau đó dùng optimizer để chọn execution plan cho nhiều câu SQL. Với DynamoDB, AWS khuyến nghị xác định use case, entity và access pattern trước rồi mới thiết kế primary key và index. Không phải relational linh hoạt còn DynamoDB cứng nhắc; đúng hơn là hai bên đặt sự linh hoạt ở vị trí khác nhau. Relational database tối ưu cho quan hệ, join và truy vấn biến đổi. DynamoDB tối ưu cho những đường truy cập bằng key đã biết và cần hiệu năng có thể dự đoán ở quy mô lớn.
3. Từ request đến partition trong dynamodb
Điểm quan trọng là: DynamoDB dùng PK để biết nên đọc ở đâu trước, thay vì phải đi tìm trong toàn bộ table.
Khi client gửi request, DynamoDB cần biết partition key, và nếu có sort key thì dùng thêm sort key để thu hẹp phạm vi dữ liệu cần đọc.
Partition key giúp DynamoDB xác định nhóm lưu trữ phù hợp; sort key giúp tìm đúng item hoặc một khoảng item đã được sắp xếp trong nhóm đó.
Nhờ vậy, request đi thẳng đến vùng dữ liệu cần đọc, thay vì quét toàn bộ table, nên giảm đọc thừa và giúp chi phí dễ đoán hơn.
Có thể hiểu đơn giản: PK/SK giúp DynamoDB đi đúng “ngăn tủ” trước, rồi mới lấy đúng “hồ sơ” bên trong; còn Scan thì giống như mở gần như cả tủ ra để tìm.

4. GetItem, Query và Scan

DynamoDB có nhiều cách đọc dữ liệu, nhưng dễ hiểu nhất là nhìn vào ba operation: GetItem, Query và Scan.
GetItem dùng khi đã biết chính xác item cần lấy. Nếu table chỉ có partition key thì gửi partition key. Nếu table có cả partition key và sort key thì phải gửi đủ cả hai. Đây là cách đọc trực tiếp nhất, giống như đã biết đúng số hồ sơ và đi thẳng tới hồ sơ đó.
Query dùng khi muốn lấy một nhóm dữ liệu có cùng partition key. Nếu có sort key, ta có thể giới hạn thêm, ví dụ lấy message bắt đầu bằng MESSAGE#, lấy đơn hàng trong một khoảng ngày, hoặc lấy item có sort key nằm giữa hai giá trị. Đây là cách đọc chính nên dùng khi thiết kế API.
Điểm dễ nhầm là FilterExpression không giúp DynamoDB đọc ít dữ liệu hơn. DynamoDB vẫn đọc dữ liệu theo key trước, rồi filter chỉ loại bớt kết quả trước khi trả về cho client. Vì vậy, key condition mới là phần quan trọng để giảm phạm vi đọc; filter không cứu được một thiết kế key chưa rõ ràng.
Scan là cách đọc rộng nhất. Nó đi qua toàn bộ table hoặc index theo từng trang, sau đó mới lọc nếu có filter. Scan có thể hữu ích cho việc quản trị, export dữ liệu, hoặc table nhỏ, nhưng không nên là cách đọc chính cho API có nhiều traffic vì dễ tốn chi phí và chậm.
Mặc định, DynamoDB đọc theo kiểu eventually consistent, nghĩa là dữ liệu vừa ghi xong có thể cần một chút thời gian rất ngắn để xuất hiện ở mọi nơi. Với table và LSI, có thể bật ConsistentRead=true nếu cần đọc chắc chắn dữ liệu mới nhất. Nhưng với GSI thì chỉ hỗ trợ eventually consistent, nên luồng vừa ghi xong rồi đọc ngay qua GSI cần được thiết kế cẩn thận.
5. Operational State Cho GenAI

Một ứng dụng GenAI không chỉ có prompt và embedding. Phía sau nó còn có rất nhiều dữ liệu vận hành: user nào đang có session nào, message nào được gửi trước, job nào đang chờ xử lý, job nào đang chạy, job nào thành công hoặc thất bại, request nào đã xử lý rồi để tránh chạy trùng, workflow đang dừng ở bước nào và dữ liệu tạm thời khi nào hết hạn.
Những dữ liệu này thường được đọc theo key rất rõ ràng. Ví dụ: lấy toàn bộ message của một session, lấy trạng thái của một job, tìm các job đang RUNNING, hoặc kiểm tra một request đã được xử lý chưa. Đây là nhóm bài toán DynamoDB làm khá tốt, từ POC đến production.
Ví dụ với chat, ta có thể lưu message theo session:
PK = SESSION#abc123 SK = MSG#timestamp
Như vậy, khi cần mở lại cuộc trò chuyện, app chỉ cần query theo SESSION#abc123 để lấy timeline message theo thứ tự.
Với job xử lý nền, ta có thể lưu:
PK = JOB#987 SK = STATE
Nếu worker hoặc dashboard cần tìm các job đang chạy, có thể tạo thêm index theo status, ví dụ STATUS#RUNNING kèm thời gian cập nhật gần nhất. Nhờ vậy hệ thống dễ tìm job cần xử lý mà không phải quét toàn bộ dữ liệu.
Tuy nhiên, DynamoDB không nên bị dùng cho mọi thứ. Tìm kiếm embedding giống nhau thuộc về vector search. File lớn như PDF, audio, prompt file hoặc output dài nên để ở S3; DynamoDB chỉ nên giữ đường dẫn, metadata và trạng thái xử lý. Báo cáo lớn trên toàn bộ lịch sử thì nên đưa sang data lake hoặc hệ thống analytics.
Nói ngắn gọn: DynamoDB rất hợp để giữ trạng thái vận hành của ứng dụng GenAI, nhưng một kiến trúc tốt là biết giao đúng loại dữ liệu cho đúng công cụ.
6. GSI Và LSI

GSI và LSI đều là index phụ trong DynamoDB. Có thể hiểu đơn giản: chúng tạo thêm một “đường đọc” khác ngoài primary key chính của table.
GSI dùng khi bạn cần đọc dữ liệu theo một cách hoàn toàn khác với key chính của table. GSI có thể có partition key và sort key riêng. Ví dụ table chính đang gom message theo user, nhưng bạn muốn tìm message theo status hoặc theo ngày, thì có thể cần GSI.
Điểm tiện của GSI là có thể thêm hoặc xóa sau khi table đã tồn tại. Tuy nhiên, dữ liệu trên GSI được cập nhật bất đồng bộ, nên sau khi ghi vào table chính, kết quả trên GSI có thể xuất hiện trễ một chút. Vì vậy, mọi lần đọc từ GSI đều là eventually consistent.
LSI thì khác. LSI vẫn giữ nguyên partition key của table chính, nhưng cho phép dùng một sort key khác. Nó phù hợp khi bạn vẫn muốn đọc dữ liệu trong cùng một nhóm partition key, nhưng sắp xếp hoặc lọc theo một cách khác.
Ví dụ table chính lưu message như sau:
PK = USER#42 SK = MSG#timestamp
Nếu vẫn muốn đọc message của USER#42, nhưng sắp xếp theo priority thay vì timestamp, LSI có thể dùng cùng PK = USER#42 nhưng sort key khác là priority.
Điểm bất tiện của LSI là phải tạo ngay từ lúc tạo table. Sau này muốn thêm hoặc sửa LSI thì thường phải tạo table mới và migrate dữ liệu. LSI cũng có giới hạn quan trọng: khi table có LSI, tổng dữ liệu của cùng một partition key, gọi là item collection, không được vượt 10 GB.
Nói ngắn gọn:
GSI dùng khi cần một cách đọc mới, có thể khác hẳn key chính, và có thể thêm sau.
LSI dùng khi vẫn đọc trong cùng partition key, nhưng muốn sort/filter theo một sort key khác, và phải thiết kế từ đầu.
Khi chọn GSI hay LSI, đừng bắt đầu từ tên công nghệ. Hãy bắt đầu từ câu hỏi: app cần đọc dữ liệu theo cách nào, có cần dữ liệu mới nhất ngay lập tức không, index có thể cần thêm sau này không, và dữ liệu trong một nhóm key có thể lớn đến đâu.
7. Khi Nào Chọn DynamoDB

Nên chọn DynamoDB khi ứng dụng cần đọc dữ liệu theo key rõ ràng và muốn tốc độ ổn định ở quy mô lớn. Ví dụ: lấy session của user, lấy message trong một cuộc trò chuyện, kiểm tra trạng thái job, lưu shopping cart, lưu trạng thái thiết bị, hoặc lưu request đã xử lý để tránh chạy trùng.
Điểm quan trọng không phải là “dữ liệu có dạng JSON”. Điểm quan trọng là app có biết rõ sẽ đọc dữ liệu bằng key nào hay không. Nếu biết rõ câu hỏi kiểu “lấy dữ liệu của user X”, “lấy message của session Y”, “lấy job đang RUNNING”, DynamoDB thường là lựa chọn tốt.
Nên chọn database quan hệ như PostgreSQL hoặc MySQL khi quan hệ giữa nhiều loại dữ liệu là phần quan trọng nhất. Ví dụ cần nhiều JOIN, cần ràng buộc chặt giữa các bảng, cần report linh hoạt, hoặc hôm nay hỏi dữ liệu kiểu này nhưng ngày mai có thể đổi sang kiểu khác. Nếu access pattern chưa rõ, relational database thường dễ bắt đầu hơn.
Trong hệ thống thật, không nhất thiết chỉ chọn một database. Một ứng dụng GenAI có thể dùng DynamoDB để lưu session, message timeline, job status và idempotency; dùng vector database để tìm kiếm theo ý nghĩa; dùng S3 để lưu file lớn; dùng warehouse để phân tích; và dùng relational database cho dữ liệu nghiệp vụ cần constraint rõ ràng.
Nếu mới học, cách tốt nhất là làm một lab nhỏ. Viết ra 5 câu hỏi app cần trả lời, thiết kế PK/SK cho từng câu, thêm vài item mẫu, rồi thử GetItem, Query và Scan. Sau đó tự hỏi: request này đi bằng key nào, nó đọc bao nhiêu dữ liệu, và nếu cách query thay đổi thì thiết kế key có phải đổi không.
Khi trả lời được ba câu đó, bạn sẽ hiểu DynamoDB đúng hơn nhiều so với việc chỉ nhớ định nghĩa.