<?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>Data-Shape on Tarragon</title><link>https://tarrragon.github.io/blog/tags/data-shape/</link><description>Recent content in Data-Shape on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 11 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/data-shape/index.xml" rel="self" type="application/rss+xml"/><item><title>2.8 Cache Data Shape 與 Access Pattern</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cache-data-shape-access-pattern/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cache-data-shape-access-pattern/</guid><description>&lt;p>Cache data shape 與 access pattern 的核心責任是讓快取資料結構反映服務語意。進入 Redis command 或特定快取服務前，讀者需要先知道 key、value、hash、set、sorted set、stream 與多層 cache 各自適合承擔哪種讀取責任。&lt;/p>
&lt;h2 id="key-space">Key Space&lt;/h2>
&lt;p>Key space 的責任是定義快取資料如何被定位、分組、失效與遷移。key 命名要包含資料責任、版本、租戶或區域等必要維度，讓失效與回退可控。&lt;/p>
&lt;p>常見 key 維度包含：&lt;/p>
&lt;ol>
&lt;li>資料類型，例如 &lt;code>product&lt;/code>、&lt;code>user-permission&lt;/code>、&lt;code>quota&lt;/code>。&lt;/li>
&lt;li>版本，例如 &lt;code>v1&lt;/code>、&lt;code>v2&lt;/code>。&lt;/li>
&lt;li>租戶或區域，例如 tenant、region、locale。&lt;/li>
&lt;li>實體識別，例如 product id、user id。&lt;/li>
&lt;/ol>
&lt;p>key 缺少版本時，cache migration 會變成破壞性替換。key 缺少租戶或區域時，失效範圍會被放大。&lt;/p>
&lt;h2 id="value-shape">Value Shape&lt;/h2>
&lt;p>Value shape 的責任是定義快取值的語意與演進方式。完整 JSON blob 適合一次讀取完整資料，但欄位更新與版本相容成本高；hash 適合欄位局部更新，但需要明確欄位責任；set 與 sorted set 適合集合與排名；counter 適合限流或計數。&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>string / blob&lt;/td>
 &lt;td>商品詳情、設定快照&lt;/td>
 &lt;td>schema 變更容易破壞相容&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>hash&lt;/td>
 &lt;td>使用者摘要、商品局部欄位&lt;/td>
 &lt;td>欄位責任不清會變成半正式狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>set&lt;/td>
 &lt;td>membership、權限集合&lt;/td>
 &lt;td>stale membership 可能造成越權&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>sorted set&lt;/td>
 &lt;td>排名、時間排序、優先級&lt;/td>
 &lt;td>score 語意錯誤會造成排序漂移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>counter&lt;/td>
 &lt;td>rate limit、配額&lt;/td>
 &lt;td>原子性與過期窗口要對齊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>stream&lt;/td>
 &lt;td>輕量事件流&lt;/td>
 &lt;td>容易和正式 message queue 責任混淆&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>資料形狀的本質是服務責任選擇，Redis 語法是落地方式。&lt;/p>
