<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Cost-Optimization on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cost-optimization/</link><description>Recent content in Cost-Optimization on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 02 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/cost-optimization/index.xml" rel="self" type="application/rss+xml"/><item><title>DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB&lt;/a> overview 的 implementation-layer deep article。寫作參照 &lt;a href="https://tarrragon.github.io/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>quarterly review 看 DynamoDB bill 突然漲 80%、追查發現是 dev team 把所有 table 切 on-demand「省 capacity 管理」。finance 反問「於是省了多少 SRE 工時、又多花多少 cost」、team 答不出來。反向情境：Black Friday 前一週 provisioned table auto-scaling 上限是日常 5 倍、但開賣瞬間流量是 50 倍、auto-scaling 反應週期 5 分鐘、前 10 分鐘大量 throttle。兩個 production 痛點指向同一件事 — capacity mode 選擇不能只看「peak/avg ratio &amp;gt; 5x」單軸閾值。&lt;/p>
&lt;p>本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 性質 / 事件分級 / DBA 工時釋放 / vendor crossover），把單軸決策樹擴成完整判讀框架。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>DynamoDB 適用度前置判讀&lt;/strong>：本篇假設 workload 已通過 DynamoDB 適用度 4 軸（PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定）— 詳見 &lt;a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀&lt;/a>、本篇不重複展開。Capacity mode 選擇是 &lt;em>已選 DynamoDB 後&lt;/em> 的成本決策；若 workload 不適用 DynamoDB、mode 選擇無法救回 vendor 選錯的成本。&lt;/p>&lt;/blockquote>
&lt;h2 id="核心機制兩種-mode-的工程差異">核心機制：兩種 mode 的工程差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>屬性&lt;/th>
 &lt;th>Provisioned&lt;/th>
 &lt;th>On-demand&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>計費方式&lt;/td>
 &lt;td>預先買 RCU/WCU、按 hour 計&lt;/td>
 &lt;td>按 request 計、無 capacity 預設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Auto-scaling&lt;/td>
 &lt;td>動態調整、target utilization 70%、min / max&lt;/td>
 &lt;td>自動 scale、仍受單 partition 1000 WCU / 3000 RCU 上限&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Throttle 表現&lt;/td>
 &lt;td>&lt;code>WriteThrottleEvents&lt;/code> 立即可見、exception 拋出&lt;/td>
 &lt;td>不顯示 throttle、表現為 latency spike（hot partition 隱藏）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost 模型&lt;/td>
 &lt;td>可預測、低基礎 rate&lt;/td>
 &lt;td>按用量、cost-per-request 約 provisioned base rate 的 6-7 倍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mode 切換限制&lt;/td>
 &lt;td>24 小時內只能切一次&lt;/td>
 &lt;td>同左&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Auto-scaling 內部機制&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>CloudWatch alarm 觸發 → scaling activity → 1-5 分鐘調整 capacity&lt;/li>
&lt;li>target utilization 70%（建議值、留 buffer 給 scale latency）&lt;/li>
&lt;li>連續 spike 仍可能 throttle（auto-scaling 反應週期 &amp;gt; spike 速度）&lt;/li>
&lt;/ul>
&lt;p>對應 knowledge card：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">peak forecast&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">cost per request&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB</a> overview 的 implementation-layer deep article。寫作參照 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology</a>。</p></blockquote>
<p>quarterly review 看 DynamoDB bill 突然漲 80%、追查發現是 dev team 把所有 table 切 on-demand「省 capacity 管理」。finance 反問「於是省了多少 SRE 工時、又多花多少 cost」、team 答不出來。反向情境：Black Friday 前一週 provisioned table auto-scaling 上限是日常 5 倍、但開賣瞬間流量是 50 倍、auto-scaling 反應週期 5 分鐘、前 10 分鐘大量 throttle。兩個 production 痛點指向同一件事 — capacity mode 選擇不能只看「peak/avg ratio &gt; 5x」單軸閾值。</p>
<p>本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 性質 / 事件分級 / DBA 工時釋放 / vendor crossover），把單軸決策樹擴成完整判讀框架。</p>
<blockquote>
<p><strong>DynamoDB 適用度前置判讀</strong>：本篇假設 workload 已通過 DynamoDB 適用度 4 軸（PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定）— 詳見 <a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀</a>、本篇不重複展開。Capacity mode 選擇是 <em>已選 DynamoDB 後</em> 的成本決策；若 workload 不適用 DynamoDB、mode 選擇無法救回 vendor 選錯的成本。</p></blockquote>
<h2 id="核心機制兩種-mode-的工程差異">核心機制：兩種 mode 的工程差異</h2>
<table>
  <thead>
      <tr>
          <th>屬性</th>
          <th>Provisioned</th>
          <th>On-demand</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>計費方式</td>
          <td>預先買 RCU/WCU、按 hour 計</td>
          <td>按 request 計、無 capacity 預設</td>
      </tr>
      <tr>
          <td>Auto-scaling</td>
          <td>動態調整、target utilization 70%、min / max</td>
          <td>自動 scale、仍受單 partition 1000 WCU / 3000 RCU 上限</td>
      </tr>
      <tr>
          <td>Throttle 表現</td>
          <td><code>WriteThrottleEvents</code> 立即可見、exception 拋出</td>
          <td>不顯示 throttle、表現為 latency spike（hot partition 隱藏）</td>
      </tr>
      <tr>
          <td>Cost 模型</td>
          <td>可預測、低基礎 rate</td>
          <td>按用量、cost-per-request 約 provisioned base rate 的 6-7 倍</td>
      </tr>
      <tr>
          <td>Mode 切換限制</td>
          <td>24 小時內只能切一次</td>
          <td>同左</td>
      </tr>
  </tbody>
