Amazon DynamoDB 概念筆記

fcamel
fcamel的程式開發心得
14 min readMar 16, 2020

這篇是看了一些 DynamoDB 概念後的筆記,了解它底層運作方式和特色。

需求分析

Designing Data-Intensive Applications 提到分散式系統的需求來分析:

  • Reliable: 寫入時會用同步寫到三個不同 availability zone 的 storage node (異地備援)。也有提供 snapshot/backup 到 S3。Auto admin 會自動作 failover 換 leader。
  • Scalable: primary key 是必要欄位,依 primary key 分 partition,並且強制每個 partition 的資料上限。由此保證系統提供穩定的高效能。不提供複雜 SQL 的語法,也就不用耗費大量計算處理複雜的 SQL (no feature, no issue!)。
  • Maintainable: AWS 以 serverless 的方式提供 DynamoDB 服務,使用時不需人力維護系統。DynamoDB 內有個 auto admin,如同 DBA 一樣會自動修復壞掉的節點、加減機器,作許多 DBA 手動處理的事。使用者只需設定每秒讀寫次數的範圍,上限愈高表示 AWS 要佈愈多機器待命,費用也會愈高。

DynamoDB 解決了這三個困難的需求,開發者可專注在使用 DynamoDB 的 design patterns 處理 business logic。

Database 功能

憑印象記錄幾點:

  • read 預設是 eventually consistency,會從三個 storage node 之一讀資料; 可要求 strong consistency,此時會從 leader 讀資料。
  • 提供 local 和 global secondary index (兩者的意思見這裡)。
  • 資料存在同個 region 不同 availability zone。可以加設 Global Table 跨 regions 同步資料。有 conflicts 時採用 last writer win (LWW)。要注意不同機器時間無法完全同步,所以 LWW 不見得會反映真實世界發生順序。
  • 只能用 primary key 更新資料。
  • 提供 DynamoDB Streams 讀取資料變化的過程,配合 AWS Lamda 可作到 database trigger 的效果。

整體感覺是著重效能,可接受短時間讀到不一致的資料 (eventually consistent reads + global indexes + LWW)。若使用者偏好 strong consistency,也提供對應的方案 (read from leader 和 local index)。

我原本是偏好 strong consistency,看了 DynamoDB 的取捨後,想想這樣比較合理,如同 CPU 設計選擇不保證 sequential consistent 一樣。

如上圖所示:

  • 寫入資料會透過 request router 導到 primary key 對到的 storage node 的 leader。leader 寫入兩個不同 availability zone 的 replicas 後,才會回報 client 寫入成功(synchronous replication)。
  • leader 和 replicas 之間會用 heartbeat 偵測,太久沒回應會作 failover。

如上圖所示,會用 primary key 的 hash 分 partition。

leader 收到更新時,會將更新存成 replication log,Audo Admin 偵測有 storage node 掛掉時,會用 snapshot + replication log 重建一個 node。replication log 同時用作 DynamoDB Streams、Global Table 互相通知更新的格式。

整體來說最關鍵的設計可能是採用 single leader + 2 replicas + 限制 shard 的資料上限 (10GB)?剩下的功能是基於這些限制疊出來的。此外,auto admin 實作想必很複雜。

限流機制

https://www.slideshare.net/AmazonWebServices/amazon-dynamodb-under-the-hood-how-we-built-a-hyperscale-database-dat321-aws-reinvent-2018

採用 token bucket algorithm 限流,概念是定期會補 tokens 給 DynamoDB client,tokens 用完了就會暫停服務,達到限流效果。

和使用 moving average 在超出上限後限流有一點不同,token bucket 可以在低於平均用量時「儲存」 一定 tokens,之後忽然有個爆衝時,可以用先前儲存的 tokens 應付超出設定平均值的流量。DynamoDB 允許儲存最多五分鐘未用的額度。

除 token bucket algorithm 應付一時爆衝外,DynamoDB 還有同時考慮不同 partition 的負擔,讓負載重的 partition 可以用負載輕的 partition 的 tokens。但若全部都很重時,就需要調 tokens 上限 (read/write capacity unit, RCU/WCU)。人工調整很累,DynamoDB 有提供 auto scaling,可在一個範圍內自動調整,幫忙節省用戶的錢。

設計流程和 Design Patterns

以下是 AWS re:Invent 2019: Data modeling with Amazon DynamoDB (CMY304) 的筆記。 AWS re:Invent 2018: Amazon DynamoDB Deep Dive: Advanced Design Patterns for DynamoDB (DAT401) 也值得一看,這裡就不提了。

設計流程是

  1. 建立 Entity Relation Diagram (ERD)
  2. 定義 access patterns
  3. 設計 primary keys 和 secondary indexes

