<?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>Feature-Tier on Tarragon</title><link>https://tarrragon.github.io/blog/tags/feature-tier/</link><description>Recent content in Feature-Tier on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sat, 20 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/feature-tier/index.xml" rel="self" type="application/rss+xml"/><item><title>功能分層與 Backend 選擇</title><link>https://tarrragon.github.io/blog/monitoring/04-collector/feature-tier-boundary/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/04-collector/feature-tier-boundary/</guid><description>&lt;p>Collector 的可插拔 Storage Backend 分成兩個功能層級。分界線是查詢模式 — SQLite 能高效處理的查詢定義了簡單版的功能邊界，超出的查詢需求觸發 PostgreSQL 的引入。所有事件都經過同一個 Ingestion domain，差異在 Query 和 Dashboard domain 能提供什麼能力。&lt;/p>
&lt;h2 id="sqlite-層開發者工具">SQLite 層：開發者工具&lt;/h2>
&lt;p>SQLite 層提供的功能聚焦在「開發者自己 debug 和監控」。所有查詢都是單一維度的 — 按時間、按類型、按名稱過濾，不需要跨事件 JOIN 或跨使用者聚合。&lt;/p>
&lt;h3 id="承載的功能">承載的功能&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>查詢模式&lt;/th>
 &lt;th>SQL 範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>最近 error 列表&lt;/td>
 &lt;td>按 type + 時間過濾&lt;/td>
 &lt;td>&lt;code>WHERE type='error' ORDER BY ts DESC LIMIT 20&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error 計數（按 name 分群）&lt;/td>
 &lt;td>單表 GROUP BY&lt;/td>
 &lt;td>&lt;code>SELECT name, COUNT(*) FROM events WHERE type='error' GROUP BY name&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>單次 session 回放&lt;/td>
 &lt;td>按 session_id 過濾&lt;/td>
 &lt;td>&lt;code>WHERE session_id='xxx' ORDER BY ts&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件時間軸&lt;/td>
 &lt;td>按時間排序&lt;/td>
 &lt;td>&lt;code>WHERE ts BETWEEN ? AND ? ORDER BY ts&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基本 rule engine&lt;/td>
 &lt;td>逐筆事件評估&lt;/td>
 &lt;td>收到事件時逐條比對 rule（不需要查歷史）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CLI 查詢&lt;/td>
 &lt;td>任意過濾&lt;/td>
 &lt;td>&lt;code>WHERE type=? AND name LIKE ? AND ts &amp;gt; ?&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這些功能覆蓋開發者日常 debug 和監控的核心操作 — 查錯誤、看時間軸、回放 session、設規則告警。&lt;/p>
&lt;h3 id="對應的-dashboard-視圖">對應的 Dashboard 視圖&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>視圖&lt;/th>
 &lt;th>顯示&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>總覽頁&lt;/td>
 &lt;td>最近 1 小時的事件計數（按 type 分）+ 最近 error 列表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件詳情&lt;/td>
 &lt;td>單筆事件的完整 JSON&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Session 回放&lt;/td>
 &lt;td>單次 session 內的事件序列&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="對應的事件消費">對應的事件消費&lt;/h3>
&lt;p>SQLite 層消費所有四類事件，但消費方式是「單筆或單 session 級查詢」：&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>event&lt;/td>
 &lt;td>按名稱計數、按 session 排列&lt;/td>
 &lt;td>原始 7 天（debug）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>error&lt;/td>
 &lt;td>按名稱分群、按時間排列、看 stack trace&lt;/td>
 &lt;td>原始 30 天（error 追蹤價值較長）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>metric&lt;/td>
 &lt;td>按名稱查最近 N 筆的值&lt;/td>
 &lt;td>原始 7 天 + 每小時聚合 90 天&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle&lt;/td>
 &lt;td>按 session 排列、看狀態轉換&lt;/td>
 &lt;td>原始 7 天&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="postgresql-層行為分析">PostgreSQL 層：行為分析&lt;/h2>
