Amazon DynamoDB 初步使用心得

fcamel
fcamel的程式開發心得
9 min readJun 26, 2020

--

先前寫過關於 DynamoDB 如何運作,這篇記錄一些使用後的零散心得。

Single-Table Design

上篇有介紹過 single-table design,後來有看到別人提供不錯的分析:

綜括來說,需要透過寫入 pre-join data 加速讀取時,用 single-table design 才有意義。

附帶一提,《Distributed Key-Value Database》將 relational database 轉變到 scalable key-value database 的需求和設計取捨寫得很清楚,推薦一讀。

Scalability

單一節點的上限是 3,000 RCU 和 1,000 WCU (read/write capacity unit, 先粗略當作 read/write per second 看吧):

DynamoDB supports your access patterns using the throughput that you provisioned as long as the traffic against a given partition does not exceed 3,000 RCUs or 1,000 WCUs.

先前的介紹所述,寫入 DynamoDB 的資料會自動寫到三個不同 Availability Zone,所以讀是寫的 3x 效能,滿合理的。

若資料有分散到不同 primary key,throughput 沒有上限。我實測的結果,很輕鬆達到 10,000 WCU,一兩天就完成大量搬移資料的工作。但要留意 throughput 沒有上限不表示可以一口氣狂讀狂寫,DynmoDB 需要時間準備更多節點處理忽然提升的請求 (後述)。

官方宣稱 latency 是 ms 為單位,我沒有實測。整體來說,throughput 和 latency 效能都相當地好。

Consumed Units 和價錢的算法

這部份內容太多了,拆成另一篇《Amazon DynamoDB 的 Consumed Units 注意事項》

Auto Scaling 的原理

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AutoScaling.html

DynamoDB 的使用數據會送往 CloudWatch。啟用 auto scaling 後,AWS 會自動加上 CloudWatch Alarms,然後在觸發 alarms 後作對應的動作。比方說達到目標使用率超過兩分鐘,就調高 provisioned capacity。

確切的規則可以在 CloudWatch 的 Alarms 裡看到。預設會 hide auto scaling alarms,要關掉才會看到規則。下面是兩個例子:

  • ConsumedWriteCapacityUnits > 12345 for 2 datapoints within 2 minutes
  • ConsumedWriteCapacityUnits < 12345 for 15 datapoints within 15 minutes

理論上若覺得 auto scaling 規則不好用,應該可以自己用一樣的機制調整。

Global/Local Secondary Index

DynamoDB 的設計讓人充份意識到 index 的成本,因為 index 的空間要收錢,讀寫 index 也要收錢。寫入原本就比較貴了,寫入時會觸發更新一個 index 的話,寫入就變兩倍費用了。

LSI 唯一贏 GSI 的好處是 consistency,但限制較多,官方不推薦使用。就此略過。

新增 GSI 後會在背景補 index 舊資料

Part of this operation involves backfilling data from the table into the new index. During backfilling, the table remains available. However, the index is not ready until its Backfilling attribute changes from true to false. You can use the DescribeTable action to view this attribute.

Table 的 PK + SK 必須 unique,但 GSI 的 PK + SK 不用

In a DynamoDB table, each key value must be unique. However, the key values in a global secondary index do not need to be unique.

大致上用 GSI 滿直覺的,唯一要注意的是它是 eventually consistent,不像 RDBMS 更新資料後,可以假設接下來可以用 index 查詢可取得新狀態。不過正常情況,應該也是 10 ~ 100 ms 為單位更新好 GSI 吧。

Pagination (Offset or Continuation Key)

DynamoDB 回傳資料後,會帶 LastEvaluatedKey,用作下次查詢用的 ExclusiveStartKey,藉此作到 pagination。不支援 offset 跳過多筆資料,只能 app 自行模擬,但是 query 查到的資料一樣會收費,不論是否有傳回給 app。因此,app 若想模擬 offset,既沒效率又會多花錢,最好功能上避開這種需求。

Batch Update

若是單筆寫入,AWS SDK 會在收到 provisioned throughput exception 時自動重試,BatchWriteItem 應該也是如此。但是,只有在 BatchWriteItems 全部 items 失敗時才算失敗。通常是部份成功,app 要檢查回傳的 UnprocessedItems,自行重試。要注意 BatchWriteItem 時常有 UnprocessedItems,沒處理肯定會掉資料的。

此外,BatchWriteItem 最多只能帶 25 筆,還有一些其它限制。

DynamoDB Local

官方有提供 DynamoDB Local 供開發測試用,有裝 docker 的話,用法很簡單:

$ docker run -p 8000:8000 amazon/dynamodb-local
Initializing DynamoDB Local with the following configuration:
Port: 8000
InMemory: true
DbPath: null
SharedDb: false
shouldDelayTransientStatuses: false
CorsParams: *

可以連 localhost:8000/shell/ 使用 JavaScript console,有簡單的教學和 sample code,方便快速上手。

DynamoDB Local 有些行為和 DynamoDB 不同,因此有人寫了另一套更接近 DynamoDB 行為的 dynalite。不確定現在何者的行為比較接近 DynamoDB,不過 dynalite 有附原始碼,必要時可以自己補強一下。

Cache (DAX)

DynamoDB Accelerator Accelerator (DAX) 是 DynamoDB 的 cache。它作用於 DynamoDB 之外,需要另外收費,app 也需略作調整。

DAX 運作原理很簡單,就是在 DynamoDB 前加一層 proxy:

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.concepts.html
  • DAX 有提供 cluster,一個 primary 和數個 read replica。
  • 有 read/write through,但是設計上維持 DynamoDB 一貫甩鍋精神:作好基礎建設,然後留一些雷給使用者。

DynamoDB 取資料的方式有 GetItem、Query 和 Scan。Query 可以下比較複雜的條件, 像是依 sort key 取某個 range 內的資料。DAX 將資料分開存入 item cache 和 query cache。兩者都是 LRU cache,有各自 default TTL,可提早清資料。

兩者都是 read through cache。item cache 有作 write through。但 query cache 沒有。也就是說,query cache 內的資料不會主動被 invalidated。加上 app 可能為了縮短 write latency 而繞過 DAX 寫入 DynamoDB,這也會造成 cache dirty。結論是除非 app 不在意 query cache dirty,不然不適合用 query cache。

結語

整體來說,DynamoDB 設計一致性很好,功能簡單、容易預測行為。但 table schema 和 index 設計大概需要些經驗才能掌握得當。

DynamoDB 基本精神是 scalability first,任何可能影響 scale out 的設計取舍,答案很簡單,就是往 scale out 的方向走:

  • Batch update?單次 items 數有上限 (只有25!)、每筆請求大小有上限、來不及處理就部份不處理,有進度就好,app 要自行留意補處理。
  • 批次取資料?回傳大小有上限,留 last evaluted key 讓 app 能繼續取資料就好。
  • 支援 offset?效率不佳,不支援。反正 app 可用 last evaluted key 模擬出來。
  • cache invalidation?作不到 scale out,app 自行處理。
  • 後台提備 99 percentile?不易作好 scale out,提供 min/max/average 即可。

在這樣的取捨下,得到的是沒有上限的 scalability。雖然吐嘈 DynamoDB 老是甩鍋,我滿欣賞這樣的設計:有個明確的目標,將它作到極致。

--

--