資料工程面試:如何設計 DynamoDB TransactWriteItems 的冪等與重試?
題干與適用場景
訂單服務需要在一次業務操作中建立訂單、扣減庫存並寫入帳本。網路逾時可能發生在 DynamoDB 已提交之後,客戶端會再次送出請求。請使用 TransactWriteItems 設計原子寫入、條件檢查、冪等重試和對帳流程,並說明交易的容量成本及區域邊界。
面試官考察什麼
- 能否區分 DynamoDB 交易的原子提交、條件失敗和網路未知結果。
- 能否正確使用
ClientRequestToken、業務冪等鍵和條件表達式。 - 能否解釋交易讀寫帶來的額外容量消耗、限制與熱點風險。
- 能否把重試、補償、對帳、監控和跨區域一致性邊界串成完整方案。
回答前要釐清的問題
- 訂單、庫存和帳本是否都在同一 AWS Region 和同一帳戶邊界內?
- 庫存扣減是否允許超賣,帳本是否必須追加不可變記錄?
- 客戶端逾時後能否查詢業務冪等鍵的最終狀態?
- 重試呼叫是否可能跨越 token 的有效窗口?
- 是否需要跨區域災備寫入,還是只要求單區域原子性?
30 秒回答框架
我會為每個業務操作生成穩定的冪等鍵,並在同一 Region 用 TransactWriteItems 把訂單、庫存條件扣減和帳本寫入放在一個原子交易中。請求帶 ClientRequestToken,服務端保存業務狀態並透過條件表達式防止重複扣減;逾時先查詢業務鍵,再按可判斷的錯誤重試。監控交易衝突、條件失敗、容量消耗和未知結果,跨區域場景改用事件和對帳,因為該交易的原子邊界不跨 Region。
分步驟深入解答
第一步:定義業務不變量
寫清楚「訂單建立成功時庫存必須減少一次,帳本必須存在一次」的不變量。訂單和帳本記錄使用同一個 operationId,庫存項用條件表達式保證 available >= quantity。不要用客戶端時間戳作為唯一鍵,因為重試和時鐘偏差會造成重複業務操作。
第二步:組合交易動作
在一個 TransactWriteItems 請求中組合 Put、Update、Delete 和 ConditionCheck。訂單 Put 要求訂單鍵不存在,庫存 Update 同時檢查版本或可用量,帳本 Put 使用 operationId 作為唯一鍵。一個交易中不能對同一項重複操作,動作數量和請求大小也要符合 API 限制。
第三步:設計兩層冪等
ClientRequestToken 讓相同交易請求在 token 有效窗口內可安全重試;業務冪等鍵則持久化在訂單和帳本中,覆蓋更長生命週期。兩者必須對應同一個業務操作,不能每次重試都生成新 token。超過 token 窗口後,先讀取業務狀態,再決定是否重建交易。
第四步:區分失敗與未知結果
條件衝突、容量不足或校驗失敗通常表示交易未按預期提交,可以按錯誤類型退避或返回業務失敗。連線在提交後斷開屬於未知結果,不能立即執行反向扣庫存;應先按 operationId 讀取訂單、庫存版本和帳本,再透過狀態機決定重試或對帳。
第五步:計算成本和熱點
交易會對每個項目執行準備和提交階段的底層讀寫,因此容量消耗高於普通單項寫入。按最大項大小、交易並發和重試放大估算吞吐,並為庫存熱點分片或改用預扣庫存佇列。不要只用平均 WCU 計算峰值,條件失敗和衝突也會消耗容量與延遲預算。
第六步:處理跨區域需求
該交易的原子性適用於呼叫所在 Region 的交易邊界。若訂單在主 Region、分析或備份在其他 Region,提交後發布帶 operationId 的事件,消費者按冪等鍵寫入,並用對帳任務發現缺失或重複。跨區域複製延遲期間,讀模型不能被當作交易提交的即時證明。
第七步:觀測與恢復
記錄交易成功率、條件失敗、衝突、限流、重試次數、未知結果、每個表的容量消耗和端到端延遲。把訂單狀態分為 PENDING、COMMITTED、RECONCILING 和 FAILED,定時任務掃描未知狀態並補齊帳本或生成人工佇列。告警應按 operationId 串聯請求日誌、表項和事件。
高品質示範回答
我會為一次下單生成穩定的 operationId,在同一 Region 的 TransactWriteItems 中建立訂單、條件扣減庫存並寫入以該 ID 為鍵的帳本記錄。訂單寫入要求鍵不存在,庫存更新檢查版本和 available >= quantity,帳本寫入天然去重;請求同時攜帶穩定的 ClientRequestToken。條件失敗直接返回可解釋的業務錯誤,提交後的網路逾時先讀取 operationId 對應的三類記錄,不能盲目反向寫入。容量按交易準備與提交的額外讀寫、熱點和重試放大估算。跨區域同步使用帶冪等鍵的事件和對帳,不宣稱跨 Region 原子性;監控衝突、容量、未知結果和狀態機恢復時間。
常見錯誤
- 把網路逾時當成交易一定失敗,立即執行反向庫存操作。
- 每次重試都生成新的業務鍵,導致重複訂單或帳本。
- 只依賴
ClientRequestToken,忽略長期業務冪等記錄。 - 忽略條件衝突、交易大小和交易讀寫的額外容量成本。
- 把跨 Region 複製或全域表讀到的資料當成交易提交證明。
- 沒有未知狀態掃描和對帳機制,依賴人工查日誌。
追問及應對
追問一:ClientRequestToken 能永久保證冪等嗎?
不能。它只覆蓋 API 規定的有效窗口。長期冪等必須由訂單、帳本或獨立操作表保存業務鍵和最終狀態。
追問二:庫存條件失敗時要不要重試?
若原因是庫存不足,重試不會改變結論,應返回業務失敗;若是暫時衝突或限流,可退避重試,但必須複用同一業務操作鍵並限制次數。
追問三:提交後逾時如何判斷結果?
讀取 operationId 對應的訂單、庫存版本和帳本記錄。三者一致表示已提交;部分可見或狀態不一致時進入 RECONCILING,由對帳流程決定補寫或人工處理。
追問四:為什麼不把跨區域寫入也放進一個交易?
該 API 的原子邊界不覆蓋多個 Region。跨區域需求應使用事件、冪等消費者和對帳,明確最終一致窗口與降級策略。
追問五:交易衝突如何定位?
記錄 operationId、涉及表鍵、條件版本和重試次數,按熱點鍵聚合衝突率。若單庫存項持續衝突,應分片、預扣或改成佇列化庫存分配。
追問六:如何測試未知結果路徑?
在服務端確認提交後主動丟棄回應,模擬客戶端逾時;隨後驗證查詢、重試、狀態機、事件去重和對帳結果,確保不會重複扣庫存或寫帳本。