&lt;p>PostgreSQL 層在 SQLite 層的基礎上加入「跨 session、跨使用者的聚合分析」。這些查詢需要 JOIN 多張表、計算時間窗口、處理大量資料的 GROUP BY — SQLite 的單寫者模型和有限的查詢最佳化器在這些場景下效能不足。&lt;/p>
&lt;h3 id="觸發引入-postgresql-的功能需求">觸發引入 PostgreSQL 的功能需求&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能需求&lt;/th>
 &lt;th>為什麼 SQLite 不夠&lt;/th>
 &lt;th>PostgreSQL 提供什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Funnel 分析&lt;/strong>&lt;/td>
 &lt;td>跨大量 session 的 multi-step JOIN 和聚合效能不足&lt;/td>
 &lt;td>Window functions + 高效 JOIN&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Cohort 留存&lt;/strong>&lt;/td>
 &lt;td>需要按「註冊週」分群、計算每週的回訪率&lt;/td>
 &lt;td>Date functions + 大規模 GROUP BY&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>RFM 分群&lt;/strong>&lt;/td>
 &lt;td>需要跨所有使用者計算 recency/frequency/monetary&lt;/td>
 &lt;td>全表聚合 + 分位數計算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>時間趨勢 dashboard&lt;/strong>&lt;/td>
 &lt;td>需要「過去 30 天每小時的 error P95」&lt;/td>
 &lt;td>時間分桶 + percentile 函數&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>高併發寫入&lt;/strong>&lt;/td>
 &lt;td>多個 SDK 同時 flush 且持續出現 database is locked&lt;/td>
 &lt;td>連線池 + 並行寫入&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>長期保留 + 聚合&lt;/strong>&lt;/td>
 &lt;td>降採樣的 materialized view&lt;/td>
 &lt;td>REFRESH MATERIALIZED VIEW&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="判斷公式">判斷公式&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">需要 funnel / cohort / RFM 任一 → PostgreSQL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">需要跨使用者聚合（不只看自己的資料） → PostgreSQL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">需要高併發寫入（多個 SDK 同時 flush 且持續出現 database is locked 錯誤） → PostgreSQL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">以上都不需要 → SQLite 足夠&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="對應的-dashboard-視圖sqlite-層不提供">對應的 Dashboard 視圖（SQLite 層不提供）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>視圖&lt;/th>
 &lt;th>查詢模式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Funnel 漏斗&lt;/td>
 &lt;td>多步驟轉換率（session 級 JOIN）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cohort 留存表&lt;/td>
 &lt;td>時間窗口 × 群組矩陣&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>RFM 分群散佈&lt;/td>
 &lt;td>三維度分位數計算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error 趨勢圖（長期）&lt;/td>
 &lt;td>30 天 × 每小時的時間序列&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>效能 P95 趨勢&lt;/td>
 &lt;td>percentile_cont 視窗函數&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="對應的事件消費-1">對應的事件消費&lt;/h3>