</table>
<p><strong>Auto-scaling 內部機制</strong>：</p>
<ul>
<li>CloudWatch alarm 觸發 → scaling activity → 1-5 分鐘調整 capacity</li>
<li>target utilization 70%（建議值、留 buffer 給 scale latency）</li>
<li>連續 spike 仍可能 throttle（auto-scaling 反應週期 &gt; spike 速度）</li>
</ul>
<p>對應 knowledge card：<a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">peak forecast</a>、<a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">cost per request</a>、<a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling</a>。</p>
<h2 id="6-軸決策框架">6 軸決策框架</h2>
<p>mode 選擇不是單軸 peak/avg ratio。下面 6 軸是 9 個 production case（Zomato / Zoom / Amazon Ads / Disney+ / Tixcraft / Capcom / Lemino / Genesys / PayPay）跨 case 揭露的真實決策維度。</p>
<h3 id="軸-1peak--average-流量-ratio">軸 1：peak / average 流量 ratio</h3>
<p>最直覺的軸、但是單軸誤判的根源。基本判讀：</p>
<ul>
<li>高 ratio（spiky / flash-sale）傾向 on-demand</li>
<li>穩定 ratio（sustained / 平緩）傾向 provisioned + auto-scaling</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：「peak/avg &gt; 5x → on-demand」、「provisioned base rate × 6-7 = on-demand rate」這些具體閾值是經驗值 / 通用工程估算、<code>9.C5</code> / <code>9.C20</code> case 都沒給具體 ratio 數字。實際 crossover 點隨 region pricing + workload shape 變動、不要照搬本文數字。</p></blockquote>
<p>軸 1 單獨不夠用、要跟軸 2-6 合成判讀。</p>
<h3 id="軸-2讀寫比-trend-變化">軸 2：讀寫比 trend 變化</h3>
<p><code>9.C5 Amazon Ads</code> 揭露的觀測軸：「讀寫比 <em>變化</em> 比讀寫比本身更重要」。</p>
<ul>
<li>絕對讀寫比對容量規劃不是最重要（C5 是 18:1、C27 推估 5:1、絕對值各家不同）</li>
<li>業務邏輯改變（新增即時報表 / 新增推播 / 新增分析 query）會讓讀寫比跳一個量級</li>
<li>觀測上加 metric：read / write ratio 7-day rolling average、超過 ±30% 偏移觸發 review</li>
</ul>
<p>把 trend 變化當 capacity mode 重新評估的訊號 — 不是固定週期 review、是 <em>trend 偏移</em> 觸發 review。</p>
<h3 id="軸-3surge-是-暫時-還是-永久-baseline-上移">軸 3：surge 是 <em>暫時</em> 還是 <em>永久 baseline 上移</em></h3>
<p><code>9.C18 Zoom</code> COVID 30x DAU surge 揭露的軸：surge 後 baseline 永久上移、不會回去。</p>
<ul>
<li>暫時 surge（單日活動 / 季節高峰）：on-demand 划算、活動結束 mode 不用調</li>
<li>永久上移後（Zoom COVID、社會行為改變）：原 on-demand 設計會持續燒錢、要重新算 crossover、考慮切回 provisioned</li>
</ul>
<p><strong>Tripwire</strong>：surge 結束後 4-8 週仍維持 surge 期間 baseline 的 70%+、判定為「永久 baseline 上移」、重評 mode。</p>
<blockquote>
<p><strong>Scope warning</strong>：「4-8 週 / 70% 閾值」屬通用工程估算、9.C18 Zoom case 揭露「surge 後 baseline 不會回去」概念、未揭露具體閾值。</p></blockquote>
<h3 id="軸-4predictable-peak-vs-flash-sale">軸 4：predictable-peak vs flash-sale</h3>
<p><code>9.C27 Disney+</code> 跟 <code>9.C15 Tixcraft</code> 對比揭露的軸：兩種 event-driven peak 不是同一類。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>predictable-peak（Disney+ 新片發布）</th>
          <th>flash-sale（拓元售票）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>時間 lead</td>
          <td>已知日期、提前 1-2 天可預備</td>
          <td>已知時刻、提前 1-5 分鐘有效</td>
      </tr>
      <tr>
          <td>峰值倍數</td>
          <td>metadata 3-5x、持續數小時</td>
          <td>6750x in seconds、t=0 起跳 / t=300 結束</td>
      </tr>
      <tr>
          <td>Scale 方式</td>
          <td>scheduled scaling 預先升 baseline</td>
          <td>scheduled scaling 太慢、必須 pre-provision + composite PK</td>
      </tr>
      <tr>
          <td>Auto-scaling</td>
          <td>跟得上（事件持續時間長）</td>
          <td>完全跟不上（事件時間 &lt; scaling 反應週期）</td>
      </tr>
      <tr>
          <td>後續調回</td>
          <td>事件結束後 scheduled scaling 降回</td>
          <td>結束後立即降回、避免燒錢</td>
      </tr>
  </tbody>
