入門閱讀時間 2 分鐘

DynamoDB 投影運算式

投影運算式是 DynamoDB 裡的 SELECT col1, col2:一個逗號分隔的名列表,告訴 GetItemQueryScan 只返回那些屬性,而不是整個條目。

DynamoDB 投影運算式能降低讀取成本嗎?

不能。ProjectionExpression 裁剪的是響應載荷,而不是你被計費的讀取容量。DynamoDB 從儲存裡讀取完整條目,按它在磁碟上的大小計量 ,然後在返回途中丟掉你沒點名的屬性。要真正削減讀取成本,改用一個覆蓋式

  • 它裁剪載荷,而不是讀取成本。DynamoDB 從儲存裡讀取(並計費)完整條目,然後在返回途中丟掉你沒點名的屬性。ProjectionExpression 是一種網路最佳化,而不是容量最佳化。
  • 它是你抓取一個公開子集的方式。點名呼叫方被允許看到的那幾個屬性;其餘的永遠不離開表。
  • 對任何可能是保留字的東西用 #name 預留位置。運算式裡的裸屬性名會與 DynamoDB 約 570 個保留字衝突,導致請求失敗。
  • 要真正省讀取,改用一個覆蓋式索引。一個只投影你所需列的 ,會以它自己(更小的)大小被讀取。

它實際省下什麼

從 SQL 過來,你會以為 SELECT a, bSELECT * 掃描得更少。在 DynamoDB 裡這個直覺是錯的。一次讀取的容量單位是由條目在磁碟上的大小算出來的,向上取整到下一個 4 KB——而且是在應用投影_之前_。AWS 說得很明白:ProjectionExpression 不改變一個請求消耗的讀取容量。1

所以投影為你省下兩樣東西,兩樣都是真的,但兩樣都是讀取的下游:

  • 線路上的位元組。一個 6 KB 的條目作為兩個小屬性返回,是個很小的響應。在一個返回上百個條目的 Query 上,這加起來很快就可觀了。
  • 用戶端的工作。更少的反序列化,更少的記憶體佔用,更少意外洩漏進日誌或 API 響應的東西。

省的是 RCU。這就是那個陷阱:人們伸手去用投影來削減賬單,看到沒變化,就斷定 DynamoDB 壞了。它沒壞——你量錯了槓桿。

投影一個公開的使用者資料

假設你營運一個使用者目錄。每份資料是一個條目,鍵設計得能按使用者名稱抓一個人:

PK = "PROFILE#ada"      (partition key)
SK = "PROFILE#ada"      (sort key — single-item collection)

這個條目很胖。它既攜帶帳戶的公開面孔,又攜帶一堆私有和營運屬性:

{
  "PK": "PROFILE#ada",
  "SK": "PROFILE#ada",
  "displayName": "Ada L.",
  "avatarUrl": "https://cdn.example.com/u/ada.png",
  "bio": "Builds things.",
  "emailAddress": "ada@example.com",
  "passwordResetToken": "…",
  "billingCustomerId": "cus_…",
  "lastLoginIp": "…",
  "internalRiskScore": 0.02
}

一張公開的資料卡片需要三個欄位。抓取整個條目意味著 emailAddresslastLoginIpinternalRiskScore 會傳到一個永遠不該看到它們的上下文裡。只點名那個公開子集:

GetItem  PK = "PROFILE#ada"  SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio

響應攜帶三個屬性。私有的那些留在表裡——不是被你的應用在到達_之後_過濾掉,而是壓根從未被序列化進響應。這就是安全上的勝利,而且它是那種一旦秘密已經越過邊界就很難挽回的勝利。

你可以在 DynamoDB 運算式構建器裡組裝並複製這個確切的請求——名稱、預留位置,以及 SDK 呼叫——它會替你產出 ProjectionExpressionExpressionAttributeNames 對映。

在下面的預設裡增刪欄位,看著 ProjectionExpression 變化——只有列出的屬性會返回:

建構你的請求
產生的程式碼
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ProjectionExpression": "#proj0, #proj1, #proj2",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk",
    "#proj0": "action",
    "#proj1": "actor",
    "#proj2": "createdAt"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#"
    }
  }
})

# 預留位置轉義保留字

這裡就是一個乾淨的投影會炸掉的地方。DynamoDB 保留了一長串詞——namestatuscommentsizetimestamp,還有上百個。2如果你正在投影的某個屬性是其中之一,運算式裡的裸名就會被拒絕。

假設這份資料還有一個 status 屬性("active""suspended")。這會失敗:

ProjectionExpression   displayName, status

status 是保留字。修復方法是一個運算式屬性名——一個 # 字首的預留位置,對映到真實名稱:

ProjectionExpression       displayName, #s
ExpressionAttributeNames   { "#s": "status" }

同樣的機制能伸進巢狀屬性。要從一個 map 裡取出單個欄位,或從一個 list 裡取一個元素,用文件路徑語法——並且給每一段都加預留位置,因為其中任何一段都可能是保留字:

ProjectionExpression       #addr.#city, tags[0]
ExpressionAttributeNames   { "#addr": "address", "#city": "city" }

一條實用規則:給一切都加預留位置。你就永遠不必記住你正踩在那約 570 個保留字裡的哪一個上,而且運算式兩種寫法讀起來都一樣。而如果你更想知道究竟是哪些名稱出了問題,把它們貼上進保留字檢查器——它會標記衝突,並輸出 ExpressionAttributeNames 別名對映。

覆蓋式索引何時勝過投影

如果你真心需要削減讀取成本——不只是載荷——那個槓桿是一個只投影你所讀屬性的全域次要索引。GSI 是資料的一份單獨副本;你為它的投影選擇 KEYS_ONLYINCLUDEALL3一個 KEYS_ONLY 或窄 INCLUDE 的索引每條目在物理上更小,所以針對它的 Query 會以那個更小的大小被計量。

這就是覆蓋式索引:查詢完全從索引裡得到回答,不用回基表跑一趟。當一個熱讀取模式永遠只需要大條目裡的幾個屬性時用它。

ProjectionExpression覆蓋式 GSI
裁剪載荷
削減讀取成本——以索引的大小讀取
額外儲存被投影欄位的第二份副本
額外寫入成本寫入會傳播到索引
最適合隱藏私有欄位;小贏從大條目裡熱讀取幾個欄位

取捨是誠實的:索引花你儲存和寫入容量來省下讀取容量。對於從一個重條目裡頻繁讀取一個薄切片,值得;為了省下一次性的 GetItem,不值得。參見 GSI vs LSI 來挑選索引型別,並在你把索引放到熱路徑上之前看看GSI 讀取何時可能是陳舊的

陷阱與後續步驟

  • 別指望賬單變小。光靠投影永遠不改變 RCU。如果數字沒動,那是有據可查的行為,不是 bug。
  • 給保留字加預留位置。運算式裡一個裸的 namestatus 會讓請求失敗——用 # 對映它。
  • 總是包含鍵屬性——它們幾乎不增載入荷,還讓你能分頁或重新抓取條目。
  • 只在一個熱模式從大條目裡讀取幾個欄位時才伸手去用覆蓋式索引;先權衡寫入/儲存成本。

運算式構建器裡構造 ProjectionExpression 及其屬性名對映,然後試試 DynoTable,對你自己的表執行這些投影,看著響應縮小。


  1. AWS DynamoDB 開發者指南,《Using projection expressions in DynamoDB》——讀取容量基於應用任何 ProjectionExpression 之前的條目大小。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
  2. AWS DynamoDB 開發者指南,《Reserved Words in DynamoDB》。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. AWS DynamoDB 開發者指南,《Attribute Projections》(KEYS_ONLY / INCLUDE / ALL)。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

已更新