&lt;p>PostgreSQL 層消費的事件和 SQLite 相同（Ingestion 不變），但消費方式從「單筆/單 session」擴展到「跨 session/跨使用者」：&lt;/p></description><content:encoded><![CDATA[<p>Collector 的可插拔 Storage Backend 分成兩個功能層級。分界線是查詢模式 — SQLite 能高效處理的查詢定義了簡單版的功能邊界，超出的查詢需求觸發 PostgreSQL 的引入。所有事件都經過同一個 Ingestion domain，差異在 Query 和 Dashboard domain 能提供什麼能力。</p>
<h2 id="sqlite-層開發者工具">SQLite 層：開發者工具</h2>
<p>SQLite 層提供的功能聚焦在「開發者自己 debug 和監控」。所有查詢都是單一維度的 — 按時間、按類型、按名稱過濾，不需要跨事件 JOIN 或跨使用者聚合。</p>
<h3 id="承載的功能">承載的功能</h3>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>查詢模式</th>
          <th>SQL 範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最近 error 列表</td>
          <td>按 type + 時間過濾</td>
          <td><code>WHERE type='error' ORDER BY ts DESC LIMIT 20</code></td>
      </tr>
      <tr>
          <td>Error 計數（按 name 分群）</td>
          <td>單表 GROUP BY</td>
          <td><code>SELECT name, COUNT(*) FROM events WHERE type='error' GROUP BY name</code></td>
      </tr>
      <tr>
          <td>單次 session 回放</td>
          <td>按 session_id 過濾</td>
          <td><code>WHERE session_id='xxx' ORDER BY ts</code></td>
      </tr>
      <tr>
          <td>事件時間軸</td>
          <td>按時間排序</td>
          <td><code>WHERE ts BETWEEN ? AND ? ORDER BY ts</code></td>
      </tr>
      <tr>
          <td>基本 rule engine</td>
          <td>逐筆事件評估</td>
          <td>收到事件時逐條比對 rule（不需要查歷史）</td>
      </tr>
      <tr>
          <td>CLI 查詢</td>
          <td>任意過濾</td>
          <td><code>WHERE type=? AND name LIKE ? AND ts &gt; ?</code></td>
      </tr>
  </tbody>
</table>
<p>這些功能覆蓋開發者日常 debug 和監控的核心操作 — 查錯誤、看時間軸、回放 session、設規則告警。</p>
<h3 id="對應的-dashboard-視圖">對應的 Dashboard 視圖</h3>
<table>
  <thead>
      <tr>
          <th>視圖</th>
          <th>顯示</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>總覽頁</td>
          <td>最近 1 小時的事件計數（按 type 分）+ 最近 error 列表</td>
      </tr>
      <tr>
          <td>事件詳情</td>
          <td>單筆事件的完整 JSON</td>
      </tr>
      <tr>
          <td>Session 回放</td>
          <td>單次 session 內的事件序列</td>
      </tr>
  </tbody>
</table>
<h3 id="對應的事件消費">對應的事件消費</h3>
<p>SQLite 層消費所有四類事件，但消費方式是「單筆或單 session 級查詢」：</p>
<table>
  <thead>
      <tr>
          <th>事件類型</th>
          <th>消費方式</th>
          <th>保留需求</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>event</td>
          <td>按名稱計數、按 session 排列</td>
          <td>原始 7 天（debug）</td>
      </tr>
      <tr>
          <td>error</td>
          <td>按名稱分群、按時間排列、看 stack trace</td>
          <td>原始 30 天（error 追蹤價值較長）</td>
      </tr>
      <tr>
          <td>metric</td>
          <td>按名稱查最近 N 筆的值</td>
          <td>原始 7 天 + 每小時聚合 90 天</td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>按 session 排列、看狀態轉換</td>
          <td>原始 7 天</td>
      </tr>
  </tbody>
</table>
<h2 id="postgresql-層行為分析">PostgreSQL 層：行為分析</h2>
<p>PostgreSQL 層在 SQLite 層的基礎上加入「跨 session、跨使用者的聚合分析」。這些查詢需要 JOIN 多張表、計算時間窗口、處理大量資料的 GROUP BY — SQLite 的單寫者模型和有限的查詢最佳化器在這些場景下效能不足。</p>
<h3 id="觸發引入-postgresql-的功能需求">觸發引入 PostgreSQL 的功能需求</h3>
<table>
  <thead>
      <tr>
          <th>功能需求</th>
          <th>為什麼 SQLite 不夠</th>
          <th>PostgreSQL 提供什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Funnel 分析</strong></td>
          <td>跨大量 session 的 multi-step JOIN 和聚合效能不足</td>
          <td>Window functions + 高效 JOIN</td>
      </tr>
      <tr>
          <td><strong>Cohort 留存</strong></td>
          <td>需要按「註冊週」分群、計算每週的回訪率</td>
          <td>Date functions + 大規模 GROUP BY</td>
      </tr>
      <tr>
          <td><strong>RFM 分群</strong></td>
          <td>需要跨所有使用者計算 recency/frequency/monetary</td>
          <td>全表聚合 + 分位數計算</td>
      </tr>
      <tr>
          <td><strong>時間趨勢 dashboard</strong></td>
          <td>需要「過去 30 天每小時的 error P95」</td>
          <td>時間分桶 + percentile 函數</td>
      </tr>
      <tr>
          <td><strong>高併發寫入</strong></td>
          <td>多個 SDK 同時 flush 且持續出現 database is locked</td>
          <td>連線池 + 並行寫入</td>
      </tr>
      <tr>
          <td><strong>長期保留 + 聚合</strong></td>
          <td>降採樣的 materialized view</td>
          <td>REFRESH MATERIALIZED VIEW</td>
      </tr>
  </tbody>
</table>
<h3 id="判斷公式">判斷公式</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">需要 funnel / cohort / RFM 任一 → PostgreSQL
</span></span><span class="line"><span class="ln">2</span><span class="cl">需要跨使用者聚合（不只看自己的資料） → PostgreSQL
</span></span><span class="line"><span class="ln">3</span><span class="cl">需要高併發寫入（多個 SDK 同時 flush 且持續出現 database is locked 錯誤） → PostgreSQL
</span></span><span class="line"><span class="ln">4</span><span class="cl">以上都不需要 → SQLite 足夠</span></span></code></pre></div><h3 id="對應的-dashboard-視圖sqlite-層不提供">對應的 Dashboard 視圖（SQLite 層不提供）</h3>
<table>
  <thead>
      <tr>
          <th>視圖</th>
          <th>查詢模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Funnel 漏斗</td>
          <td>多步驟轉換率（session 級 JOIN）</td>
      </tr>
      <tr>
          <td>Cohort 留存表</td>
          <td>時間窗口 × 群組矩陣</td>
      </tr>
      <tr>
          <td>RFM 分群散佈</td>
          <td>三維度分位數計算</td>
      </tr>
      <tr>
          <td>Error 趨勢圖（長期）</td>
          <td>30 天 × 每小時的時間序列</td>
      </tr>
      <tr>
          <td>效能 P95 趨勢</td>
          <td>percentile_cont 視窗函數</td>
      </tr>
  </tbody>
</table>
<h3 id="對應的事件消費-1">對應的事件消費</h3>
<p>PostgreSQL 層消費的事件和 SQLite 相同（Ingestion 不變），但消費方式從「單筆/單 session」擴展到「跨 session/跨使用者」：</p>
<table>
  <thead>
      <tr>
          <th>事件類型</th>
          <th>SQLite 層消費</th>
          <th>PostgreSQL 層新增消費</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>event</td>
          <td>按名稱計數</td>
          <td>funnel 步驟轉換、cohort 行為分群</td>
      </tr>
      <tr>
          <td>error</td>
          <td>按名稱分群</td>
          <td>跨版本 error 率比較、P95 回應時間趨勢</td>
      </tr>
      <tr>
          <td>metric</td>
          <td>最近 N 筆值</td>
          <td>長期趨勢（materialized view 預聚合）</td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>單 session 排列</td>
          <td>session 長度分佈、留存率計算</td>
      </tr>
  </tbody>
</table>
<h2 id="domain-的分層影響">Domain 的分層影響</h2>
<table>
  <thead>
      <tr>
          <th>Domain</th>
          <th>SQLite 層</th>
          <th>PostgreSQL 層新增</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Ingestion</strong></td>
          <td>HTTP POST → 驗證 → 寫入</td>
          <td>不變（寫入目標換 backend）</td>
      </tr>
      <tr>
          <td><strong>Storage</strong></td>
          <td>SQLite embedded</td>
          <td>PostgreSQL + 連線池</td>
      </tr>
      <tr>
          <td><strong>Query</strong></td>
          <td>單表過濾 + 單表 GROUP BY</td>
          <td>JOIN + window function + percentile</td>
      </tr>
      <tr>
          <td><strong>Rule</strong></td>
          <td>逐筆事件即時評估</td>
          <td>不變（rule 不依賴聚合查詢）</td>
      </tr>
      <tr>
          <td><strong>Dashboard</strong></td>
          <td>總覽 + 事件詳情 + session 回放</td>
          <td>新增 funnel / cohort / RFM / 趨勢圖</td>
      </tr>
  </tbody>
</table>
<p>Ingestion 和 Rule 兩個 domain 和 storage backend 無關 — 事件進來的方式和規則評估的邏輯不因 backend 改變。Query 和 Dashboard 是分層影響最大的兩個 domain — PostgreSQL 層的查詢能力決定了 Dashboard 能提供什麼視圖。</p>
<h2 id="實作邊界">實作邊界</h2>
<p>Storage interface 用 Go 的 interface composition 分成兩層：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">BasicStorage</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nf">Store</span><span class="p">(</span><span class="nx">event</span> <span class="nx">Event</span><span class="p">)</span> <span class="kt">error</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nf">Query</span><span class="p">(</span><span class="nx">filter</span> <span class="nx">QueryFilter</span><span class="p">)</span> <span class="p">([]</span><span class="nx">Event</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nf">Close</span><span class="p">()</span> <span class="kt">error</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nf">Downsample</span><span class="p">()</span> <span class="kt">error</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nf">Purge</span><span class="p">()</span> <span class="kt">error</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">type</span> <span class="nx">AnalyticsStorage</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">BasicStorage</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nf">Aggregate</span><span class="p">(</span><span class="nx">spec</span> <span class="nx">AggregateSpec</span><span class="p">)</span> <span class="p">(</span><span class="nx">AggregateResult</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nf">Funnel</span><span class="p">(</span><span class="nx">steps</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="nx">timeWindow</span> <span class="nx">Duration</span><span class="p">)</span> <span class="p">(</span><span class="nx">FunnelResult</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nf">Cohort</span><span class="p">(</span><span class="nx">groupBy</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">metric</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">CohortResult</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>SQLite implementation 只實作 <code>BasicStorage</code>。PostgreSQL implementation 實作 <code>AnalyticsStorage</code>。Dashboard 用 Go 的 type assertion（<code>if as, ok := storage.(AnalyticsStorage); ok { ... }</code>）判斷能力 — funnel/cohort 視圖在 SQLite 模式下不顯示入口，而非顯示後報錯。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>可插拔 Storage Backend 的架構 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a></li>
<li>事件枚舉方法（哪些事件要收） → <a href="/blog/monitoring/01-mental-model/event-enumeration-method/" data-link-title="事件枚舉與補齊檢查" data-link-desc="從操作盤點系統性地推導出完整的事件清單 — 四類補齊檢查確保沒有遺漏、粒度判準確保每個事件只記一個事實">事件枚舉與補齊檢查</a></li>
<li>分層保留策略 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進的分層保留段</a></li>
<li>Funnel 分析的完整方法論 → <a href="/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">Funnel analysis</a></li>
<li>查詢消費模式（各場景需要什麼事件）→ <a href="/blog/monitoring/04-collector/query-consumption-patterns/" data-link-title="查詢消費模式" data-link-desc="Debug / Alerting / 產品決策 / 安全審計 / 效能監控 — 五種查詢場景各需要什麼事件、什麼欄位、什麼查詢模式">查詢消費模式</a></li>
</ul>
]]></content:encoded></item></channel></rss>