</table>
<p><code>9.C27 Disney+</code>（Marvel / Star Wars 首日 metadata 流量 3-5 倍、持續時段較長）可以提前 1-2 天 pre-scale、scheduled scaling 合適。<code>9.C15 Tixcraft</code> 6750x in seconds，scheduled scaling 太慢、必須事前 pre-provision baseline 拉到極高、或用 on-demand + composite partition key 雙保險。</p>
<p>兩者都不是「peak/avg &gt; 5x → on-demand」單軸決策能解。</p>
<blockquote>
<p><strong>Scope warning</strong>：「scheduled scaling 30-60 分鐘前升 capacity」這個具體 lead time 是經驗值、case 未揭露具體時間。pre-scale 的 lead time 依事件性質決定、不是固定 30-60 分鐘。</p></blockquote>
<h3 id="軸-5dba--sre-工時釋放">軸 5：DBA / SRE 工時釋放</h3>
<p><code>9.C19 Capcom</code> 跟 <code>9.C29 Lemino</code> 揭露的成本軸：DynamoDB 真實成本不只看 monthly bill。</p>
<ul>
<li><code>9.C19 Capcom</code>：30% 成本下降的本質是「工程資源從 DB 運維轉到遊戲品質」、Capcom 是遊戲公司不是 IT 公司、把 DBA 時間從 Postgres patching / replication 設定 / backup 排程釋放到遊戲機制設計</li>
<li><code>9.C29 Lemino</code>：90% 工程工時下降（DBA + connection management + capacity planning 統包）</li>
</ul>
<p><strong>評估公式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">總成本 = direct cost (monthly bill)
</span></span><span class="line"><span class="ln">2</span><span class="cl">       + 工程工時機會成本 (DBA 從 patch/replication/backup 釋放出來做的事)</span></span></code></pre></div><p>on-demand 的 6-7x base rate 在 DBA 工時釋放下、實質 ROI 可能仍正向（特別在小團隊 / 非 IT 主業公司）。但要算總成本、不是只看 bill。</p>
<h3 id="軸-6dynamodb-vs-自管-cluster-cost-crossover">軸 6：DynamoDB vs 自管 cluster cost crossover</h3>
<p><code>9.C20 Zomato</code> 警惕段揭露的最上層決策軸：mode 選擇之上還有 vendor 選擇。</p>
<ul>
<li><code>9.C20 Zomato</code>：「成本降 50% 是 <em>當下流量</em> 的對照」、未來流量繼續成長、DynamoDB cost-per-request 成長率比 TiDB 自管 cluster 高、某流量規模後 crossover、自管 cluster 反而便宜</li>
<li>不是只在 on-demand vs provisioned 之間挑、是要算「未來 12-24 個月在預期流量下、DynamoDB（不論 mode）vs 自管 cluster 的成本曲線」</li>
</ul>
<p>判讀分層：</p>
<ul>
<li><strong>小 / 中流量 startup</strong>：DynamoDB on-demand 簡單划算、不用糾結</li>
<li><strong>大流量 + 流量可預測 + DBA 團隊已存在</strong>：自管 cluster crossover 點可能成立、值得算</li>
<li><strong>大流量 + 流量不可預測 + 小團隊</strong>：DynamoDB managed 仍划算（軸 5 加成）</li>
</ul>
<p>本軸是 mode 選擇之上的更上層決策、不是每次都展開、但寫進邊界判讀條件。</p>
<h2 id="操作流程">操作流程</h2>
<p>從 workload profiling 到 mode 切換的 8 步流程。</p>
<h4 id="step-1workload-profiling">Step 1：workload profiling</h4>
<p>用 CloudWatch 過去 30 天 RCU/WCU、算 p50 / p95 / p99 peak、求 peak/avg ratio（軸 1 輸入）+ read/write ratio rolling avg（軸 2 輸入）。</p>
<h4 id="step-2surge-性質判讀">Step 2：surge 性質判讀</h4>
<ul>
<li>是暫時 surge 還是永久 baseline 上移（軸 3）— 看 surge 結束後 4-8 週的 baseline trend</li>
<li>是 predictable-peak 還是 flash-sale（軸 4）— 看事件時間跟 auto-scaling 反應週期的比例</li>
</ul>
<h4 id="step-36-軸合成決策">Step 3：6 軸合成決策</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">軸 1（peak/avg）+ 軸 2（讀寫比 trend）+ 軸 3（surge 性質）
</span></span><span class="line"><span class="ln">2</span><span class="cl">+ 軸 4（事件分級）+ 軸 5（工時機會成本）+ 軸 6（vendor crossover）
</span></span><span class="line"><span class="ln">3</span><span class="cl">→ provisioned + auto-scaling / on-demand / scheduled scaling 三選一</span></span></code></pre></div><p>不是任一軸獨自決定、是 6 軸合成；軸間衝突時優先序：軸 6（vendor）&gt; 軸 5（工時）&gt; 軸 3（surge 永久 vs 暫時）&gt; 軸 4（事件分級）&gt; 軸 1（peak/avg）&gt; 軸 2（讀寫比 trend）。</p>
<h4 id="step-4provisioned-配-auto-scaling">Step 4：provisioned 配 auto-scaling</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">BillingMode</span><span class="p">:</span><span class="w"> </span><span class="l">PROVISIONED</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">ProvisionedThroughput</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">ReadCapacityUnits</span><span class="p">:</span><span class="w"> </span><span class="m">100</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">WriteCapacityUnits</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="nt">AutoScalingSettings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span><span class="nt">TargetTrackingScalingPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">TargetValue</span><span class="p">:</span><span class="w"> </span><span class="m">70.0</span><span class="w">  </span><span class="c"># target utilization</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">ScaleOutCooldown</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">ScaleInCooldown</span><span class="p">:</span><span class="w"> </span><span class="m">60</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">MinCapacity</span><span class="p">:</span><span class="w"> </span><span class="m">50</span><span class="w">      </span><span class="c"># baseline</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">MaxCapacity</span><span class="p">:</span><span class="w"> </span><span class="m">1000</span><span class="w">    </span><span class="c"># baseline × 預期 surge multiplier</span></span></span></code></pre></div><p>target utilization 70% 留 buffer 給 scale latency；alarm 設 5 分鐘觀察窗。</p>
<h4 id="step-5scheduled-scaling">Step 5：scheduled scaling</h4>
<p>已知大事件（黑五、開票、新片發布）前預先提升 min capacity、事件後回原值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 黑五前 24 小時把 min capacity 拉到日常 10 倍</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">client</span><span class="o">.</span><span class="n">put_scheduled_action</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">ResourceId</span><span class="o">=</span><span class="s2">&#34;table/orders&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">ScheduledActionName</span><span class="o">=</span><span class="s2">&#34;black-friday-pre-scale&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">Schedule</span><span class="o">=</span><span class="s2">&#34;cron(0 0 * * ? *)&#34;</span><span class="p">,</span>  <span class="c1"># 時間 lead 依事件性質決定、非固定 30-60 分鐘</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">ScalableTargetAction</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;MinCapacity&#34;</span><span class="p">:</span> <span class="mi">5000</span><span class="p">,</span> <span class="s2">&#34;MaxCapacity&#34;</span><span class="p">:</span> <span class="mi">50000</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h4 id="step-6mode-switch">Step 6：mode switch</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws dynamodb update-table <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --table-name orders <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --billing-mode-summary <span class="nv">BillingMode</span><span class="o">=</span>PAY_PER_REQUEST</span></span></code></pre></div><p>每張 table 24 小時內只能切一次、要計畫 maintenance window。</p>
<h4 id="step-7驗證點">Step 7：驗證點</h4>
<p>切換後第一週對比 cost + throttle metric、確認方向正確：</p>
<ul>
<li>cost 變化方向跟預期一致（on-demand 應該變貴 / provisioned 應該變便宜）</li>
<li>throttle rate 沒上升</li>
<li>latency p99 沒退化</li>
</ul>
<h4 id="step-8總成本評估軸-5--軸-6">Step 8：總成本評估（軸 5 + 軸 6）</h4>
<p>直接 cost + 工時機會成本 + 對照自管 cluster 的 cost crossover 曲線。Quarterly review 用這個公式、不是只看 monthly bill。</p>
<p><strong>Rollback boundary</strong>：on-demand → provisioned 隨時可切、但 baseline 要先 sized 好；切錯方向第一個月可逆、長期累積 cost 不可逆。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>production 觀察到的 6 個典型 anti-pattern：</p>
<h4 id="case-1on-demand-後-cost-翻-3-倍">Case 1：on-demand 後 cost 翻 3 倍</h4>
<p>dev team 切 on-demand「不用管 capacity」、但 workload 是 sustained constant、on-demand 6-7x base rate 全付出來。<code>9.C5 Amazon Ads</code> 明示「sustained workload 用 provisioned + auto-scaling」。修法：穩定 workload 用 provisioned + auto-scaling（軸 1 + 軸 2）。</p>
<h4 id="case-2auto-scaling-跟不上-spike">Case 2：auto-scaling 跟不上 spike</h4>
<p>流量 1 分鐘內 10x、auto-scaling alarm 5 分鐘才觸發、前 4 分鐘全 throttle。修法：peak/avg 高且 spike 突然 → on-demand、或 scheduled scaling 預先升配（軸 1 + 軸 4）；flash-sale 場景 auto-scaling 不夠快、必須 pre-provision。</p>
<h4 id="case-3on-demand-hot-partition-隱藏">Case 3：on-demand hot partition 隱藏</h4>
<p>on-demand 不顯示 throttle、latency 從 5ms 變 50ms、application timeout retry 加劇問題。修法：on-demand 仍要看 partition-level metric（Contributor Insights）、不能假設 mode 解決設計問題（跟 <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">partition-key-antipatterns</a> cross-link）；mode × partition 交叉判讀。</p>
<h4 id="case-4provisioned-target-utilization-設太高">Case 4：provisioned target utilization 設太高</h4>
<p>target = 90% 看似省、實際每次 spike 都先 throttle 再 scale。修法：70% buffer 給 scale latency、不要為了省 cost 把 utilization 推到極限。</p>
<h4 id="case-5頻繁切-mode-撞-24h-限制">Case 5：頻繁切 mode 撞 24h 限制</h4>
<p>team 想「白天 provisioned 晚上 on-demand」省 cost、但 mode 切換 24h 一次、計畫破產。修法：白天 provisioned + 晚上把 capacity 設低、不切 mode；用 scheduled scaling 處理日週期、不用 mode switch。</p>
<h4 id="case-6surge-後沒重評-mode長期燒錢軸-3-對應">Case 6：surge 後沒重評 mode、長期燒錢（軸 3 對應）</h4>
<p>Zoom 式 30x permanent baseline 上移後、原 on-demand 設計成本爆炸。修法：surge 結束 4-8 週後重評、若 baseline 維持 70%+ 改 provisioned；把「surge 後 mode review」寫進 runbook、不是 ad-hoc 才想到。</p>
<p><strong>Anti-recommendation</strong>：流量 &lt; 100 RPS、cost &lt; $50/月的小 table 不用糾結 mode、on-demand 簡單；workload 穩定且 cost 高才值得做 provisioned + auto-scaling 的工程投入。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<p>CloudWatch metric：</p>
<ul>
<li><code>ConsumedReadCapacityUnits</code> / <code>ConsumedWriteCapacityUnits</code>：基本用量</li>
<li><code>ProvisionedReadCapacityUnits</code> / <code>ProvisionedWriteCapacityUnits</code>：provisioned 預設值</li>
<li><code>ThrottledRequests</code>：provisioned mode 直接訊號、on-demand 為零不代表沒問題</li>
<li><code>SuccessfulRequestLatency</code> p99：on-demand mode 下 hot partition 訊號</li>
</ul>
<p><strong>新增的觀測軸</strong>（軸 2 / 軸 3 對應）：</p>
<ul>
<li>read/write ratio 7-day rolling avg、超過 ±30% 偏移觸發 review</li>
<li>surge baseline 4-week rolling avg、判斷 surge 是暫時還是永久</li>
<li>AWS Cost Explorer 按 table + mode 切 cost trend、月對比</li>
</ul>
<p>Auto-scaling activity log：CloudWatch alarm history + scaling activity，觀察 scaling 是否頻繁但 utilization 仍低（表示 alarm 設太敏感）。</p>
<p><strong>指標口徑紀律</strong>：引用 case 數字時明示口徑 — <code>9.C5</code> 90M reads/sec 是「年度峰值最高一秒、非平均」、<code>9.C20</code> 90% latency 降可能只 p50 不是 p99/p999、<code>9.C18</code> 30x DAU 是「permanent baseline 上移」非單日 peak。讀 vendor case 數字要分「最大瞬時 / 99 百分位 / 常態 / 滾動」四個口徑、不是混用。</p>
<p>Cost gate：每月 finance review 把 DynamoDB cost 對齊 access pattern volume、不只看絕對數字；軸 5 工時釋放跟軸 6 vendor crossover 也納入。</p>
<p>接回 <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>、<a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">1.10 KV / Document DB 容量規劃</a>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="frame-8-event-driven-scaling-5-種模式">Frame 8 event-driven scaling 5 種模式</h3>
<p><code>9.C5</code> / <code>9.C15</code> / <code>9.C18</code> / <code>9.C24</code> / <code>9.C27</code> 跨 case 揭露 event-driven scaling 至少 5 種形狀：</p>
<ul>
<li><strong>flash-sale spike</strong>：拓元 6750x in seconds（軸 4 走 pre-provision + composite PK）</li>
<li><strong>predictable peak</strong>：Disney+ 新片首發（軸 4 走 scheduled scaling）</li>
<li><strong>sustained growth</strong>：Amazon Ads / Capcom（軸 1 + 軸 5 → provisioned + auto-scaling）</li>
<li><strong>surge baseline permanent shift</strong>：Zoom 30x DAU 不會回去（軸 3 → 重評 mode）</li>
<li><strong>B2B sustained + 高可用</strong>：Genesys 99.999%（軸 5 + 軸 6 → managed 工時釋放比 cost 重要）</li>
</ul>
<p>不是用「peak/avg &gt; 5x」單一閾值決策、是事件型分類 × 軸合成。</p>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">partition-key-antipatterns</a> — capacity mode 不解 hot partition、mode × partition 交叉判讀</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">single-table-design-pattern</a> — access pattern 影響 peak/avg ratio 跟 read/write ratio</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">gsi-lsi-design</a> — GSI 多時 cost 跟 mode 互動</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/" data-link-title="DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例" data-link-desc="Global Tables 不只是 conflict 痛點、也是 cross-device sync / global read / DR failover 的正向工程方案；本文展開 B2B SaaS vs B2C 業務 driver、LWW conflict resolution、reconciliation pipeline，含 Genesys 99.999% 跨 15 region 跟 Disney&#43; 跨裝置同步的對照">global-tables-conflict</a> — 多 region capacity 規劃放大、軸 5 工時釋放在 multi-region 更顯著</li>
<li>Migration playbook：跨 vendor cost optimization（如 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato TiDB → DynamoDB</a>）對應 type C operational hybrid</li>
<li>替代路由：cost 極度敏感 + 流量穩定 + DBA 團隊已存在 → 自管 PostgreSQL / MySQL 可能更便宜（軸 6 crossover）、回 <a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL vendor</a></li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">Zoom 9.C18</a> 互引：30x permanent surge 後的 mode 重評（軸 3 主案例）</li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">Capcom 9.C19</a> + <a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">Lemino 9.C29</a> 互引：DBA 工時釋放（軸 5 主案例）</li>
<li>跟 <a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">Aurora read-replica-scaling</a> 共軸 cross-link：本篇從 KV 層 mode 選擇切入、5 模式分類在本篇主寫；Aurora 從 SQL 讀副本視角切入、事件分級表（FanDuel 平日 / playoff / championship / Super Bowl）跟雙 SLO 並行（DraftKings 讀寫雙峰錯位）+ fleet 治理在 Aurora 端主寫、本篇不重複展開</li>
</ul>
]]></content:encoded></item><item><title>DynamoDB TTL 資料生命週期：自動過期、48 小時刪除延遲、過期仍可讀與 storage 成本</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/ttl-data-lifecycle/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/ttl-data-lifecycle/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB&lt;/a> overview 的 implementation-layer deep article。寫作參照 &lt;a href="https://tarrragon.github.io/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>訊息系統的 storage bill 每月穩定上漲、查 table 發現裡面堆了三年份的過期通知、沒人清。team 設了 TTL「自動清理」、結果兩個新問題冒出來：第一、設了 TTL 之後 storage 還是沒馬上降、過了好幾小時才開始掉；第二、有個報表 query 把「已過期但還沒被刪」的 item 也撈進來、算錯數字。兩個痛點揭露 DynamoDB TTL 的真實語意 — 它是 &lt;em>最終會刪除&lt;/em> 的背景機制、不是即時刪除、也不是查詢層的過濾器。本文展開 TTL 的 epoch 語意、刪除延遲特性、過期可讀陷阱與 storage 成本判讀。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>生命週期前提：先確認 workload 適配 DynamoDB&lt;/strong>：資料生命週期管理是 &lt;em>已選 DynamoDB&lt;/em> 之後才浮現的議題 — TTL 解的是「資料存進來之後怎麼自動退場」、而非「資料該不該存進 DynamoDB」。後者由 4 軸前置判讀決定：PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定、判讀軸詳見 &lt;a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀&lt;/a>。本文承接該前提、聚焦用 TTL 管理資料生命週期與 storage 成本。&lt;/p>&lt;/blockquote>
&lt;h2 id="核心機制ttl-attribute-與背景刪除">核心機制：TTL attribute 與背景刪除&lt;/h2>
&lt;p>DynamoDB TTL 讓 item 在指定時間後自動被刪除、不消耗寫容量。機制很簡單但語意有三個容易踩的邊界。&lt;/p>
&lt;p>&lt;strong>設定方式&lt;/strong>：在 item 上放一個數值 attribute、值是 &lt;em>Unix epoch 秒數&lt;/em>（不是毫秒、不是 ISO 字串）、並在 table 啟用 TTL 指向該 attribute：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">put_item&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;PK&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;MSG#&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">msg_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;SK&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;META&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;body&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;expireAt&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">30&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">86400&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 30 天後過期、epoch 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>三個關鍵語意&lt;/strong>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>語意&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;th>後果&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>刪除非即時&lt;/td>
 &lt;td>過期後由 AWS 背景程序刪除、通常 48 小時內、不保證準時&lt;/td>
 &lt;td>不能用 TTL 做即時失效邏輯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>過期仍可讀&lt;/td>
 &lt;td>過期但尚未被刪的 item 仍出現在 GetItem / Query / Scan 結果&lt;/td>
 &lt;td>read 路徑要 application 端 filter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>刪除免 WCU&lt;/td>
 &lt;td>TTL 刪除不消耗 write capacity&lt;/td>
 &lt;td>大量過期清理不增寫成本&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>第二列是報表算錯的根因：&lt;strong>TTL 不是查詢過濾器&lt;/strong>。過期到實際刪除之間有一段窗口、這期間 item 還在、還會被讀到。需要「過期立刻不可見」的、application 必須在讀取後自己比對 &lt;code>expireAt&lt;/code> 過濾。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Scope warning&lt;/strong>：「TTL 通常 48 小時內刪除」屬 AWS vendor 規格描述、AWS 不保證準時、實際延遲視 table 大小與背景負載而定、實作時 cross-verify 官方 doc。&lt;code>9.C26 PayPay&lt;/code> case 揭露「TTL 機制可自動清理過期訊息」的 &lt;em>用途&lt;/em>、未揭露刪除延遲的具體數字。&lt;/p>&lt;/blockquote>
