Index là một trong những khái niệm quan trọng nhất của Amazon DynamoDB mà bạn cần nắm vững cho kỳ thi AWS DVA-C02. Indexes cho phép bạn truy vấn dữ liệu bằng các thuộc tính khác ngoài khóa chính, cải thiện đáng kể hiệu suất truy vấn và linh hoạt trong ứng dụng của bạn. Trong bài viết này, chúng ta sẽ đi sâu vào các loại index trong DynamoDB, cách chúng hoạt động, khi nào nên sử dụng chúng, và các cân nhắc quan trọng cho kỳ thi.
Tổng quan về Indexes trong DynamoDB
Trước khi đi sâu vào chi tiết từng loại index, hãy hiểu tại sao chúng tôi cần indexes trong DynamoDB:
- DynamoDB là cơ sở dữ liệu NoSQL, được tối ưu hóa cho việc truy vấn dữ liệu bằng khóa chính
- Nếu không có indexes, truy vấn dữ liệu dựa trên các thuộc tính khác sẽ yêu cầu Table Scan – một hoạt động tốn kém và không hiệu quả
- Indexes cung cấp đường dẫn truy cập thay thế để truy vấn dữ liệu mà không cần thay đổi cấu trúc bảng gốc
Các loại Index trong DynamoDB
DynamoDB cung cấp hai loại index chính:
- Local Secondary Index (LSI)
- Global Secondary Index (GSI)
Hãy phân tích từng loại chi tiết:
1. Local Secondary Index (LSI)
LSI là một index có cùng partition key với bảng gốc nhưng có sort key khác. Đây là tính chất “local” – index được giới hạn trong cùng một partition với bảng chính.
Đặc điểm quan trọng của LSI:
- Thời điểm tạo: LSI phải được tạo khi bảng được tạo lần đầu tiên. Bạn không thể thêm, xóa hoặc sửa đổi LSI sau khi bảng đã tồn tại.
- Giới hạn: Mỗi bảng DynamoDB có thể có tối đa 5 LSI.
- Dung lượng: LSI chia sẻ dung lượng throughput (RCU/WCU) với bảng gốc.
- Dữ liệu: LSI có thể được cấu hình để chỉ chứa một số thuộc tính từ bảng gốc (projected attributes).
- Ràng buộc kích thước: Tổng kích thước của một item và tất cả các bản sao LSI của nó không được vượt quá 10GB.
- Tính nhất quán: Hỗ trợ cả truy vấn nhất quán mạnh (strongly consistent) và nhất quán cuối cùng (eventually consistent).
Khi nào nên sử dụng LSI:
- Khi bạn cần truy vấn nhất quán mạnh (strongly consistent reads)
- Khi các truy vấn của bạn luôn bao gồm cùng một partition key nhưng với các tiêu chí sort key khác nhau
- Khi bạn có thể xác định tất cả các mẫu truy vấn tại thời điểm tạo bảng
Ví dụ LSI:
Giả sử bạn có một bảng Orders
với partition key là CustomerId
và sort key là OrderId
. Bạn có thể tạo một LSI với cùng partition key CustomerId
nhưng sử dụng OrderDate
làm sort key để truy vấn đơn hàng của khách hàng theo ngày.
Bảng: Orders
PK: CustomerId
SK: OrderId
LSI-OrderDate:
PK: CustomerId (giống với bảng chính)
SK: OrderDate (khác với bảng chính)
2. Global Secondary Index (GSI)
GSI là một index có thể có partition key và sort key khác hoàn toàn với bảng gốc. “Global” ở đây nghĩa là index có thể truy vấn trên tất cả các partitions của bảng chính.
Đặc điểm quan trọng của GSI:
- Thời điểm tạo: GSI có thể được tạo, sửa đổi hoặc xóa bất cứ lúc nào, ngay cả sau khi bảng đã tồn tại.
- Giới hạn: Mỗi bảng DynamoDB có thể có tối đa 20 GSI.
- Dung lượng: GSI có dung lượng throughput (RCU/WCU) riêng, tách biệt với bảng gốc.
- Dữ liệu: Giống như LSI, GSI cũng có thể được cấu hình để chỉ chứa một số thuộc tính từ bảng gốc.
- Tính nhất quán: GSI chỉ hỗ trợ truy vấn nhất quán cuối cùng (eventually consistent).
- Phân phối lại dữ liệu: GSI phân phối lại dữ liệu của bảng gốc dựa trên partition key của index.
Khi nào nên sử dụng GSI:
- Khi bạn cần truy vấn dữ liệu bằng các thuộc tính hoàn toàn khác với khóa chính của bảng
- Khi bạn cần thêm mới các mẫu truy vấn sau khi bảng đã được tạo
- Khi bạn có thể chấp nhận truy vấn nhất quán cuối cùng (eventually consistent)
Ví dụ GSI:
Tiếp tục với ví dụ bảng Orders
, bạn có thể tạo một GSI với partition key là ProductId
để truy vấn tất cả các đơn hàng theo sản phẩm.
Bảng: Orders
PK: CustomerId
SK: OrderId
GSI-ProductId:
PK: ProductId (khác với bảng chính)
SK: OrderDate (tùy chọn)
Chiến lược chiếu thuộc tính (Attribute Projection)
Khi tạo index, bạn cần quyết định thuộc tính nào từ bảng gốc sẽ được “chiếu” (projected) vào index. DynamoDB cung cấp ba tùy chọn:
- KEYS_ONLY: Chỉ khóa chính của bảng gốc và khóa của index được lưu trữ trong index.
- INCLUDE: Chỉ định một danh sách thuộc tính bổ sung để lưu trữ trong index.
- ALL: Tất cả các thuộc tính từ bảng gốc được sao chép vào index.
Việc chọn chiến lược chiếu thuộc tính phù hợp có tác động lớn đến hiệu suất và chi phí:
- KEYS_ONLY: Tiết kiệm không gian lưu trữ nhất, nhưng yêu cầu truy vấn bổ sung đến bảng gốc nếu bạn cần thuộc tính khác.
- INCLUDE: Cân bằng giữa chi phí lưu trữ và hiệu suất truy vấn.
- ALL: Chi phí lưu trữ cao nhất nhưng hiệu suất tốt nhất vì không cần truy vấn bổ sung đến bảng gốc.
Mẫu thiết kế Index phổ biến
1. Sparse Index
Sparse index là index chỉ chứa các items từ bảng gốc mà có thuộc tính được định nghĩa làm khóa cho index. Các items không có thuộc tính này sẽ không xuất hiện trong index.
Ví dụ:
Trong bảng Users
, không phải tất cả người dùng đều có thuộc tính PremiumMember
. Bạn có thể tạo GSI với PremiumMember
làm partition key để nhanh chóng tìm tất cả người dùng cao cấp mà không phải quét toàn bộ bảng.
2. Overloading GSI
Kỹ thuật này liên quan đến việc sử dụng một GSI cho nhiều mục đích bằng cách tận dụng tiền tố trong khóa.
Ví dụ:
GSI-Status-Date:
PK: "ORDER#status" hoặc "RETURN#status"
SK: date
Với thiết kế này, bạn có thể truy vấn cả đơn hàng và đơn trả hàng theo trạng thái sử dụng cùng một GSI.
3. Index Write Sharding
Khi bạn cần phân phối đều các ghi lên GSI để tránh hot partition.
Ví dụ:
Thay vì sử dụng status
trực tiếp làm partition key của GSI, bạn có thể thêm một số ngẫu nhiên:
GSI-PK: "status#1", "status#2", "status#3"...
So sánh LSI và GSI
Đặc điểm | Local Secondary Index | Global Secondary Index |
---|---|---|
Partition Key | Phải giống với bảng gốc | Có thể khác với bảng gốc |
Sort Key | Khác với bảng gốc | Tùy chọn, có thể khác với bảng gốc |
Thời điểm tạo | Chỉ khi tạo bảng | Bất cứ lúc nào |
Số lượng tối đa | 5 | 20 |
Tính năng nhất quán | Strongly consistent và eventually consistent | Chỉ eventually consistent |
Dung lượng throughput | Chia sẻ với bảng gốc | Riêng biệt |
Giới hạn kích thước | 10GB cho mỗi partition key | Không giới hạn |
Vấn đề thường gặp với Indexes
1. Throttling
GSI có dung lượng throughput riêng. Nếu bạn không cung cấp đủ WCU cho GSI, các hoạt động ghi trên bảng gốc có thể bị throttled ngay cả khi bảng gốc có đủ dung lượng.
2. Eventual Consistency
Sự đồng bộ hóa giữa bảng gốc và GSI là eventually consistent. Có thể có độ trễ giữa khi dữ liệu được ghi vào bảng gốc và khi nó xuất hiện trong GSI.
3. Projection Cost
Chiếu nhiều thuộc tính vào index (ALL) sẽ tiêu tốn nhiều không gian lưu trữ hơn, nhưng có thể cải thiện hiệu suất truy vấn.
Chiến lược tối ưu hóa Index
1. Choose the Right Key Schema
Thiết kế partition key để phân phối đều lưu lượng và tránh hot partitions.
2. Minimize Attribute Projections
Chỉ chiếu các thuộc tính cần thiết để giảm chi phí lưu trữ và cải thiện hiệu suất ghi.
3. Use Auto Scaling
Đặc biệt quan trọng đối với GSI để đảm bảo chúng có đủ dung lượng để xử lý lưu lượng đỉnh.
4. Monitor Your Indexes
Sử dụng CloudWatch để theo dõi các metrics như ConsumedReadCapacityUnits, ConsumedWriteCapacityUnits và ThrottledRequests.
Các câu hỏi thường gặp trong kỳ thi DVA-C02
- Khi nào nên sử dụng LSI so với GSI?
- LSI: Khi cần truy vấn strongly consistent và các truy vấn của bạn luôn bao gồm cùng một partition key
- GSI: Khi cần truy vấn theo thuộc tính khác với partition key của bảng gốc
- Điều gì xảy ra khi một GSI bị throttled?
- Nếu một GSI không có đủ WCU, các hoạt động ghi vào bảng gốc cũng sẽ bị throttled
- Tại sao không thể thêm LSI sau khi tạo bảng?
- LSI được phân phối dựa trên partition key của bảng gốc, vì vậy việc thêm LSI mới sẽ yêu cầu phân phối lại toàn bộ dữ liệu
- Làm thế nào để giảm chi phí khi sử dụng indexes?
- Sử dụng projection KEYS_ONLY hoặc INCLUDE với danh sách thuộc tính tối thiểu cần thiết
- Sử dụng GSI sparse để giảm số lượng items trong index
- Làm thế nào để truy vấn bảng chính và GSI cùng một lúc?
- Không thể trong một hoạt động duy nhất, bạn cần thực hiện các truy vấn riêng biệt
Các ví dụ AWS CLI để làm việc với Indexes
1. Tạo bảng với LSI
aws dynamodb create-table \
--table-name Orders \
--attribute-definitions \
AttributeName=CustomerId,AttributeType=S \
AttributeName=OrderId,AttributeType=S \
AttributeName=OrderDate,AttributeType=S \
--key-schema \
AttributeName=CustomerId,KeyType=HASH \
AttributeName=OrderId,KeyType=RANGE \
--local-secondary-indexes \
"IndexName=OrderDateIndex,KeySchema=[{AttributeName=CustomerId,KeyType=HASH},{AttributeName=OrderDate,KeyType=RANGE}],Projection={ProjectionType=INCLUDE,NonKeyAttributes=[Status,Amount]}" \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
2. Tạo GSI cho bảng hiện có
aws dynamodb update-table \
--table-name Orders \
--attribute-definitions \
AttributeName=Status,AttributeType=S \
AttributeName=OrderDate,AttributeType=S \
--global-secondary-index-updates \
"[{\"Create\":{\"IndexName\":\"StatusDateIndex\",\"KeySchema\":[{\"AttributeName\":\"Status\",\"KeyType\":\"HASH\"},{\"AttributeName\":\"OrderDate\",\"KeyType\":\"RANGE\"}],\"Projection\":{\"ProjectionType\":\"ALL\"},\"ProvisionedThroughput\":{\"ReadCapacityUnits\":5,\"WriteCapacityUnits\":5}}}]"
3. Truy vấn sử dụng GSI
aws dynamodb query \
--table-name Orders \
--index-name StatusDateIndex \
--key-condition-expression "Status = :status AND OrderDate BETWEEN :date1 AND :date2" \
--expression-attribute-values '{":status":{"S":"SHIPPED"}, ":date1":{"S":"2023-01-01"}, ":date2":{"S":"2023-01-31"}}'
Kết luận
Hiểu sâu về các loại index trong DynamoDB là rất quan trọng cho kỳ thi AWS DVA-C02. Đúng index có thể cải thiện đáng kể hiệu suất ứng dụng của bạn trong khi giảm chi phí. Hãy nhớ:
- LSI phù hợp khi bạn cần truy vấn strongly consistent read và có thể xác định tất cả các mẫu truy vấn tại thời điểm tạo bảng
- GSI phù hợp cho hầu hết các trường hợp sử dụng do tính linh hoạt của chúng
- Thiết kế partition key phù hợp để phân phối đều lưu lượng
- Chọn chiến lược projection phù hợp để cân bằng giữa chi phí và hiệu suất
- Giám sát metrics để tránh throttling
Bằng cách nắm vững các khái niệm này và thực hành với các ví dụ thực tế, bạn sẽ chuẩn bị tốt cho các câu hỏi liên quan đến indexes trong kỳ thi AWS DVA-C02.