iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
自我挑戰組

30 天的 SAA 學習筆記系列 第 13 篇

Day 13 - 運算與儲存核心 DynamoDB:NoSQL 思維與分區鍵設計

  • 分享至 

  • xImage
  •  

🗃️ DynamoDB 是什麼

DynamoDB 是 AWS 的全受管 NoSQL 資料庫。跟上一篇的 RDS 並排看,差別最清楚:

RDS(關聯式) DynamoDB(NoSQL)
資料長相 固定 schema,每一列欄位都一樣 key-value/文件型,除了主鍵,欄位自由
名詞 表格/列/欄 table/item/attribute
跨表查詢 用 JOIN 兜資料 幾乎不支援 JOIN
擴展 換更大的 instance、加 Read Replica 容量自動擴展,任何規模都維持個位數毫秒延遲

設計順序是反過來的: RDS 先設計表格關聯,查詢時再 JOIN;DynamoDB 先列出應用程式所有的查詢情境,再決定 partition key 和 sort key——key 選錯了,後面很難用索引補救。這篇往下的熱分區、Query 和 Scan、兩種索引、容量單位,都是這句話的後果。

同一張 table 裡的 item 不用長得一樣,一筆訂單有 5 個 attribute、另一筆有 4 個,完全可以:

Table: Orders
{ "userId": "u-1001", "orderTime": "2026-09-18T10:22:00", "total": 1280, "status": "PAID",  "coupon": "AUG20" }
{ "userId": "u-1001", "orderTime": "2026-09-19T08:05:00", "total": 340,  "status": "SHIPPED" }
{ "userId": "u-2042", "orderTime": "2026-09-19T09:41:00", "total": 99,   "status": "PAID",  "gift": true }
   └── partition key ──┘└──── sort key ────────┘

唯一一定要有的是主鍵:這張表選 userId 當 partition key、orderTime 當 sort key。單一 item 上限 400KB。


🔑 分區鍵設計:DynamoDB 的核心思維

每張表都要有 partition key(分區鍵)。DynamoDB 用它的值做雜湊,決定 item 存在哪個實體分區——自動、看不到,但決定了效能。

設計目標只有一個:讓請求平均分散到各分區。 請求集中在同一個 key 值,單一分區會被打滿而節流(throttle),整張表容量再寬裕也沒用——這叫熱分區(hot partition)。

壞的 partition key:orderDate(值只有「今天」「昨天」……)
  2026-09-18  ████████████████████████  ← 尖峰時所有寫入全擠在這一個分區 → throttle
  2026-09-17  ██
  2026-09-16  █

好的 partition key:userId(值有幾十萬種,天然分散)
  u-1001  ███      u-2042  ██      u-3310  ███      u-4177  ██   ……

判斷一個欄位適不適合當 partition key,看兩件事:

  • 值夠不夠多:使用者 ID、裝置 ID 這種值很多的欄位才適合;訂單狀態(只有 5 種)、日期、國家都是壞 key
  • 流量會不會集中:值再多,如果 90% 的請求都打同一個熱門商品,一樣是熱分區

還可以加一個 sort key(排序鍵),兩者合稱複合主鍵:同一個 partition key 下可以放多個 item,依 sort key 排序。Orders 表的 userId + orderTime 就是這樣,同一個使用者的訂單排在一起、照時間排好。


🔎 Query 和 Scan:兩種讀法差一百倍

Partition key 設計好壞,直接反映在讀資料的方式上:

Query Scan
怎麼找 給 partition key(可以再加 sort key 的條件),直接跳到那個分區拿 從頭到尾讀整張表,一筆一筆比對
讀多少資料 只讀符合的那些 item 整張表全部讀一遍
消耗的容量 只算讀到的 整張表的大小,就算最後只留下 3 筆
例子 「u-1001 在 9 月的訂單」 「所有 status = PAID 的訂單」(沒有以 status 為 key 的索引時)
  • Query 的 partition key 只能用等於;sort key 可以用範圍(between、begins_with)
  • Scan 加過濾條件(filter expression)不會省錢:過濾是讀完才做,容量照整張表算
