<?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>Scale on Tarragon</title><link>https://tarrragon.github.io/blog/tags/scale/</link><description>Recent content in Scale on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 26 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/scale/index.xml" rel="self" type="application/rss+xml"/><item><title>Dataset 規模改變什麼可行：「需要 index」依 scale 而定</title><link>https://tarrragon.github.io/blog/report/dataset-scale-changes-feasibility/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/dataset-scale-changes-feasibility/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>「需要 index、需要 cache、需要 hot path」這類設計動詞、預設是 &lt;strong>production scale 的語言&lt;/strong>。Dataset 小的時候、O(N) scan 甚至 O(N²) 都可能 trivial、不需要 index。&lt;/p>
&lt;p>具體 threshold：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Dataset 大小&lt;/th>
 &lt;th>可行操作&lt;/th>
 &lt;th>Index / cache 必要？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>&amp;lt; 1 MB&lt;/strong>&lt;/td>
 &lt;td>O(N²) 全表 scan、JS regex、無腦比對&lt;/td>
 &lt;td>完全不需要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>1-10 MB&lt;/strong>&lt;/td>
 &lt;td>O(N) full scan per query、簡單 in-memory 處理&lt;/td>
 &lt;td>通常不需要、有 index 是 nice-to-have&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>10-100 MB&lt;/strong>&lt;/td>
 &lt;td>O(N) 仍可、但要避免 per-keystroke；考慮 lazy load + index&lt;/td>
 &lt;td>開始需要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>100 MB - 1 GB&lt;/strong>&lt;/td>
 &lt;td>必須 index / 分塊 / streaming&lt;/td>
 &lt;td>必要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>&amp;gt; 1 GB&lt;/strong>&lt;/td>
 &lt;td>必須分散式 / DB / search engine&lt;/td>
 &lt;td>強制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>錯誤預設&lt;/strong>：「production scale 設計」直接套用到小 dataset = 過度工程。&lt;strong>修法&lt;/strong>：先量測實際 dataset 大小、再決定要不要 index。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼預設-production-scale-是反模式">為什麼預設 production scale 是反模式&lt;/h2>
&lt;p>寫程式的習慣偏好「scalable solution」 — 但在 small dataset 情境下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>過度工程成本&lt;/strong>：寫 / 維護 index、cache invalidation、tiered storage 都是 cost&lt;/li>
&lt;li>&lt;strong>實際收益 0&lt;/strong>：dataset 小、O(N) scan 已經 &amp;lt; 1ms、index 沒帶來感知差異&lt;/li>
&lt;li>&lt;strong>複雜度引入新 bug&lt;/strong>：cache invalidation 是出名難題、small dataset 直接 scan 反而對&lt;/li>
&lt;/ul>
&lt;p>實務上 80% 內部工具 / 個人專案 / 小型部落格的 dataset 是「&amp;lt; 10 MB」級別。為它寫 production-scale 設計 = 為不存在的問題付成本。&lt;/p>
&lt;hr>
&lt;h2 id="具體-threshold-跟可行操作">具體 threshold 跟可行操作&lt;/h2>
&lt;p>每段 dataset 大小都有「直覺以為需要、實際不需要」的對照：&lt;/p>
&lt;h3 id="-1-mb極小">&amp;lt; 1 MB（極小）&lt;/h3>
&lt;p>例：個人部落格內容 metadata、小型 config、單頁 SPA state。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>直覺以為需要&lt;/th>
 &lt;th>實際不需要、可用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Search index&lt;/td>
 &lt;td>&lt;code>Array.filter(text.includes)&lt;/code> 就夠&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pagination&lt;/td>
 &lt;td>全載入、CSS scroll&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lazy load&lt;/td>
 &lt;td>一次全 fetch、&amp;lt; 100 KB 沒差&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Web Worker&lt;/td>
 &lt;td>主執行緒同步處理、&amp;lt; 1ms 不會卡&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cache&lt;/td>
 &lt;td>重算每次、&amp;lt; 1ms 沒差&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="1-10-mb小">1-10 MB（小）&lt;/h3>
&lt;p>例：本部落格 raw markdown（7.5 MB、135 篇）、中型 documentation site、小型 e-commerce catalog。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>直覺以為需要&lt;/th>
 &lt;th>實際不需要、可用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Search index 全集&lt;/td>
 &lt;td>Title-only index（更小）+ runtime substring fallback、依場景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分頁 query&lt;/td>
 &lt;td>全 fetch + client filter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Server-side rendering each request&lt;/td>
 &lt;td>Static + client interaction&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database indexing&lt;/td>
 &lt;td>In-memory map（如果 query pattern 簡單）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Background indexing&lt;/td>
 &lt;td>Build-time 一次處理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>但有時候已經值得 index — 視 query 頻率、複雜度、UX 需求而定（看 &lt;a href="../build-time-vs-runtime-computation-spectrum/">#87&lt;/a> 四軸）。&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>「需要 index、需要 cache、需要 hot path」這類設計動詞、預設是 <strong>production scale 的語言</strong>。Dataset 小的時候、O(N) scan 甚至 O(N²) 都可能 trivial、不需要 index。</p>
