🎯 什麼情境該想到我

當你「訊息內容太龐大(大檔、大量資料),不想讓它整包在系統裡流動,只想帶一張憑證、需要時再取回」的時候。

⚙️ 怎麼用(步驟 / 公式)

意圖:把訊息的大量內容存到持久儲存,訊息只攜帶一張「取件單」(鍵 / ID),需要時再憑單取回完整內容,減少傳輸量。

做法 / 結構要點:

  • 收到訊息時,把大內容存進資料庫或儲存區,換得一個唯一鍵。
  • 移除訊息本體、只留這個鍵(取件單)往下送。
  • 下游需要完整內容時,用取件單去儲存區取回。

★ 什麼時候需要它:三個明確的訊號

① ★ 訊息超過 broker 上限(查證 2026-09-25)
   SQS:1,048,576 bytes(1 MiB)
   Kafka:由 message.max.bytes 決定,預設約 1 MB 量級
   → ★ 調高上限不是答案:會連帶影響 broker 記憶體、複製延遲、GC
② ★ 大多數消費者不需要完整內容(只有一個需要)
   → 為了那一個而讓所有人都收 5 MB,是純浪費
③ ★★ 訊息含敏感資料,不想讓它留在 broker 的 retention/備份/DLQ 裡
   → ★ 這是安全用途,常被忽略(見 [[內容過濾器]])

★ Go 的實作與四個必帶欄位

type DocReady struct {
    DocID     string    `json:"doc_id"`
    StoreKey  string    `json:"store_key"`   // ★ 物件儲存的 key
    SizeBytes int64     `json:"size_bytes"`  // ★ 讓消費者能預先決定要不要處理
    SHA256    string    `json:"sha256"`      // ★★ 驗證拿到的是同一份
    ExpiresAt time.Time `json:"expires_at"`  // ★★ 寄物櫃什麼時候會清掉
}

★★ SHA256 與 ExpiresAt 是最常被省略、也最重要的兩個:

  • 沒有 SHA256 → 物件被覆寫時你不會知道(同一個 key,不同內容)。
  • 沒有 ExpiresAt → 消費者不知道「還來不來得及」,也沒人知道什麼時候該清。

★★ 這個模式的核心難題:寄物櫃與訊息的生命週期不一致

★ 四種會出事的組合:
① ★★ 物件先被刪,訊息還在
   S3 lifecycle 設 7 天,訊息在 DLQ 躺了 10 天 → ★ 重放時拿不到內容
   → ★ 規則:寄物櫃的保留期必須「長於」訊息的最長生命週期
      (含 DLQ retention + 人工處理時間)

② ★★ 訊息丟了,物件留著 → 孤兒物件,永遠沒人清
   → ★ 靠 lifecycle policy 自動過期,不要靠應用程式刪

③ ★★ 寫入順序反了
   ❌ 先發訊息、後寫物件 → 消費者可能先到,讀到 404
   ✅ ★ 先寫物件(並確認成功)、再發訊息

④ ★ 誰負責刪
   消費者刪 → ★ 多個消費者時,第一個刪完其他人就讀不到了
   → ★★ 一律用 lifecycle policy 自動過期,不要讓消費者刪

★★ ③ 和 ④ 是最常犯的兩個錯。
③ 讓消費者間歇性讀到 404(而且在高速通道上機率不低);
④ 在有多個訂閱者的發布訂閱通道上直接造成資料遺失。

★ 存取控制

// ★ 兩種給法
// ① 直接給 key,消費者用自己的 IAM 權限讀
//    → ★ 需要所有消費者都有那個 bucket 的權限(★ 權限面變寬)
// ② ★ 給預簽名 URL(presigned URL)
//    → ★ 精確到單一物件、有有效期;消費者不需要 bucket 權限
//    → ⚠️ 但有效期通常短(幾小時),★ 訊息在 DLQ 躺三天後 URL 就失效了

★ 務實做法:訊息帶 key(不帶 presigned URL),消費者用 IAM 讀。
presigned URL 的有效期和訊息生命週期幾乎必然不匹配。

★ 什麼時候不要用

  • ★ 內容很小(幾 KB)——多一次網路往返不划算。
  • ★ 消費者「一定」需要完整內容且處理極快——直接放訊息裡更簡單、更可靠。
  • ★ 沒有可靠的共用儲存。寄物櫃變成新的單點,而且它的可用性現在決定訊息流的可用性。
  • ★ 需要 broker 的原生重放。訊息還在但物件被清掉的重放,等於不能重放。

★ 兄弟辨析

差別
內容擴充器★ 反向:訊息不帶資料,由消費者去查補上
內容過濾器移除欄位;寄物單是把內容搬走
訊息存放區★ 保存訊息以供查詢;寄物單是為了傳輸
檔案傳輸★ 大量資料的另一條路(不經過 broker)

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 需要額外的儲存與清理機制(何時刪除存放的內容)。

  • 取回是額外一趟查詢,用延遲換取傳輸量的節省。

  • 儲存區成為相依點,要考慮其可用性。

  • ★★ 順序必須是「先寫物件、確認成功、再發訊息」。 反了會讓消費者讀到 404。

  • ★★ 一律用 lifecycle policy 自動過期,不要讓消費者刪物件。 多訂閱者時第一個刪完其他人就讀不到。

  • ★★ 寄物櫃保留期必須長於訊息的最長生命週期,含 DLQ retention 與人工處理時間。否則重放時拿不到內容。

  • ★ 一定要帶 SHA256(驗證是同一份)與 ExpiresAt(消費者知道來不來得及)。

  • ★ 不要用 presigned URL:有效期通常幾小時,和訊息生命週期(含 DLQ 幾天)不匹配。帶 key 讓消費者用 IAM 讀。

  • ★ 寄物櫃是新的單點。它的可用性現在決定訊息流的可用性。

  • ★ 調高 broker 的訊息上限不是答案,會影響記憶體、複製延遲與 GC。

  • 內容很小就不要用,多一次網路往返不划算。

🔗 相關工具