Scan + filter「status = PAID」
  讀取  10,000 筆  ████████████████████  ← 容量照這個算
  回傳       3 筆  █

只能靠 Scan 的查詢,通常代表 key 設計漏了這個情境——補救方法就是下一段的索引。


🗂️ 兩種索引:LSI vs GSI

Partition key 只能拿來查「等於某個值」,如果應用程式常常要用別的欄位查資料,就得靠次要索引:

Local Secondary Index(LSI) Global Secondary Index(GSI)
Partition key 跟原表一樣 可以換成別的欄位
建立時機 只能在建表當下一起定義,事後不能加 隨時可以新增或刪除
容量 跟原表共用 自己獨立的容量設定
讀取一致性 可以選強一致性 只能最終一致性
單一 partition key 值的索引項目上限 10 GB 沒有限制
  • LSI 只能建表時定義,已經在跑的表加不了;GSI 隨時可加
  • GSI 只能最終一致:它其實是背後另一張表,資料非同步複製過去;LSI 跟原表在同一個分區,才做得到強一致

https://ithelp.ithome.com.tw/upload/images/20260927/20150978cYMCwBBRVY.jpg

套回 Orders 表:想依訂單狀態查,就開 GSI(status + orderTime)。但 status 只有幾種值,GSI 本身會變熱分區,常見解法是改用 status#2026-09 這種組合 key 把值打散。


📏 容量單位:讀寫怎麼計價

DynamoDB 的效能和費用都用兩個單位算:

單位 一個單位等於 換算
RCU(read capacity unit) 每秒 1 次強一致性讀取,item 最大 4 KB 最終一致性讀取只算一半:1 RCU = 每秒 2 次;item 超過 4 KB 就按 4 KB 一份往上算
WCU(write capacity unit) 每秒 1 次寫入,item 最大 1 KB item 3 KB 的一次寫入 = 3 WCU

例:每秒讀 100 次 2 KB 的 item,強一致性要 100 RCU,最終一致性只要 50 RCU。

兩者差在哪(跟 Day 12 的 replica lag 類似):每筆資料在底層存三份,寫入時兩份確認就回成功。

寫入 total = 500
  副本 A  500 ✔ ┐
  副本 B  500 ✔ ┘ 兩份確認 → 回「寫入成功」
  副本 C  300     ← 還在追,通常 1 秒內追上

最終一致性讀取:可能剛好讀到 C → 拿到舊的 300    0.5 RCU
強一致性讀取:  保證拿到最新的 500               1 RCU
  • 最終一致性:便宜一半,允許短暫讀到舊資料;DynamoDB 預設用這個
  • 強一致性:一定最新,但貴一倍,而且 GSI 不支援

容量怎麼買,有兩種模式:

Provisioned On-Demand
怎麼設 自己填每秒要多少 RCU/WCU,可以加 Auto Scaling 讓它在上下限之間自動調 什麼都不填,用多少算多少
超過會怎樣 超過設定值就 throttle(有一點 burst 額度緩衝) 自動撐住,幾乎不會 throttle
單價 便宜,適合流量穩定、可預測 每次請求的單價比較高,適合流量忽高忽低、難預測、或剛上線不知道量

兩種模式可以互換,但兩個方向的限制不一樣:

  • On-Demand → Provisioned:隨時可以切
  • Provisioned → On-Demand:24 小時內最多切 4 次

實務上常見的節奏:

新服務上線,不知道量                    跑一陣子,流量形狀穩定了
   On-Demand  ──────────────────────▶  Provisioned + Auto Scaling
   (不用猜容量,單價高)                 (照觀察到的流量設上下限,省錢)

📌 補充