比較不直覺的地方是「只使用一個 table」存所有資料,並且在寫入資料時產生 access patterns 要的結果。如果無法有效率地用 primary key 取得,就用 secondary indexes,但是使用 secondary indexes 的方法要配合一些特殊技巧,看例子比較好懂。

問題描述

設計符合電子商店下單的資料庫。下圖是 ERD:

https://www.slideshare.net/AmazonWebServices/data-modeling-with-amazon-dynamodb-adb301-new-york-aws-summit

下圖是 table 用的屬性和儲存資料的例子:

之後會存不同型別的 PK (primary key) 和 SK (sort key),所以將型別編碼在資料裡。這個例子使用 composite primary key = primary key + sort key,此時會用 primary key 作 partition,每個 partition 內用 sort key 排序資料。

One-to-many relationship: 收貨地址

使用者可以有多個出貨地址,用 JSON 存在一個屬性裡。

One-to-many relationship: 使用者的訂單

可以有多筆訂單,用不同的 sort key 表示。

此時 composite primary key 有 (USER#…, #PROFILE#…) 和 (USER#…, ORDER#…) 兩種,存在同一個 table 裡。查詢時可依型別區分,不會因為存在同一個 table 而分不清楚。查詢特定使用者訂單的方法像這樣:

"PK = USER#alexdebrie AND BEGINS_WITH(SK, 'ORDER#')"

One-to-many relationship: 貨品的訂單

如上圖所示,從商品出發的 one-to-many relationship 可用類似方法記。composite primary key 多了一種 (ITEM#…, ORDER#…)。綜合上述的資料,下表總結四種 entities 在同一個 table 裡的表示方法:

https://www.slideshare.net/AmazonWebServices/data-modeling-with-amazon-dynamodb-adb301-new-york-aws-summit

從 order 出發查詢: Inverted Index

建一個 Global Secondary Index (GSI) 將 PK/SK 互調,滿足用 order ID 查詢的需求。

依屬性過濾特定使用者的訂單

作法為

  1. 將屬性 Status 和 CreatedAt 合併成 OrderStatusDate,形成具唯一性的屬性
  2. 建立 GSI 用 OrderStatusDate 當 SK

結果如下圖所示:

LSI vs. GSI

https://www.slideshare.net/AmazonWebServices/amazon-dynamodb-deep-dive-advanced-design-patterns-for-dynamodb-dat401-aws-reinvent-2018pdf

另一位講者有提到 Local Secondary Index (LSI) 是換 sort key,所以這個例子應該也可以用 LSI?

找出所有特定狀態的訂單 (Sparse Index)

廠商要找出所有未出貨的訂單,此時要用 Status 查詢所有使用者的所有訂單,也就是要用 many-to-many relationship 的屬性過濾資料。

這個例子最不直覺,雖然看到解法後就變「直覺」了。既然只能用 primary key 或 composite primary key 查詢,就將 status 設為 index 的 primary key。但是 status 的值太少了,這樣會讓單一 partition 有太多資料,所以產生新的屬性用不同值表示 Status 的同一個值。

結果如下圖,針對 Status 的 “PLACED” 產生 PlacedId 的屬性,想個方法算出不會重復的值即可。這樣讀寫都會散到不同 storage node。

注意,和 relational database 建立 Status 的 index 相比,這裡只有針對 Status 特定的值 (PLACED) 建index。事先要考慮好查詢的方式,並且在使用 partition 的前提下設計 index。

結語

從最上層的概念來看,DynamoDB 提供兩大特色:

  • 提供適當的抽象介面,引導開發者在寫入資料時同時考慮讀取的效能,達到讀寫效能的平衡。
  • Serverless 出發的管理介面,最小化維護資料庫的人力成本 (只需設定 RCU/WCU)。

設計取捨上,DynamoDB 為了提升 scalability,只提供侷限的功能,像是限制 partition 最大 10GB,才能快速複製到其它地方。

在這樣的限制下,可以穩定地提供高效能的讀寫,足夠使用的 high availability 和 durability (三處異地備份)。

看過前面的使用例子後,比較能理解下表的意思。

https://www.slideshare.net/AmazonWebServices/amazon-dynamodb-deep-dive-advanced-design-patterns-for-dynamodb-dat401-aws-reinvent-2018pdf

比較大的困難是需要事先知道讀資料的模式,才能設計對應的 schema 和index,以便在寫入時產生可快速讀取的資料。不確定事後更改的成本如何。

relational database 設計相對「無腦」一些,照 Entity Relation Diagram 正規化,用起來很有彈性。兩者對彈性和效能的取捨不同,這部份得有實戰經驗才好依個案分析利弊了。

相關文章

--

--