<p>具體 threshold：</p>
<table>
  <thead>
      <tr>
          <th>Dataset 大小</th>
          <th>可行操作</th>
          <th>Index / cache 必要？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>&lt; 1 MB</strong></td>
          <td>O(N²) 全表 scan、JS regex、無腦比對</td>
          <td>完全不需要</td>
      </tr>
      <tr>
          <td><strong>1-10 MB</strong></td>
          <td>O(N) full scan per query、簡單 in-memory 處理</td>
          <td>通常不需要、有 index 是 nice-to-have</td>
      </tr>
      <tr>
          <td><strong>10-100 MB</strong></td>
          <td>O(N) 仍可、但要避免 per-keystroke；考慮 lazy load + index</td>
          <td>開始需要</td>
      </tr>
      <tr>
          <td><strong>100 MB - 1 GB</strong></td>
          <td>必須 index / 分塊 / streaming</td>
          <td>必要</td>
      </tr>
      <tr>
          <td><strong>&gt; 1 GB</strong></td>
          <td>必須分散式 / DB / search engine</td>
          <td>強制</td>
      </tr>
  </tbody>
</table>
<p><strong>錯誤預設</strong>：「production scale 設計」直接套用到小 dataset = 過度工程。<strong>修法</strong>：先量測實際 dataset 大小、再決定要不要 index。</p>
<hr>
<h2 id="為什麼預設-production-scale-是反模式">為什麼預設 production scale 是反模式</h2>
<p>寫程式的習慣偏好「scalable solution」 — 但在 small dataset 情境下：</p>
<ul>
<li><strong>過度工程成本</strong>：寫 / 維護 index、cache invalidation、tiered storage 都是 cost</li>
<li><strong>實際收益 0</strong>：dataset 小、O(N) scan 已經 &lt; 1ms、index 沒帶來感知差異</li>
<li><strong>複雜度引入新 bug</strong>：cache invalidation 是出名難題、small dataset 直接 scan 反而對</li>
</ul>
<p>實務上 80% 內部工具 / 個人專案 / 小型部落格的 dataset 是「&lt; 10 MB」級別。為它寫 production-scale 設計 = 為不存在的問題付成本。</p>
<hr>
<h2 id="具體-threshold-跟可行操作">具體 threshold 跟可行操作</h2>
<p>每段 dataset 大小都有「直覺以為需要、實際不需要」的對照：</p>
<h3 id="-1-mb極小">&lt; 1 MB（極小）</h3>
<p>例：個人部落格內容 metadata、小型 config、單頁 SPA state。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際不需要、可用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Search index</td>
          <td><code>Array.filter(text.includes)</code> 就夠</td>
      </tr>
      <tr>
          <td>Pagination</td>
          <td>全載入、CSS scroll</td>
      </tr>
      <tr>
          <td>Lazy load</td>
          <td>一次全 fetch、&lt; 100 KB 沒差</td>
      </tr>
      <tr>
          <td>Web Worker</td>
          <td>主執行緒同步處理、&lt; 1ms 不會卡</td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>重算每次、&lt; 1ms 沒差</td>
      </tr>
  </tbody>
</table>
<h3 id="1-10-mb小">1-10 MB（小）</h3>
<p>例：本部落格 raw markdown（7.5 MB、135 篇）、中型 documentation site、小型 e-commerce catalog。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際不需要、可用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Search index 全集</td>
          <td>Title-only index（更小）+ runtime substring fallback、依場景</td>
      </tr>
      <tr>
          <td>分頁 query</td>
          <td>全 fetch + client filter</td>
      </tr>
      <tr>
          <td>Server-side rendering each request</td>
          <td>Static + client interaction</td>
      </tr>
      <tr>
          <td>Database indexing</td>
          <td>In-memory map（如果 query pattern 簡單）</td>
      </tr>
      <tr>
          <td>Background indexing</td>
          <td>Build-time 一次處理</td>
      </tr>
  </tbody>
</table>
<p>但有時候已經值得 index — 視 query 頻率、複雜度、UX 需求而定（看 <a href="../build-time-vs-runtime-computation-spectrum/">#87</a> 四軸）。</p>
<h3 id="10-100-mb中">10-100 MB（中）</h3>
<p>例：中型公司內部工具的 user list、開源 project 全 repo、中型 dataset analytics。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Index for everything</td>
          <td>主要 query patterns 加 index、cold path runtime 仍可</td>
      </tr>
      <tr>
          <td>Aggressive pagination</td>
          <td>分頁 + filter pushdown</td>
      </tr>
      <tr>
          <td>Server-side scan</td>
          <td>通常仍可、加 cache 即可</td>
      </tr>
      <tr>
          <td>分散式處理</td>
          <td>通常單機夠</td>
      </tr>
  </tbody>