功能 一句話
DAX 記憶體快取,讀取從毫秒降到微秒;只快取最終一致性讀取,寫入直接穿透
DynamoDB Streams 記錄新增/修改/刪除事件(保留 24 小時),常用來觸發 Lambda
Global Tables 多個 Region 各一份可讀寫的 replica,雙向同步
TTL item 到期自動刪除、不耗 WCU,但不是一到期馬上刪
備份 on-demand 備份,或開 PITR 還原到 35 天內任一秒

✅ 小結

概念 說明
DynamoDB 全受管 NoSQL,table/item/attribute,先想查詢方式再設計 key,幾乎不支援 JOIN
Partition key 決定資料落在哪個分區,要挑值多又分散的欄位,設計不好會造成熱分區被節流
Sort key 跟 partition key 組成複合主鍵,適合一對多的資料形狀
Query vs Scan Query 靠 key 直接找、只算讀到的;Scan 整張表讀一遍、過濾不省錢
LSI 只能建表當下定義,共用容量,可選強一致性
GSI 隨時可加,獨立容量,只有最終一致性
RCU/WCU 讀 4 KB、寫 1 KB 各算一個單位;最終一致性讀取只算一半
On-Demand vs Provisioned 前者免設定、單價高,流量難預測時用;後者自己設容量、便宜,流量穩定時用
DAX/Global Tables 分別解決「重複讀取太熱」和「多 Region 都要能寫」兩個不同問題

回到開頭那句:所有東西都從 key 的選擇長出來。熱分區是 key 選得太集中,Scan 是 key 沒對齊查詢,GSI 是替另一種查詢再選一組 key。

身份與網路安全、運算與儲存核心到這裡告一段落。下一天進高可用與流量入口:Auto Scaling 搭配 ELB。


🧠 AI 出題

問題 1

某手機遊戲公司用 DynamoDB 記錄玩家在限時活動中送出的每一次得分,表格的 partition key 是 eventId、sort key 是 playerId#timestamp,每筆 item 小於 1 KB。每場活動只有一個 eventId,活動開始後每秒約 8,000 次寫入全部集中在這個值上;即使把表格的佈建 WCU 調高到 20,000,仍然持續發生節流(throttling)。每筆得分都必須寫入成功,活動結束後才彙總排名。

在不降低寫入吞吐量的前提下,解決方案架構師應該怎麼做?

  • A. 將表格改成 On-Demand 容量模式,讓 DynamoDB 依實際流量自動擴展,不再受佈建 WCU 上限的限制
  • B. 在表格前面加上 DAX 叢集,讓寫入先進入 DAX 的記憶體快取,再由 DAX 以批次方式寫回 DynamoDB
  • C. 把 eventId 加上 0~19 的隨機後綴作為 partition key,彙總時分別 Query 20 個鍵值再合併
  • D. 建立以 playerId 為 partition key 的 GSI,讓寫入分散到索引的不同分區上,減輕原表熱分區的寫入壓力

問題 2

某電商的 DynamoDB 表格 Orders 存有 5 億筆訂單,partition key 是 orderId。風控系統會在可疑訂單上多寫一個 reviewFlag attribute,這類訂單不到總量的 0.1%。風控人員每隔幾分鐘就要查詢一次「所有待審核的可疑訂單」,目前的做法是 Scan 加上 filter,每次都讀完整張表。公司希望以最符合成本效益(MOST cost-effective)的方式,讓這個查詢維持近乎即時。

解決方案架構師應該怎麼做?

  • A. 建立以 reviewFlag 為 partition key 的 GSI,只有帶這個 attribute 的訂單會進索引,再對它 Query
  • B. 建立以 status 為 partition key 的 GSI,讓所有訂單都寫進索引,查詢時再用 filter 篩出待審核的可疑訂單
  • C. 新增以 reviewFlag 為 sort key 的 LSI,讓同一個 orderId 底下的可疑訂單可以直接用 Query 取得
  • D. 每天把表格匯出到 Amazon S3,再用 Amazon Athena 查詢帶有 reviewFlag 的訂單,減少對 DynamoDB 表格的讀取