&lt;p>對應 knowledge card：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ttl/" data-link-title="TTL" data-link-desc="說明資料過期時間如何影響快取新鮮度、成本與一致性">ttl&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/soft-ttl/" data-link-title="Soft TTL" data-link-desc="說明資料進入刷新期後仍可短暫使用以降低 stampede">soft-ttl&lt;/a>。&lt;/p>
&lt;h2 id="刪除延遲與過期可讀兩個必須處理的窗口">刪除延遲與過期可讀：兩個必須處理的窗口&lt;/h2>
&lt;p>TTL 的「最終刪除」特性製造兩個 application 必須意識的窗口。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB</a> overview 的 implementation-layer deep article。寫作參照 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">vendor deep article methodology</a>。</p></blockquote>
<p>訊息系統的 storage bill 每月穩定上漲、查 table 發現裡面堆了三年份的過期通知、沒人清。team 設了 TTL「自動清理」、結果兩個新問題冒出來：第一、設了 TTL 之後 storage 還是沒馬上降、過了好幾小時才開始掉；第二、有個報表 query 把「已過期但還沒被刪」的 item 也撈進來、算錯數字。兩個痛點揭露 DynamoDB TTL 的真實語意 — 它是 <em>最終會刪除</em> 的背景機制、不是即時刪除、也不是查詢層的過濾器。本文展開 TTL 的 epoch 語意、刪除延遲特性、過期可讀陷阱與 storage 成本判讀。</p>
<blockquote>
<p><strong>生命週期前提：先確認 workload 適配 DynamoDB</strong>：資料生命週期管理是 <em>已選 DynamoDB</em> 之後才浮現的議題 — TTL 解的是「資料存進來之後怎麼自動退場」、而非「資料該不該存進 DynamoDB」。後者由 4 軸前置判讀決定：PK 天然均勻 / control plane vs data plane / consistency 可接受 eventual / access pattern 穩定、判讀軸詳見 <a href="../single-table-design-pattern/#dynamodb-%e9%81%a9%e7%94%a8%e5%ba%a6%e5%89%8d%e7%bd%ae%e5%88%a4%e8%ae%804-%e8%bb%b8">single-table-design-pattern 開頭 4 軸前置判讀</a>。本文承接該前提、聚焦用 TTL 管理資料生命週期與 storage 成本。</p></blockquote>
<h2 id="核心機制ttl-attribute-與背景刪除">核心機制：TTL attribute 與背景刪除</h2>
<p>DynamoDB TTL 讓 item 在指定時間後自動被刪除、不消耗寫容量。機制很簡單但語意有三個容易踩的邊界。</p>
<p><strong>設定方式</strong>：在 item 上放一個數值 attribute、值是 <em>Unix epoch 秒數</em>（不是毫秒、不是 ISO 字串）、並在 table 啟用 TTL 指向該 attribute：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">table</span><span class="o">.</span><span class="n">put_item</span><span class="p">(</span><span class="n">Item</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s2">&#34;PK&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;MSG#</span><span class="si">{</span><span class="n">msg_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="s2">&#34;SK&#34;</span><span class="p">:</span> <span class="s2">&#34;META&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="s2">&#34;expireAt&#34;</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span> <span class="o">+</span> <span class="mi">30</span> <span class="o">*</span> <span class="mi">86400</span><span class="p">,</span>  <span class="c1"># 30 天後過期、epoch 秒</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">})</span></span></span></code></pre></div><p><strong>三個關鍵語意</strong>：</p>
<table>
  <thead>
      <tr>
          <th>語意</th>
          <th>內容</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>刪除非即時</td>
          <td>過期後由 AWS 背景程序刪除、通常 48 小時內、不保證準時</td>
          <td>不能用 TTL 做即時失效邏輯</td>
      </tr>
      <tr>
          <td>過期仍可讀</td>
          <td>過期但尚未被刪的 item 仍出現在 GetItem / Query / Scan 結果</td>
          <td>read 路徑要 application 端 filter</td>
      </tr>
      <tr>
          <td>刪除免 WCU</td>
          <td>TTL 刪除不消耗 write capacity</td>
          <td>大量過期清理不增寫成本</td>
      </tr>
  </tbody>