&lt;p>&lt;code>string / blob&lt;/code> 的判讀重點是整包資料是否需要一起讀取與一起失效。&lt;code>hash&lt;/code> 的判讀重點是欄位是否真的能獨立更新。&lt;code>set&lt;/code> 與 &lt;code>sorted set&lt;/code> 的判讀重點是 membership 或排序錯誤會造成什麼後果。&lt;code>counter&lt;/code> 的判讀重點是原子性與過期窗口。&lt;code>stream&lt;/code> 的判讀重點是這條路徑是否已經接近 message queue 責任。&lt;/p>
&lt;h2 id="access-pattern">Access Pattern&lt;/h2>
&lt;p>Access pattern 的責任是定義快取面對的讀寫節奏。高讀低寫、熱點讀取、短期活動尖峰、租戶隔離與跨區讀取，都會影響 key 設計與容量策略。&lt;/p>
&lt;p>高讀低寫適合長 TTL 與背景刷新；熱點讀取需要 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/hot-key/" data-link-title="Hot Key" data-link-desc="說明單一 key 承受大量讀寫時如何形成容量瓶頸">hot key&lt;/a> 保護；短期尖峰需要 warmup 與分散過期；多租戶場景需要避免單租戶 key 壓垮共享 cache。&lt;/p>
&lt;h2 id="multi-layer-cache">Multi-layer Cache&lt;/h2>
&lt;p>多層快取的責任是分散延遲與來源壓力。常見層次包含 process local cache、distributed cache、CDN 或 search/read model。每一層都需要定義 freshness、失效來源與 fallback。&lt;/p>
&lt;p>多層 cache 的主要風險是 stale 疊加。local cache stale、distributed cache stale 與 CDN stale 缺少共同失效策略時，讀者看到的錯誤會很難追。&lt;/p>
&lt;h3 id="ml-feature-store-的多層-cache-設計模式">ML feature store 的多層 cache 設計模式&lt;/h3>
&lt;p>ML inference 場景的 feature lookup 是多層 cache 的典型應用。&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi feature store&lt;/a> 的策略段提出 &lt;em>可重用做法&lt;/em>：用 L1 in-process cache + L2 distributed cache + L3 持久 store 三層。Tubi 實做的是把 feature store 從 ScyllaDB 遷到 ElastiCache（屬於 L2 層的選擇）、p99 &amp;lt; 10ms；三層架構是策略段推導出的通用設計、不一定 Tubi 完整實做。&lt;/p></description><content:encoded><![CDATA[<p>Cache data shape 與 access pattern 的核心責任是讓快取資料結構反映服務語意。進入 Redis command 或特定快取服務前，讀者需要先知道 key、value、hash、set、sorted set、stream 與多層 cache 各自適合承擔哪種讀取責任。</p>
<h2 id="key-space">Key Space</h2>
<p>Key space 的責任是定義快取資料如何被定位、分組、失效與遷移。key 命名要包含資料責任、版本、租戶或區域等必要維度，讓失效與回退可控。</p>
<p>常見 key 維度包含：</p>
<ol>
<li>資料類型，例如 <code>product</code>、<code>user-permission</code>、<code>quota</code>。</li>
<li>版本，例如 <code>v1</code>、<code>v2</code>。</li>
<li>租戶或區域，例如 tenant、region、locale。</li>
<li>實體識別，例如 product id、user id。</li>
</ol>
<p>key 缺少版本時，cache migration 會變成破壞性替換。key 缺少租戶或區域時，失效範圍會被放大。</p>
<h2 id="value-shape">Value Shape</h2>
<p>Value shape 的責任是定義快取值的語意與演進方式。完整 JSON blob 適合一次讀取完整資料，但欄位更新與版本相容成本高；hash 適合欄位局部更新，但需要明確欄位責任；set 與 sorted set 適合集合與排名；counter 適合限流或計數。</p>
<table>
  <thead>
      <tr>
          <th>資料形狀</th>
          <th>適合場景</th>
          <th>主要風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>string / blob</td>
          <td>商品詳情、設定快照</td>
          <td>schema 變更容易破壞相容</td>
      </tr>
      <tr>
          <td>hash</td>
          <td>使用者摘要、商品局部欄位</td>
          <td>欄位責任不清會變成半正式狀態</td>
      </tr>
      <tr>
          <td>set</td>
          <td>membership、權限集合</td>
          <td>stale membership 可能造成越權</td>
      </tr>
      <tr>
          <td>sorted set</td>
          <td>排名、時間排序、優先級</td>
          <td>score 語意錯誤會造成排序漂移</td>
      </tr>
      <tr>
          <td>counter</td>
          <td>rate limit、配額</td>
          <td>原子性與過期窗口要對齊</td>
      </tr>
      <tr>
          <td>stream</td>
          <td>輕量事件流</td>
          <td>容易和正式 message queue 責任混淆</td>
      </tr>
  </tbody>
</table>
<p>資料形狀的本質是服務責任選擇，Redis 語法是落地方式。</p>
<p><code>string / blob</code> 的判讀重點是整包資料是否需要一起讀取與一起失效。<code>hash</code> 的判讀重點是欄位是否真的能獨立更新。<code>set</code> 與 <code>sorted set</code> 的判讀重點是 membership 或排序錯誤會造成什麼後果。<code>counter</code> 的判讀重點是原子性與過期窗口。<code>stream</code> 的判讀重點是這條路徑是否已經接近 message queue 責任。</p>
<h2 id="access-pattern">Access Pattern</h2>
<p>Access pattern 的責任是定義快取面對的讀寫節奏。高讀低寫、熱點讀取、短期活動尖峰、租戶隔離與跨區讀取，都會影響 key 設計與容量策略。</p>
<p>高讀低寫適合長 TTL 與背景刷新；熱點讀取需要 <a href="/blog/backend/knowledge-cards/hot-key/" data-link-title="Hot Key" data-link-desc="說明單一 key 承受大量讀寫時如何形成容量瓶頸">hot key</a> 保護；短期尖峰需要 warmup 與分散過期；多租戶場景需要避免單租戶 key 壓垮共享 cache。</p>
<h2 id="multi-layer-cache">Multi-layer Cache</h2>
<p>多層快取的責任是分散延遲與來源壓力。常見層次包含 process local cache、distributed cache、CDN 或 search/read model。每一層都需要定義 freshness、失效來源與 fallback。</p>
<p>多層 cache 的主要風險是 stale 疊加。local cache stale、distributed cache stale 與 CDN stale 缺少共同失效策略時，讀者看到的錯誤會很難追。</p>
<h3 id="ml-feature-store-的多層-cache-設計模式">ML feature store 的多層 cache 設計模式</h3>
<p>ML inference 場景的 feature lookup 是多層 cache 的典型應用。<a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi feature store</a> 的策略段提出 <em>可重用做法</em>：用 L1 in-process cache + L2 distributed cache + L3 持久 store 三層。Tubi 實做的是把 feature store 從 ScyllaDB 遷到 ElastiCache（屬於 L2 層的選擇）、p99 &lt; 10ms；三層架構是策略段推導出的通用設計、不一定 Tubi 完整實做。</p>
<p><strong>通用三層模式</strong>（推導自 9.C25 策略段、實際分層深度視 workload）：</p>
<ul>
<li><strong>L1：in-process cache</strong>：跟 application 同一 process、避免 network hop、適合最熱的少量 features</li>
<li><strong>L2：distributed cache</strong>（ElastiCache / Memcached）：跨 application instance 共享、能擴容、Tubi 在這層用 ElastiCache 達 p99 &lt; 10ms</li>
<li><strong>L3：持久 store</strong>（ScyllaDB / DynamoDB / S3 + Parquet）：全量資料、cache miss 時的 fallback</li>
</ul>
<p>判讀重點：每層的 latency budget 跟 stale window 都應依 workload 跟業務容忍度設定。相對序列是 L1 stale window 最嚴、L2 中等、L3 為 source-of-truth 或可重算來源。三層 stale 若無共同失效策略、業務代價會落到 <em>推薦結果不穩定</em>、用戶看到不同 session 推不同內容。</p>
<h3 id="跨-cloud-部署的資料引力路由見-27">跨 cloud 部署的資料引力（路由：見 2.7）</h3>
<p>跨 cloud cache 部署的 <em>資料引力</em> 原則跟 <em>跨區一致性</em> 議題密切相關、主寫場域是 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">2.7 cache copy boundary 的跨區一致性窗口</a>。本章從 <em>data shape / access pattern</em> 角度補充：當 cache value 包含跨 region 共享的業務資料時、access pattern 自然偏向 <em>同 cloud read</em> + <em>跨 cloud batch sync</em>、不適合即時跨 cloud lookup。詳見 9.C35 Snap KeyDB 案例。</p>
<h2 id="選型前判準">選型前判準</h2>
<p>快取資料形狀選型前要先回答：</p>
<ol>
<li>讀取是單 key、批次 key、集合、排序還是計數。</li>
<li>寫入是整體替換、局部更新、追加還是原子遞增。</li>
<li>失效是單 key、群組、版本、租戶還是全域。</li>
<li>資料結構是否會讓快取承擔正式狀態責任。</li>
</ol>
<p>這些問題決定後續要比較 Redis data type、Memcached blob、CDN cache 或應用端 local cache。</p>
<h2 id="實體服務討論承接點">實體服務討論承接點</h2>
<p>實體快取服務文章要承接本篇的 data shape 與 access pattern。Redis/Valkey 的 hash、set、sorted set、stream 能表達多種資料形狀；Memcached 偏向簡單 key/value blob；CDN 與 local cache 則承擔不同層次的讀取加速。比較服務時要先問 access pattern，再問語法。</p>
<p>若讀取是單 key 或 blob，後續文章要比較 serialization、value size、TTL 與 eviction。若讀取是集合、排名或計數，後續文章要比較資料結構、原子性與容量行為。若讀取跨多層 cache，後續文章要比較失效傳播、stale 疊加與 observability。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>要處理 TTL 與容量策略，接著讀 <a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3 TTL 與 eviction</a>。要看選定形狀後各型別的操作語意、原子性與記憶體曲線，接著讀 <a href="/blog/backend/02-cache-redis/redis-data-types/" data-link-title="2.11 Redis data types 實作" data-link-desc="說明 sorted set、bitmap、HyperLogLog、counter 與 hash 各自承擔的服務語意、容量行為與原子性邊界">2.11 Redis data types 實作</a>。要處理 presence 類即時狀態，接著讀 <a href="/blog/backend/02-cache-redis/presence-store/" data-link-title="2.5 presence store 與即時狀態" data-link-desc="整理線上狀態、跨節點查詢與過期清理">2.5 presence store 與即時狀態</a>。</p>
]]></content:encoded></item></channel></rss>