中階閱讀時間 3 分鐘

DynamoDB 中的 Type 屬性

在 SQL 裡,一筆資料列所屬的資料表「就是」它的類型——documents 裡的一列就是一份文件。DynamoDB 的單一資料表把所有實體混在同一個 schema 底下,所以一個項目本身並沒有內建答案來回答「這是什麼?」。

Type 屬性 把那個答案放回去:每個項目上一個單純的字串, 標明它所代表的實體。

DynamoDB 中的 Type 屬性是什麼?

Type 屬性是一個你烙在每個項目上的純字串——例如 EntityType: "Document"——用來標明該項目所代表的實體。由於單一資料表把許多實體混在同一個 schema 底下,項目本身並沒有內建的類型。Type 屬性把它補回去,讓你的程式碼能辨識資料列、把 GSI 篩選成單一實體,並在遷移時存活下來。

  • 每次寫入都烙上一個 Type。 每個項目一個屬性——EntityType: "Document"—— 毫無例外。它只花幾個位元組,卻能在日後救你一命。
  • 它在混合分區中辨識實體。 一次 Query 會一起回傳工作區、 文件和留言;Type 會告訴你的程式碼哪個是哪個, 不必去解析鍵的前綴。
  • 它讓 GSI 上的單一實體篩選成為可能。 把 Type 投影進索引, 你就能把一個多載索引收窄到剛好一種實體類型。
  • 它是你做遷移時的逃生口。 當你匯出來重新建模、或把某個實體 搬到自己的資料表時,Type 就是你用來切分的那一欄。

為什麼混合資料表會弄丟類型

單一資料表設計把所有實體都存進一張 資料表,背後用 PKSK 這種通用鍵。這正是重點所在——一次 Query 就能一起回傳一個父項與它的子項。但這也意味著一個分區是 異質的。

拿一個 SaaS 文件協作應用來說。一個工作區分區裡裝著工作區記錄、 它的文件,以及那些文件上的留言:

PKSKattributes
WS#acmeMETAname, plan, seats
WS#acmeDOC#a1#METAtitle, owner, wordCount
WS#acmeDOC#a1#CMT#0007author, body, createdAt
WS#acmeDOC#a1#CMT#0008author, body, createdAt

Query PK = "WS#acme" 會在一次計費的讀取中把這四個項目都交回給你。現在你的 程式碼拿到一串原始項目,卻沒有可靠的方法說出哪個是文件、 哪個是留言——除非去字串比對 SK,而那種做法在你的鍵格式一改變的 瞬間就會變得脆弱。

在每個項目上烙上 Type

修法就是每次寫入都加一個屬性,標明實體:

PKSKEntityTypetitle
WS#acmeMETAWorkspace
WS#acmeDOC#a1#METADocumentQ3 Roadmap
WS#acmeDOC#a1#CMT#0007Comment

item.EntityType === "Document" 上分支,是一個穩定的相等檢查。 解析 SK.startsWith("DOC#") && SK.includes("#CMT#") 則是一種猜測,會在你 改版鍵的時候出問題。Type 讓你的讀取邏輯與鍵的編碼解耦—— 那才是真正的勝利。

Query PK = 'WS#acme'混合分區EntityType: 'Workspace'EntityType: 'Document'EntityType: 'Comment' Type 路由

一次讀取回傳三種實體類型;Type 屬性把每個項目路由到 正確的處理器,完全不必碰鍵。

把 GSI 收窄到單一實體

Type 在索引上才真正派上用場。假設你加了一個 GSI,鍵是 GSI1PK = WS#acmeGSI1SK = updatedAt,用來列出「這個工作區裡最近改過的 所有東西,最新的排前面」。一個多載索引會把文件「和」留言一起掃進來—— 但一個資訊流 UI 可能只想要文件。

有兩種收窄方式,差別在於錢:

做法它的成本何時使用
在 Type 上用 FilterExpression讀取所有符合鍵的項目、為全部計費,讀取後才丟掉不符的結果中混入的實體很少;想快速上線
稀疏索引(Type 放進 GSI1PK只有你要的實體才會進入索引某一種實體占多數;你想零浪費

FilterExpression 是在項目被讀取之後、且在容量被 消耗之後才執行——AWS 明確指出篩選並不會降低讀取成本 (DynamoDB 開發人員指南:FilterExpression)。 在 Type 上篩選是誠實的,不是免費的:你為那些被丟掉的留言付了錢。

要把資訊流收窄成只有文件,查詢會帶一個對 Type 屬性的條件。用 DynamoDB 運算式建構器 來組裝 FilterExpression、名稱與值——它會產生 #t = :doc 佔位符,讓你不會手滑打到保留字。

KeyConditionExpression     GSI1PK = :ws
FilterExpression           #t = :doc
ExpressionAttributeNames   { "#t": "EntityType" }
ExpressionAttributeValues  { ":ws": "WS#acme", ":doc": "Document" }

想讓索引只攜帶文件、完全跳過篩選?只在文件項目上寫入 GSI1PK——這就是 稀疏索引。 沒有 GSI 鍵的項目永遠不會複製進索引,所以這次讀取只會碰到 文件。Type 屬性正是告訴你的寫入端哪些項目 符合資格的東西。

讓值保持穩定且單一

值一次選定,並把它當作一個列舉值。Document,絕不要有時 Doc 有時 document——一個飄移的值比沒有值更糟,因為你的 相等檢查會在某種大小寫上通過,卻悄悄漏掉另一種。

每個項目一個 Type。如果一個項目感覺像兩種實體,那通常是一種建模 氣味——它應該是兩個項目,各自在自己的集合或排序鍵範圍裡, 而不是一列身兼兩職。

遷移的回報

在需要之前就先烙上 Type 的理由是:重新建模。建議的 重新建模路徑是匯出、轉換、重新匯入——而 AWS 記載了批次匯出到 S3,正是為了這種離線重塑 (匯出 DynamoDB 到 S3)。

那一天到來時,Type 就是你 GROUP BY 的那一欄。想把留言 搬進它們自己的資料表,或把匯出重新正規化成每實體一檔的檔案、 給分析倉儲用?你就在 EntityType 上切分這份匯出。沒有它,你又得 回頭在數百萬列上逆向工程鍵。

後續步驟

Type 屬性是廉價的保險:在混合讀取中辨識實體、篩選 一個多載的 GSI,並在重新建模時乾淨地切分。從第一天起就在每次寫入 都烙上它——事後把它補進一張上線中的資料表,意味著一次完整回填。

延伸閱讀:單一資料表設計了解這個模式所 服務的混合分區;GSI 與 LSI了解如何選擇 稀疏索引背後的索引形狀;以及 Query 與 Scan了解為什麼 FilterExpression 永遠救不了 你的讀取成本。

DynamoDB 運算式建構器在 Type 上 建立篩選,並試用 DynoTable瀏覽一張真正的混合實體資料表, 看看 Type 欄如何在每個項目上對齊。

已更新