</table>
<h3 id="100-mb---1-gb中-大">100 MB - 1 GB（中-大）</h3>
<p>例：中型 SaaS 的 user data、search engine over medium corpus、ML feature store。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Sharding</td>
          <td>通常單機 SSD / RAM 還夠、先垂直擴展</td>
      </tr>
      <tr>
          <td>Distributed system</td>
          <td>Single-node DB（PostgreSQL）+ index 多半夠</td>
      </tr>
      <tr>
          <td>Custom search engine</td>
          <td>Postgres FTS / SQLite FTS5 通常足夠</td>
      </tr>
      <tr>
          <td>Caching layer</td>
          <td>必要、但簡單 LRU 即可</td>
      </tr>
  </tbody>
</table>
<h3 id="-1-gb大">&gt; 1 GB（大）</h3>
<p>例：大型 SaaS、社交網路、search engine over web。</p>
<p>這個 scale 才真的需要 production-scale 設計：分散式、shard、index 嚴格設計、cache 多層、async pipeline。</p>
<hr>
<h2 id="我以後會長大的迷思">「我以後會長大」的迷思</h2>
<p>常見藉口：「現在 dataset 小、但以後會長大、所以該為以後設計」。</p>
<p>這個邏輯有兩個漏洞：</p>
<h3 id="漏洞-1未來成長不確定">漏洞 1：未來成長不確定</h3>
<p>多數內部工具 / 個人專案 / 中小企業 dataset <strong>不會</strong> 長到 production scale。為「以後可能爆炸」設計、多半是為不存在的未來付當下成本。</p>
<h3 id="漏洞-2成長前重-design-比現在-design-容易">漏洞 2：成長前重 design 比現在 design 容易</h3>
<p>當 dataset 真的長大、你會有：</p>
<ul>
<li>真實的 query pattern（vs 現在猜的）</li>
<li>真實的 hot spots（vs 現在猜的）</li>
<li>真實的 budget（vs 現在估的）</li>
</ul>
<p><strong>等需要時 redesign 比現在 over-design 划算</strong>。當前 simple design 也比較容易被 redesign（複雜結構難動）。</p>
<hr>
<h2 id="dataset-質的差別size-是-first-order-proxytype-是-second-order-multiplier">Dataset 質的差別：size 是 first-order proxy、type 是 second-order multiplier</h2>
<p>同樣 N MB dataset、不同 type 的處理成本差異大。size 只是 first approximation、要看 <strong>data type 跟 access pattern</strong>：</p>
<table>
  <thead>
      <tr>
          <th>Type</th>
          <th>1 MB 行為</th>
          <th>10 MB 行為</th>
          <th>100 MB 行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Plain text</strong></td>
          <td>regex 全掃 &lt; 1ms</td>
          <td>regex 全掃 &lt; 50ms</td>
          <td>需分塊或 index</td>
      </tr>
      <tr>
          <td><strong>JSON / structured</strong></td>
          <td>parse + filter &lt; 10ms</td>
          <td>parse 開始貴、可能要 stream parse</td>
          <td>強制 stream / index</td>
      </tr>
      <tr>
          <td><strong>Image / binary</strong></td>
          <td>load 即一個壓力、無法 in-memory regex</td>
          <td>不能放 client、需 lazy load</td>
          <td>必 server-side 處理</td>
      </tr>
      <tr>
          <td><strong>Time series</strong></td>
          <td>順序敏感、不能 random access</td>
          <td>同 + 必須有 time index</td>
          <td>同 + 必須分區</td>
      </tr>
      <tr>
          <td><strong>Graph</strong></td>
          <td>traversal 成本不只 size、看連通度</td>
          <td>連通度高 → 易爆炸</td>
          <td>必須圖 DB</td>
      </tr>
  </tbody>
</table>
<h3 id="同-size-不同-type-的可行性逆轉">同 size 不同 type 的可行性逆轉</h3>