問題 3

某新聞網站的文章存在 DynamoDB,首頁的熱門文章每秒被讀取數萬次。團隊為了降低讀取延遲導入了 DAX,應用程式也已改用 DAX client 連線,但上線後讀取延遲沒有改善,DynamoDB 的 RCU 消耗也幾乎沒變。檢查後發現,應用程式所有的讀取請求都設定了 ConsistentRead=true。文章內容允許有幾秒的更新延遲。

在修改最少的前提下,解決方案架構師應該怎麼做?

  • A. 將 DAX 叢集的節點升級到更大的規格並增加節點數,提高快取可容納的資料量與處理能力
  • B. 把 DAX 的 item cache TTL 從預設的 5 分鐘延長到 1 小時,讓熱門文章在快取中停留得更久
  • C. 在 DAX 前面再加一層 Amazon ElastiCache for Redis,由應用程式自行快取強一致性讀取的結果
  • D. 把讀取文章的請求改成最終一致性讀取,移除 ConsistentRead=true,讓 DAX 能直接從快取回應

問題 4

某叫車平台在 us-east-1 與 ap-northeast-1 各部署一套應用程式。兩地的司機與乘客都要在本地 Region 以個位數毫秒的延遲讀寫行程資料;任一 Region 完全失效時,另一個 Region 要能繼續讀寫,資料遺失控制在 1 秒左右。公司希望以維運負擔最低(LEAST operational overhead)的方式設計 DynamoDB。

哪一個方案最符合需求?

  • A. 在兩個 Region 各建一張表,啟用 DynamoDB Streams,由 Lambda 把每筆異動寫到另一個 Region 的表
  • B. 將表格設為 DynamoDB Global Tables,兩個 Region 各有一份可讀寫的 replica,由 DynamoDB 雙向同步
  • C. 表格只放在 us-east-1,ap-northeast-1 的應用程式透過 VPC peering 跨 Region 讀寫這張表
  • D. 開啟 point-in-time recovery,並每小時把 on-demand 備份複製到另一個 Region,失效時再從備份還原成新的表格

問題 5

某 SaaS 公司的 DynamoDB 表格上線一年,一直使用 On-Demand 模式。CloudWatch 顯示讀寫流量全天都落在可預測的範圍內,白天比夜間高約 50%,預期未來一年也會維持這個模式。表格約 50 GB,每筆資料每天都會被多次讀寫,費用幾乎都來自讀寫請求。財務部門要求降低 DynamoDB 的費用。

哪兩項做法最符合成本效益(MOST cost-effective)?(選擇兩項)

  • A. 將表格切換為 Standard-IA table class,以較低的每 GB 儲存單價降低 DynamoDB 的整體費用
  • B. 改成 Provisioned 模式並設定 auto scaling,讓 RCU/WCU 隨日夜流量在上下限之間調整
  • C. 在表格前加上 DAX 叢集,讓所有讀寫請求都改經 DAX 處理,減少 DynamoDB 本身的容量消耗
  • D. 改成 Provisioned 模式,把容量固定在白天尖峰的數值,確保任何時段都不會發生節流
  • E. 針對全天都會用到的基線容量購買一年期 DynamoDB reserved capacity,以承諾換取折扣

💡 解答

1. C

單一 partition key 值的吞吐量受實體分區上限限制,每秒最多約 1,000 WCU,所以表格的總容量再大,eventId 這一個值還是撐不住 8,000 次寫入。加上 0~19 的隨機後綴,就把同一場活動拆成 20 個 partition key 值,每個值每秒只剩約 400 次寫入,能散到不同分區。代價是讀取時要 Query 20 次再合併,但這份資料本來就是活動結束後才彙總,完全可以接受。