</table>
<p>第二列是報表算錯的根因：<strong>TTL 不是查詢過濾器</strong>。過期到實際刪除之間有一段窗口、這期間 item 還在、還會被讀到。需要「過期立刻不可見」的、application 必須在讀取後自己比對 <code>expireAt</code> 過濾。</p>
<blockquote>
<p><strong>Scope warning</strong>：「TTL 通常 48 小時內刪除」屬 AWS vendor 規格描述、AWS 不保證準時、實際延遲視 table 大小與背景負載而定、實作時 cross-verify 官方 doc。<code>9.C26 PayPay</code> case 揭露「TTL 機制可自動清理過期訊息」的 <em>用途</em>、未揭露刪除延遲的具體數字。</p></blockquote>
<p>對應 knowledge card：<a href="/blog/backend/knowledge-cards/ttl/" data-link-title="TTL" data-link-desc="說明資料過期時間如何影響快取新鮮度、成本與一致性">ttl</a>、<a href="/blog/backend/knowledge-cards/soft-ttl/" data-link-title="Soft TTL" data-link-desc="說明資料進入刷新期後仍可短暫使用以降低 stampede">soft-ttl</a>。</p>
<h2 id="刪除延遲與過期可讀兩個必須處理的窗口">刪除延遲與過期可讀：兩個必須處理的窗口</h2>
<p>TTL 的「最終刪除」特性製造兩個 application 必須意識的窗口。</p>
<p><strong>窗口一：過期 → 實際刪除（可讀窗口）</strong>：</p>
<p>item 的 <code>expireAt</code> 已過、但背景程序還沒刪。這段時間 item：</p>
<ul>
<li>仍會被 <code>Query</code> / <code>Scan</code> / <code>GetItem</code> 撈到</li>
<li>仍佔 storage、仍計 storage 費</li>
<li>仍會被 secondary index 索引到</li>
</ul>
<p>application 若依賴「過期就消失」、會在這個窗口讀到 stale 資料。正確做法是 read 後 filter：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">now</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">[</span><span class="n">it</span> <span class="k">for</span> <span class="n">it</span> <span class="ow">in</span> <span class="n">response</span><span class="p">[</span><span class="s2">&#34;Items&#34;</span><span class="p">]</span> <span class="k">if</span> <span class="n">it</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;expireAt&#34;</span><span class="p">,</span> <span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="mi">62</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">now</span><span class="p">]</span></span></span></code></pre></div><p>或在 query 加 <code>FilterExpression</code> 排除過期 item（注意 filter 在讀取後套用、仍消耗讀容量）。</p>
<p><strong>窗口二：TTL 刪除 → stream record</strong>：</p>
<p>TTL 刪除會在 stream 產生一筆 <code>REMOVE</code> record、且 <code>userIdentity</code> 標記為 DynamoDB 服務本身（principal <code>dynamodb.amazonaws.com</code>）。這讓「過期歸檔」成為可能 — 下游 Lambda 收到 TTL 刪除事件、把 item 寫進冷儲存（S3）再讓它從 hot table 消失：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">event</span><span class="p">[</span><span class="s2">&#34;Records&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="k">if</span> <span class="n">record</span><span class="p">[</span><span class="s2">&#34;eventName&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&#34;REMOVE&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">            <span class="n">principal</span> <span class="o">=</span> <span class="n">record</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;userIdentity&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;principalId&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">            <span class="k">if</span> <span class="n">principal</span> <span class="o">==</span> <span class="s2">&#34;dynamodb.amazonaws.com&#34;</span><span class="p">:</span>  <span class="c1"># TTL 刪除、非 application 刪除</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">                <span class="n">archive_to_s3</span><span class="p">(</span><span class="n">record</span><span class="p">[</span><span class="s2">&#34;dynamodb&#34;</span><span class="p">][</span><span class="s2">&#34;OldImage&#34;</span><span class="p">])</span></span></span></code></pre></div><p>區分「TTL 自動刪除」vs「application 主動刪除」靠 <code>userIdentity</code> — 兩者都是 <code>REMOVE</code> record、但只有 TTL 刪除帶服務 principal。對應 <a href="/blog/backend/01-database/vendors/dynamodb/streams-lambda-event-driven/" data-link-title="DynamoDB Streams 與 Lambda 事件驅動：CDC、shard 順序保證、消費模式與失敗處理" data-link-desc="DynamoDB Streams 不是免費的可靠事件流；本文展開 stream record 的四種 view type、shard 對應 partition 的順序保證邊界、Lambda event source mapping vs Kinesis 消費模式、at-least-once 下游冪等需求，以及 batch 失敗時的 bisect / DLQ 處理">streams-lambda-event-driven</a>。</p>
<blockquote>
<p><strong>Scope warning</strong>：stream record 的 <code>userIdentity</code> 標記屬 vendor 規格、欄位細節 cross-verify 官方 doc；本段機制描述非 production case 揭露。</p></blockquote>
<h2 id="操作流程">操作流程</h2>
<p>從生命週期需求到上線的 6 步流程。</p>
<h4 id="step-1判斷資料是否適合-ttl-管理">Step 1：判斷資料是否適合 TTL 管理</h4>
<p>適合 TTL 的資料有「自然過期時間」：session、訊息通知、暫存 token、event log、合規保留期到期的資料。不適合的：需要精確即時刪除的、需要刪除前審批的、永久保存的。</p>
<h4 id="step-2設計-expireat-計算">Step 2：設計 expireAt 計算</h4>
<p>寫入時算好 epoch 秒數的 <code>expireAt</code>；不同資料類型可不同保留期（通知 30 天、session 1 天、audit 依合規要求）。</p>
<h4 id="step-3啟用-table-ttl">Step 3：啟用 table TTL</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws dynamodb update-time-to-live <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --table-name messages <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --time-to-live-specification <span class="s2">&#34;Enabled=true, AttributeName=expireAt&#34;</span></span></span></code></pre></div><h4 id="step-4read-路徑加過期過濾">Step 4：read 路徑加過期過濾</h4>
<p>所有面向用戶的讀取、在 application 端比對 <code>expireAt</code>（或加 FilterExpression）；不要假設過期 item 已消失。</p>
<h4 id="step-5可選接-ttl-刪除歸檔">Step 5：（可選）接 TTL 刪除歸檔</h4>
<p>需要保留過期資料的、接 stream Lambda、用 <code>userIdentity</code> 辨識 TTL 刪除、歸檔到 S3。</p>
<h4 id="step-6驗證點">Step 6：驗證點</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 寫一筆短 TTL item、等過期後確認：</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 1. 過期但未刪窗口內仍可讀到（驗證需要 filter）</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 2. 數小時後背景刪除生效、storage 下降</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 3. 若接歸檔、確認 S3 收到對應 OldImage</span></span></span></code></pre></div><p><strong>Rollback boundary</strong>：關閉 TTL 即停止自動刪除、已刪除的 item 不可恢復（除非有歸檔）；啟用 TTL 前先確認 <code>expireAt</code> 計算正確、避免誤設過短把活躍資料刪掉。</p>
<h2 id="失敗模式">失敗模式</h2>
<p>production 常見的 5 個踩雷：</p>
<h4 id="case-1expireat-用毫秒或-iso-字串">Case 1：expireAt 用毫秒或 ISO 字串</h4>
<p>TTL 只認 Unix epoch 秒；填毫秒（多三位數）會讓過期時間落在遙遠未來、item 永不過期；填字串 TTL 直接不生效。修法：統一用 <code>int(time.time()) + seconds</code>、寫測試驗證 attribute 是秒級數值。</p>
<h4 id="case-2以為-ttl-是即時刪除做即時失效邏輯">Case 2：以為 TTL 是即時刪除、做即時失效邏輯</h4>
<p>用 TTL 當「到點立刻不可用」的開關（如優惠券到期）、實際過期後幾小時還能用。修法：即時失效靠 application 邏輯比對時間、TTL 只負責 <em>清理 storage</em>、兩者分開。</p>
<h4 id="case-3報表--對帳撈到過期未刪-item">Case 3：報表 / 對帳撈到過期未刪 item</h4>
<p>聚合 query 沒過濾過期 item、把可讀窗口內的殘留資料算進去。修法：所有讀取路徑一致地過濾 <code>expireAt</code>；對帳查詢明確排除過期。</p>
<h4 id="case-4誤設過短保留期刪掉活躍資料">Case 4：誤設過短保留期刪掉活躍資料</h4>
<p>這個 case 跟前三個的失敗代價層級不同。前面的踩雷多半可回復 — storage 緩漲可回填、過期未刪可在讀取路徑加 filter、index 殘留會隨背景刪除自然消退。誤設過短保留期則是 <em>不可逆</em> 的：<code>expireAt</code> 計算 bug（少乘 86400、用錯時區基準）把保留期算成幾小時、背景程序把仍在使用的活躍資料當成過期 item 刪除、而 TTL 刪除不寫 undo log、刪掉就沒有從 DynamoDB 端救回的途徑、只能靠外部備份（PITR / 另存的 stream archive）回灌、且回灌期間資料缺口已經對線上服務造成影響。</p>
<p>代價的關鍵在於計算錯誤的爆炸半徑：一個錯誤常數會同時套用到所有新寫入 item、刪除是持續發生的背景行為、發現時往往已刪掉大批資料。修法的重心因此放在 <em>上線前驗證</em> 而非事後補救：上線前在 staging 用短週期資料驗證 <code>expireAt</code> 算出的絕對時間點符合預期、TTL 啟用初期把 <code>TimeToLiveDeletedItemCount</code> 跟預估刪除量對照、刪除量明顯偏高就立即停用 TTL 並排查計算、不等 storage 趨勢確認。對保留期敏感的 table 先開 PITR 當不可逆操作的最後防線。</p>
<h4 id="case-5過期-item-仍被-gsi-索引推高-index-成本">Case 5：過期 item 仍被 GSI 索引、推高 index 成本</h4>
<p>過期未刪 item 仍佔 GSI storage；大量過期堆積時 GSI 成本沒因「邏輯過期」下降。修法：理解 GSI 跟著 base item 生命週期、storage 降要等實際刪除；對成本敏感的 sparse index 設計可讓過期 item 不進 GSI（對應 <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">gsi-lsi-design sparse index</a>）。</p>
<p><strong>Anti-recommendation</strong>：資料量小、storage 成本可忽略、或刪除需要審批/合規記錄 → 不必用 TTL；手動或排程刪除更可控。TTL 的價值在「大量有自然過期時間的資料、要低成本自動清理」（如 PayPay 式每日上億訊息）。</p>
<h2 id="容量與觀測">容量與觀測</h2>
<p>CloudWatch metric：</p>
<ul>
<li><code>TimeToLiveDeletedItemCount</code>：TTL 背景刪除的 item 數、確認 TTL 真的在運作</li>
<li>table <code>ItemCount</code> / storage size：長期趨勢、確認過期清理讓 storage 趨於穩態</li>
<li>過期未刪比例：自行用 <code>expireAt &lt; now</code> 的 item 數估算可讀窗口殘留量</li>
</ul>
<p><strong>判讀</strong>：</p>
<ul>
<li><code>TimeToLiveDeletedItemCount</code> 為零但有設過期資料 → TTL 沒生效（attribute 名稱錯 / 值格式錯）</li>
<li>storage 持續上漲且 TTL 刪除量遠小於寫入量 → 保留期設太長、或寫入遠超過期速度、要重估保留策略</li>
<li>大量過期未刪堆積 → 背景刪除跟不上寫入、storage 成本被殘留拉高</li>
</ul>
<blockquote>
<p><strong>Scope warning</strong>：<code>9.C26 PayPay</code> 的「3 億/天 × 30 天 = 90 億筆」是 PayPay case 文章（9.C26）的策略段推算、非 PayPay 官方揭露的精確 item 數；引用時當量級壓力 anchor、不當精確數字。</p></blockquote>
<p>接回 <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>、<a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">1.10 KV / Document DB 容量規劃</a>。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<h3 id="ttl-vs-cache-ttl-vs-合規保留">TTL vs cache TTL vs 合規保留</h3>
<p>「TTL」這個詞在不同層意義不同、不要混用：</p>
<ul>
<li><strong>DynamoDB TTL</strong>：主資料的生命週期管理、最終刪除、本篇主寫</li>
<li><strong>cache TTL</strong>（如 DAX item / query cache、Redis TTL）：快取副本的新鮮度邊界、過期是「重新回源」不是「刪除主資料」、主寫於 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 與 <a href="/blog/backend/01-database/vendors/dynamodb/dax-caching-strategy/" data-link-title="DynamoDB DAX 快取策略：cluster 架構、item/query cache、write-through 與 invalidation 邊界" data-link-desc="DAX 不是「加上去就變快」的開關；本文展開 DAX cluster 架構、item cache vs query cache 兩種快取、write-through 一致性語意、query cache 只靠 TTL 失效的陷阱，以及 strongly consistent read 繞過 cache 的邊界，含 Lemino 讀峰值補位 case fact 與 gsi-lsi-design 的 SSoT 切分">dax-caching-strategy</a></li>
<li><strong>合規保留期</strong>：法規要求的最短/最長保存、可用 TTL 實作到期清理、但刪除前的稽核記錄要另外保留（對應 <a href="/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 audit trail</a>）</li>
</ul>
<h3 id="sibling-與-cross-link">Sibling 與 cross-link</h3>
<ul>
<li><a href="/blog/backend/01-database/vendors/dynamodb/streams-lambda-event-driven/" data-link-title="DynamoDB Streams 與 Lambda 事件驅動：CDC、shard 順序保證、消費模式與失敗處理" data-link-desc="DynamoDB Streams 不是免費的可靠事件流；本文展開 stream record 的四種 view type、shard 對應 partition 的順序保證邊界、Lambda event source mapping vs Kinesis 消費模式、at-least-once 下游冪等需求，以及 batch 失敗時的 bisect / DLQ 處理">streams-lambda-event-driven</a> — TTL 刪除觸發 stream REMOVE record、用 userIdentity 辨識、可做過期歸檔</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">single-table-design-pattern</a> — single-table 下不同 entity 用不同 expireAt 保留期</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">gsi-lsi-design</a> — 過期未刪 item 仍佔 GSI、sparse index 可讓過期不進 GSI</li>
<li><a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">on-demand-vs-provisioned</a> — TTL 刪除免 WCU、不影響寫容量規劃、但 storage 成本要靠 TTL 控制</li>
<li>替代路由：快取副本新鮮度 → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a>；合規稽核 → <a href="/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 audit trail</a></li>
<li>跟 <a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">PayPay 9.C26</a> 互引：每日上億訊息用 TTL 自動清理避免 storage 爆炸的 case anchor</li>
</ul>
]]></content:encoded></item></channel></rss>