<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 MB 純文字 search：trivial
</span></span><span class="line"><span class="ln">2</span><span class="cl">1 MB JSON deep filter：可能慢（parse + nested traversal）
</span></span><span class="line"><span class="ln">3</span><span class="cl">1 MB graph traversal：可能極慢（depth 深時呈指數）
</span></span><span class="line"><span class="ln">4</span><span class="cl">1 MB image：完全不能 client substring scan</span></span></code></pre></div><p><strong>判讀</strong>：先看 type、再看 size。size 算 capacity、type 決定每 byte 的成本。</p>
<h3 id="跨-type-共通的判準">跨 type 共通的判準</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「dataset 才 1 MB 應該 fast」</td>
          <td>先確認 type — JSON / 圖 / binary 行為差異大</td>
      </tr>
      <tr>
          <td>Text 規模套到 image 場景</td>
          <td>size 直覺失效、重新評估</td>
      </tr>
      <tr>
          <td>Graph dataset 用 size 推斷</td>
          <td>看 connectivity 不只看 size</td>
      </tr>
      <tr>
          <td>Time series 想用 random access</td>
          <td>type 不對、改 sequential 設計</td>
      </tr>
  </tbody>
</table>
<p>「Dataset size」不是萬用尺、不同 type 要套不同 threshold。</p>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>預設「需要 index」沒量 dataset</td>
          <td>過度工程、抽象 leak</td>
      </tr>
      <tr>
          <td>「production-grade」當設計詞、不分場景套</td>
          <td>內部工具 over-engineered</td>
      </tr>
      <tr>
          <td>Big-O 思維直接套小 dataset</td>
          <td>漏掉 constant factor、實際 O(N²) 比 O(N log N) 還快（小 N）</td>
      </tr>
      <tr>
          <td>Cache 在 dataset &lt; 1 MB 場景</td>
          <td>Cache invalidation 引入 bug、收益 0</td>
      </tr>
      <tr>
          <td>分散式設計在 single-node 夠用場景</td>
          <td>維運複雜度爆炸</td>
      </tr>
      <tr>
          <td>「scale 萬一爆」當預設</td>
          <td>機率低事件主導設計</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="何時應該照-production-scale-設計">何時應該照 production-scale 設計</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>已知會在 6 個月內 grow 10x+</td>
          <td>證據明確、提前 design 划算</td>
      </tr>
      <tr>
          <td>公開服務、流量不可控</td>
          <td>防爆炸、必要</td>
      </tr>
      <tr>
          <td>User-generated content（dataset size 由使用者決定）</td>
          <td>上限不可控</td>
      </tr>
      <tr>
          <td>Real-time / SLA 嚴格</td>
          <td>constant factor 也重要</td>
      </tr>
      <tr>
          <td>已經慢了、有實證</td>
          <td>Bottleneck 已浮現</td>
      </tr>
  </tbody>
</table>
<p>五類共通：<strong>有證據 dataset 會大 / 已大 / 不可控</strong>。其他情境用當前 dataset size 設計。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍</a></td>
          <td>本卡是 #43 在「規模假設」維度的展現 — 不該 over-claim 規模</td>
      </tr>
      <tr>
          <td><a href="../build-time-vs-runtime-computation-spectrum/">#87 Build-time vs Runtime</a></td>
          <td>#87 的「dataset 大小」軸跟本卡同骨</td>
      </tr>
      <tr>
          <td><a href="../capability-gap-three-layer-escalation/">#86 Capability gap 三層階梯</a></td>
          <td>小 dataset 內 L2 augmenting 通常足夠、不必跳 L3</td>
      </tr>
      <tr>
          <td><a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度</a></td>
          <td>「寫 production-scale」比「量 dataset 再決定」容易、跟實際 ROI 反相關</td>
      </tr>
      <tr>
          <td><a href="../two-occurrence-threshold/">#42 2 次門檻</a></td>
          <td>真實 production 問題 ≥ 2 次再升級設計、不為「萬一」設計</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫到「需要 index」沒先量 dataset</td>
          <td>量、可能不需要</td>
      </tr>
      <tr>
          <td>寫到「production-scale」當形容詞</td>
          <td>確認真的是 production scale、否則拿掉</td>
      </tr>
      <tr>
          <td>Cache 設計、dataset 卻 &lt; 1 MB</td>
          <td>拿掉 cache、直接重算</td>
      </tr>
      <tr>
          <td>分散式、卻是個人 project</td>
          <td>退回 single-node</td>
      </tr>
      <tr>
          <td>「以後會長大」當理由</td>
          <td>等真的長大、現在用 simple</td>
      </tr>
      <tr>
          <td>Big-O 焦慮、卻沒量實際 latency</td>
          <td>量了再決、constant factor 可能主導</td>
      </tr>
      <tr>
          <td>「lazy load 是必要」沒看 bundle size</td>
          <td>確認 bundle 真的大、&lt; 200 KB 全載入即可</td>
      </tr>
  </tbody>
</table>
<p><strong>核心</strong>：「需要 index / cache / 分散式 / lazy load」這類動詞<strong>不是普世真理、是 production scale 的詞</strong>。預設套用到小 dataset = 過度工程。<strong>先量再決</strong>、跟「猜的 scale」對齊不如跟「實際 dataset」對齊。</p>
]]></content:encoded></item></channel></rss>