A 是誤解:On-Demand 只是不用自己設容量,單一分區的上限一樣存在,熱門的鍵值一樣會被節流。B 也是誤解:DAX 是讀取快取,寫入會直接穿透到 DynamoDB,沒有「先存在記憶體再批次寫回」的緩衝功能。D 解錯了地方:寫入還是要先寫進原表,原表的 eventId 依然是熱鍵,GSI 分散不了原表的寫入壓力。

2. A

這是 sparse index 的典型用法:GSI 只會收錄帶有索引 key attribute 的 item,所以以 reviewFlag 當 partition key,索引裡就只有那 0.1% 的可疑訂單,儲存和寫入的成本都很小,Query 也只讀需要的資料。因為可疑訂單寫入的頻率很低,這個 key 雖然只有少數幾種值,也不會形成熱分區。

B 可行,但 5 億筆訂單全部都會寫進索引,索引的儲存和每次寫入的 WCU 都要多付一份,成本遠高於 sparse index。C 有兩個問題:LSI 只能在建表時建立,不能加到既有的表格上;而且 LSI 的 partition key 跟原表一樣是 orderId,只能在單一訂單底下查,查不到「所有可疑訂單」。D 每天才匯出一次,違反「近乎即時」的要求。

3. D

DAX 只快取最終一致性讀取;強一致性讀取會直接轉給 DynamoDB,結果也不會存進快取。所以所有請求都帶 ConsistentRead=true 時,DAX 等於只是一個轉送的中繼站,延遲和 RCU 都不會改善。文章允許幾秒的延遲,把讀取改成最終一致性(也就是 DynamoDB 的預設值),DAX 就能直接從快取回應。

A 和 B 都在調整快取本身,但問題是請求根本沒有進快取,加大節點或延長 TTL 都不會有任何效果。C 等於自己再做一層快取,要處理失效和一致性,修改量最大;而且同樣的延遲容忍度,直接改用最終一致性讀取就能讓現有的 DAX 發揮作用。

4. B

Global Tables 讓同一張表在多個 Region 各有一份可讀寫的 replica,每個 Region 都在本地讀寫,延遲是個位數毫秒;DynamoDB 會在背景雙向同步,延遲通常在一秒左右。任一 Region 失效時,另一個 Region 的 replica 本來就可以寫,不需要任何切換操作。

A 是用 Streams 加 Lambda 自己做一套 Global Tables:衝突處理、重試、監控都要自己維護,維運負擔最高。C 讓 ap-northeast-1 每次讀寫都要跨半個地球,達不到個位數毫秒;us-east-1 失效時也沒有任何備援。D 的資料遺失最多一小時,而且從備份還原要時間,不符合「繼續讀寫、遺失約 1 秒」。

5. B、E

流量穩定又可預測,就是 Provisioned 模式最省錢的情境:單價比 On-Demand 低很多,再用 auto scaling 跟著日夜的曲線調整,夜間不必為白天的容量付錢。全天都會用到的基線容量,還可以再買 reserved capacity,用一年的承諾換取更低的折扣價。這兩項各自都能省錢,而且可以疊加使用。

A 方向相反:Standard-IA 降低的是儲存費用,讀寫請求的單價反而比較高,對一個費用幾乎都來自讀寫的表格只會更貴。C 是誤解:DAX 只能減少重複的讀取,寫入會直接穿透到 DynamoDB;資料每天被多次改寫,快取命中率也不高,還要多付 DAX 節點的費用。D 雖然也比 On-Demand 便宜,但夜間的容量完全閒置,成本不如 B 加上 auto scaling。


上一篇
Day 12 - 運算與儲存核心 RDS:Multi-AZ 與 Read Replica 差在哪
下一篇
Day 14 - 高可用與流量入口 Auto Scaling:搭配 ELB 打造高可用架構
系列文
30 天的 SAA 學習筆記 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言