<?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>Grafana Stack on Tarragon</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/</link><description>Recent content in Grafana Stack on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/index.xml" rel="self" type="application/rss+xml"/><item><title>LGTM Stack 組合運維：Loki + Grafana + Tempo + Mimir</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/lgtm-stack-operations/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/lgtm-stack-operations/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack&lt;/a> 的 vendor deep article，深化 overview 的元件組合段。初次接觸 Grafana Stack 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>Grafana Stack（LGTM = Loki + Grafana + Tempo + Mimir）是自架觀測平台的完整選項，四個元件各自承擔一類訊號的儲存跟查詢。理解每個元件的責任邊界、部署模式跟故障特性，才能避免「裝了四個元件但不知道哪個壞了」的黑盒問題。&lt;/p>
&lt;h2 id="四元件的責任分工">四元件的責任分工&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>元件&lt;/th>
 &lt;th>訊號類型&lt;/th>
 &lt;th>查詢語言&lt;/th>
 &lt;th>儲存後端&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Loki&lt;/td>
 &lt;td>Log&lt;/td>
 &lt;td>LogQL&lt;/td>
 &lt;td>Object storage + BoltDB&lt;/td>
 &lt;td>Log aggregation、grep 替代品&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mimir&lt;/td>
 &lt;td>Metric&lt;/td>
 &lt;td>PromQL&lt;/td>
 &lt;td>Object storage&lt;/td>
 &lt;td>Prometheus 的可擴展長期儲存&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tempo&lt;/td>
 &lt;td>Trace&lt;/td>
 &lt;td>TraceQL&lt;/td>
 &lt;td>Object storage&lt;/td>
 &lt;td>Trace 儲存、span 搜尋&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Grafana&lt;/td>
 &lt;td>視覺化&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>Dashboard、alert、data source&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Grafana 是查詢 / 視覺化層，Loki / Mimir / Tempo 是儲存 / 查詢層。Grafana 本身不存觀測資料，它連接 data source（Loki / Mimir / Tempo / Prometheus / Elasticsearch）做查詢跟渲染。&lt;/p>
&lt;p>四個元件獨立部署、獨立擴展、各自有健康指標。一個元件故障不影響其他元件 — Loki 掛了時 Grafana 的 metric dashboard 跟 trace 查詢仍然正常，只有 log panel 會報錯。&lt;/p>
&lt;h2 id="部署模式">部署模式&lt;/h2>
&lt;h3 id="monolithic-mode">Monolithic mode&lt;/h3>
&lt;p>四個元件（或其中幾個）跑在同一個 process / container。適合小規模（每天數 GB log、數十萬 metric series、少量 trace）。部署最簡單 — 一個 docker-compose 或 Helm chart 起全套。&lt;/p>
&lt;p>限制是沒辦法獨立擴展 — log 量大但 metric 量小時，monolithic mode 不能只加 Loki 的資源。&lt;/p>
&lt;h3 id="microservices-mode">Microservices mode&lt;/h3>
&lt;p>每個元件拆成獨立的 deployment、各自 autoscaling。Loki 拆成 distributor / ingester / querier / compactor；Mimir 拆成類似的元件；Tempo 也有對應的分層。&lt;/p>
&lt;p>適合中到大規模。部署跟維運複雜度顯著上升 — 每個元件的每個子服務都需要獨立的 health check、autoscaling 設定、persistent volume。&lt;/p>
&lt;h3 id="選擇判準">選擇判準&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>團隊 &amp;lt; 5 人、日 log &amp;lt; 10 GB&lt;/td>
 &lt;td>Monolithic&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要獨立擴展某一類訊號&lt;/td>
 &lt;td>Microservices&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不想自管、預算足夠&lt;/td>
 &lt;td>Grafana Cloud&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>已有 Prometheus、只需要加 log / trace&lt;/td>
 &lt;td>漸進式加 Loki + Tempo&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見故障模式">常見故障模式&lt;/h2>
&lt;h3 id="lokiingester-oom">Loki：ingester OOM&lt;/h3>
&lt;p>Loki ingester 把 log chunks 保存在記憶體，高流量時容易 OOM。觸發條件是突然的 log 量爆增（部署後 error storm、某服務開了 debug log level）。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack</a> 的 vendor deep article，深化 overview 的元件組合段。初次接觸 Grafana Stack 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁</a>。</p></blockquote>
<h2 id="定位">定位</h2>
<p>Grafana Stack（LGTM = Loki + Grafana + Tempo + Mimir）是自架觀測平台的完整選項，四個元件各自承擔一類訊號的儲存跟查詢。理解每個元件的責任邊界、部署模式跟故障特性，才能避免「裝了四個元件但不知道哪個壞了」的黑盒問題。</p>
<h2 id="四元件的責任分工">四元件的責任分工</h2>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>訊號類型</th>
          <th>查詢語言</th>
          <th>儲存後端</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Loki</td>
          <td>Log</td>
          <td>LogQL</td>
          <td>Object storage + BoltDB</td>
          <td>Log aggregation、grep 替代品</td>
      </tr>
      <tr>
          <td>Mimir</td>
          <td>Metric</td>
          <td>PromQL</td>
          <td>Object storage</td>
          <td>Prometheus 的可擴展長期儲存</td>
      </tr>
      <tr>
          <td>Tempo</td>
          <td>Trace</td>
          <td>TraceQL</td>
          <td>Object storage</td>
          <td>Trace 儲存、span 搜尋</td>
      </tr>
      <tr>
          <td>Grafana</td>
          <td>視覺化</td>
          <td>—</td>
          <td>—</td>
          <td>Dashboard、alert、data source</td>
      </tr>
  </tbody>
</table>
<p>Grafana 是查詢 / 視覺化層，Loki / Mimir / Tempo 是儲存 / 查詢層。Grafana 本身不存觀測資料，它連接 data source（Loki / Mimir / Tempo / Prometheus / Elasticsearch）做查詢跟渲染。</p>
<p>四個元件獨立部署、獨立擴展、各自有健康指標。一個元件故障不影響其他元件 — Loki 掛了時 Grafana 的 metric dashboard 跟 trace 查詢仍然正常，只有 log panel 會報錯。</p>
<h2 id="部署模式">部署模式</h2>
<h3 id="monolithic-mode">Monolithic mode</h3>
<p>四個元件（或其中幾個）跑在同一個 process / container。適合小規模（每天數 GB log、數十萬 metric series、少量 trace）。部署最簡單 — 一個 docker-compose 或 Helm chart 起全套。</p>
<p>限制是沒辦法獨立擴展 — log 量大但 metric 量小時，monolithic mode 不能只加 Loki 的資源。</p>
<h3 id="microservices-mode">Microservices mode</h3>
<p>每個元件拆成獨立的 deployment、各自 autoscaling。Loki 拆成 distributor / ingester / querier / compactor；Mimir 拆成類似的元件；Tempo 也有對應的分層。</p>
<p>適合中到大規模。部署跟維運複雜度顯著上升 — 每個元件的每個子服務都需要獨立的 health check、autoscaling 設定、persistent volume。</p>
<h3 id="選擇判準">選擇判準</h3>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>建議模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>團隊 &lt; 5 人、日 log &lt; 10 GB</td>
          <td>Monolithic</td>
      </tr>
      <tr>
          <td>需要獨立擴展某一類訊號</td>
          <td>Microservices</td>
      </tr>
      <tr>
          <td>不想自管、預算足夠</td>
          <td>Grafana Cloud</td>
      </tr>
      <tr>
          <td>已有 Prometheus、只需要加 log / trace</td>
          <td>漸進式加 Loki + Tempo</td>
      </tr>
  </tbody>
</table>
<h2 id="常見故障模式">常見故障模式</h2>
<h3 id="lokiingester-oom">Loki：ingester OOM</h3>
<p>Loki ingester 把 log chunks 保存在記憶體，高流量時容易 OOM。觸發條件是突然的 log 量爆增（部署後 error storm、某服務開了 debug log level）。</p>
<p>判讀指標：<code>loki_ingester_memory_chunks</code>、<code>process_resident_memory_bytes</code>。修復方向：調整 chunk flush interval（更頻繁寫入 object storage、降低記憶體壓力）、加 ingester replica、或在 pipeline 層（OTel Collector）做 log volume rate limit。</p>
<h3 id="mimircompactor-卡住">Mimir：compactor 卡住</h3>
<p>Mimir compactor 負責合併 ingester 寫入的 block。Compactor 卡住時，block 數量持續增長、query 需要掃描更多 block、延遲上升。</p>
<p>判讀指標：<code>cortex_compactor_runs_completed_total</code> 停滯、<code>cortex_bucket_blocks_count</code> 持續增長。修復方向：檢查 object storage 的寫入權限跟延遲、增加 compactor 資源（CPU / memory）、或暫時停止 ingestion 讓 compactor 追上。</p>
<h3 id="tempotrace-not-found">Tempo：trace not found</h3>
<p>使用者用 trace ID 查詢時回 &ldquo;trace not found&rdquo;，但 trace 確實存在。常見原因是 Tempo 的 bloom filter / compacted block index 還沒包含該 trace（ingestion 到可查詢有延遲），或 trace 被 retention policy 刪除。</p>
<p>判讀方式：查 trace 的 timestamp 是否在 retention 範圍內、查 <code>tempo_ingester_traces_created_total</code> 確認 ingestion 正常、查 compactor 是否正常運行。</p>
<h3 id="grafanadashboard-provisioning-漂移">Grafana：dashboard provisioning 漂移</h3>
<p>用 provisioning（YAML / JSON 檔案）管理 dashboard 時，手動在 UI 修改的 dashboard 會在下次 provisioning 同步時被覆蓋。團隊成員在 UI 調整了 panel、下次重啟 Grafana 後修改消失。</p>
<p>修復方向：dashboard 修改統一透過 git → provisioning pipeline（GitOps），UI 只用於臨時調整跟探索。把 provisioning 的 <code>allowUiUpdates</code> 設為 false、強制所有變更走 git。</p>
<h2 id="dashboard-provisioning">Dashboard Provisioning</h2>
<p>Dashboard 的管理方式影響長期維護成本。手動在 UI 建立 dashboard 的起步最快，但隨 dashboard 數量增長會出現版本不一致、無法 rollback、owner 不明的問題。</p>
<h3 id="infrastructure-as-code">Infrastructure as Code</h3>
<p>Dashboard JSON 存在 git repo、透過 provisioning 同步到 Grafana。變更走 PR review、有版本歷史、可以 rollback。</p>
<p>Grafana 的 provisioning 機制讀 YAML config，指定 dashboard JSON 的來源（local file / HTTP / API）。Helm chart 部署時把 dashboard JSON 放在 ConfigMap 或 persistent volume。</p>
<h3 id="grafonnet--jsonnet">Grafonnet / Jsonnet</h3>
<p>用 Jsonnet（Grafana 的 dashboard-as-code library）產生 dashboard JSON。適合大量相似 dashboard 的場景 — 每個服務一個 dashboard，結構相同但 data source 跟 label 不同。</p>
<p>Grafonnet 的學習曲線比直接寫 JSON 高，但在 dashboard 數量 &gt; 20 個時開始有維護效率的回報。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁</a>：overview 跟日常操作</li>
<li><a href="/blog/backend/04-observability/vendors/prometheus/" data-link-title="Prometheus" data-link-desc="Pull-based metrics 主流 OSS、PromQL 與 alerting">Prometheus 服務頁</a>：Mimir 的上游 metric 來源</li>
<li><a href="/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/" data-link-title="OTel Collector 部署模式：agent / gateway / sidecar 與 pipeline 設計" data-link-desc="說明 OpenTelemetry Collector 三種部署位置的責任分工、receivers/processors/exporters pipeline 設計，以及 collector 失效、記憶體壓力與 backpressure 的故障演練與容量邊界">OTel Collector 部署模式</a>：LGTM 的 ingestion 入口</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a>：pipeline 各層的治理</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：dashboard / alert 的 ownership</li>
</ul>
]]></content:encoded></item><item><title>Grafana Loki 設計與操作限制</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/loki-design-operational-limits/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/loki-design-operational-limits/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack&lt;/a> 的 vendor deep article，深化 overview「Loki 設計與限制」段。初次接觸 Grafana Stack 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>團隊從 ELK stack 或 CloudWatch Logs 遷到 Grafana Stack 時，Loki 是 log backend 的預設選擇。遷移後最常遇到的衝擊是查詢模式的根本差異：Elasticsearch 做 full-text index（寫入時索引每個欄位、查詢時任意搜尋），Loki 只 index labels（寫入時只索引 stream labels、查詢時先篩 stream 再 grep content）。&lt;/p>
&lt;p>這個差異是刻意的設計選擇 — Loki 的目標是「Prometheus for logs」：用跟 Prometheus metrics 相同的 label 體系管理 logs，讓 log 查詢跟 metric 查詢使用同一組 label selector。代價是失去 full-text search 的即時性。理解這個設計哲學才能正確設計 label、寫出有效率的 LogQL、避免常見的效能陷阱。&lt;/p>
&lt;h2 id="核心概念">核心概念&lt;/h2>
&lt;h3 id="like-prometheus-but-for-logs">Like Prometheus, but for logs&lt;/h3>
&lt;p>Prometheus 用 label set 識別 time series — &lt;code>{job=&amp;quot;checkout&amp;quot;, instance=&amp;quot;10.0.1.5&amp;quot;}&lt;/code> 是一條 series。Loki 用相同概念識別 log stream — &lt;code>{job=&amp;quot;checkout&amp;quot;, namespace=&amp;quot;production&amp;quot;}&lt;/code> 是一條 stream。同一條 stream 的所有 log entries 存在同一組 chunks。&lt;/p>
&lt;p>Elasticsearch 的索引模式是「寫入時建 inverted index、查詢時走索引」。Loki 的索引模式是「寫入時只記錄 stream label → chunk 的 mapping、查詢時先用 label 選 stream、再在 chunk 內做 grep」。&lt;/p>
&lt;p>這代表：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>有 label filter 的查詢很快&lt;/strong> — Loki 只掃對應 stream 的 chunks&lt;/li>
&lt;li>&lt;strong>沒有 label filter 的查詢很慢&lt;/strong> — Loki 要掃所有 stream 的 chunks（相當於 full scan）&lt;/li>
&lt;li>&lt;strong>Label cardinality 跟 Prometheus 一樣敏感&lt;/strong> — 高 cardinality label 產生大量 stream、每個 stream 的 chunk 很小、index 膨脹&lt;/li>
&lt;/ul>
&lt;h3 id="stream-與-chunk">Stream 與 chunk&lt;/h3>
&lt;p>一條 stream = 一組唯一的 label set。每條 stream 的 log entries 依時間排序存在 chunks 裡。Chunk 是 Loki 的最小儲存單位。&lt;/p>





&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">Stream: {job=&amp;#34;checkout&amp;#34;, namespace=&amp;#34;production&amp;#34;}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> └─ Chunk 1: [2026-06-22T00:00 ~ 2026-06-22T01:00] (compressed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └─ Chunk 2: [2026-06-22T01:00 ~ 2026-06-22T02:00] (compressed)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> └─ ...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Chunk 存在 object storage（S3 / GCS / MinIO），index 存在 key-value store（BoltDB / TSDB，3.0 起預設 TSDB）。Object storage 便宜（相比 Elasticsearch 的 SSD），這是 Loki 成本優勢的來源。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack</a> 的 vendor deep article，深化 overview「Loki 設計與限制」段。初次接觸 Grafana Stack 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>團隊從 ELK stack 或 CloudWatch Logs 遷到 Grafana Stack 時，Loki 是 log backend 的預設選擇。遷移後最常遇到的衝擊是查詢模式的根本差異：Elasticsearch 做 full-text index（寫入時索引每個欄位、查詢時任意搜尋），Loki 只 index labels（寫入時只索引 stream labels、查詢時先篩 stream 再 grep content）。</p>
<p>這個差異是刻意的設計選擇 — Loki 的目標是「Prometheus for logs」：用跟 Prometheus metrics 相同的 label 體系管理 logs，讓 log 查詢跟 metric 查詢使用同一組 label selector。代價是失去 full-text search 的即時性。理解這個設計哲學才能正確設計 label、寫出有效率的 LogQL、避免常見的效能陷阱。</p>
<h2 id="核心概念">核心概念</h2>
<h3 id="like-prometheus-but-for-logs">Like Prometheus, but for logs</h3>
<p>Prometheus 用 label set 識別 time series — <code>{job=&quot;checkout&quot;, instance=&quot;10.0.1.5&quot;}</code> 是一條 series。Loki 用相同概念識別 log stream — <code>{job=&quot;checkout&quot;, namespace=&quot;production&quot;}</code> 是一條 stream。同一條 stream 的所有 log entries 存在同一組 chunks。</p>
<p>Elasticsearch 的索引模式是「寫入時建 inverted index、查詢時走索引」。Loki 的索引模式是「寫入時只記錄 stream label → chunk 的 mapping、查詢時先用 label 選 stream、再在 chunk 內做 grep」。</p>
<p>這代表：</p>
<ul>
<li><strong>有 label filter 的查詢很快</strong> — Loki 只掃對應 stream 的 chunks</li>
<li><strong>沒有 label filter 的查詢很慢</strong> — Loki 要掃所有 stream 的 chunks（相當於 full scan）</li>
<li><strong>Label cardinality 跟 Prometheus 一樣敏感</strong> — 高 cardinality label 產生大量 stream、每個 stream 的 chunk 很小、index 膨脹</li>
</ul>
<h3 id="stream-與-chunk">Stream 與 chunk</h3>
<p>一條 stream = 一組唯一的 label set。每條 stream 的 log entries 依時間排序存在 chunks 裡。Chunk 是 Loki 的最小儲存單位。</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">Stream: {job=&#34;checkout&#34;, namespace=&#34;production&#34;}
</span></span><span class="line"><span class="ln">2</span><span class="cl">  └─ Chunk 1: [2026-06-22T00:00 ~ 2026-06-22T01:00] (compressed)
</span></span><span class="line"><span class="ln">3</span><span class="cl">  └─ Chunk 2: [2026-06-22T01:00 ~ 2026-06-22T02:00] (compressed)
</span></span><span class="line"><span class="ln">4</span><span class="cl">  └─ ...</span></span></code></pre></div><p>Chunk 存在 object storage（S3 / GCS / MinIO），index 存在 key-value store（BoltDB / TSDB，3.0 起預設 TSDB）。Object storage 便宜（相比 Elasticsearch 的 SSD），這是 Loki 成本優勢的來源。</p>
<h3 id="跟-elasticsearch-的根本差異">跟 Elasticsearch 的根本差異</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Loki</th>
          <th>Elasticsearch</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>索引對象</td>
          <td>只索引 labels（stream metadata）</td>
          <td>索引所有欄位（full-text + structured）</td>
      </tr>
      <tr>
          <td>查詢模式</td>
          <td>Label selector → stream → grep content</td>
          <td>Query DSL / KQL → inverted index lookup</td>
      </tr>
      <tr>
          <td>寫入成本</td>
          <td>低（不建 content index）</td>
          <td>高（建 inverted index + doc values）</td>
      </tr>
      <tr>
          <td>查詢成本</td>
          <td>取決於 stream 篩選效率（label 越精準越快）</td>
          <td>取決於 index 覆蓋度（indexed field 查詢快）</td>
      </tr>
      <tr>
          <td>儲存成本</td>
          <td>低（object storage）</td>
          <td>高（SSD / local disk）</td>
      </tr>
      <tr>
          <td>Full-text search</td>
          <td>不支援（只有 line filter grep）</td>
          <td>原生支援</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>已有 Prometheus/Grafana 生態的 log aggregation</td>
          <td>需要 full-text search 的 log analytics / SIEM</td>
      </tr>
  </tbody>
</table>
<p>判讀：如果團隊的 log 查詢模式是「先選 service/namespace/pod、再看時間範圍內的 log entries」，Loki 足夠。如果查詢模式是「在所有 log 裡搜某個 error message 或 request ID」，Elasticsearch 的 full-text index 更適合。</p>
<h2 id="配置-step-by-step">配置 step-by-step</h2>
<h3 id="label-設計原則">Label 設計原則</h3>
<p>Label 設計是 Loki 最重要的操作決策。原則跟 Prometheus 相同：低 cardinality、穩定、有查詢意義。</p>
<table>
  <thead>
      <tr>
          <th>Label</th>
          <th>Cardinality</th>
          <th>適合當 label</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>job</code></td>
          <td>低（服務數量）</td>
          <td>適合</td>
          <td>篩選到特定服務</td>
      </tr>
      <tr>
          <td><code>namespace</code></td>
          <td>低</td>
          <td>適合</td>
          <td>篩選到特定環境</td>
      </tr>
      <tr>
          <td><code>pod_name</code></td>
          <td>中（pod 數量）</td>
          <td>視情境</td>
          <td>K8s 環境常用但 pod 頻繁重建會產生大量短命 stream</td>
      </tr>
      <tr>
          <td><code>level</code>（info/warn/error）</td>
          <td>低（3-5 值）</td>
          <td>適合</td>
          <td>快速篩選 error log</td>
      </tr>
      <tr>
          <td><code>request_id</code></td>
          <td>極高（per-request）</td>
          <td>不適合</td>
          <td>每個 request 一條 stream、chunk 極小、index 爆炸</td>
      </tr>
      <tr>
          <td><code>user_id</code></td>
          <td>高</td>
          <td>不適合</td>
          <td>同上</td>
      </tr>
      <tr>
          <td><code>trace_id</code></td>
          <td>極高</td>
          <td>不適合</td>
          <td>用 Tempo 查 trace、不用 Loki label</td>
      </tr>
  </tbody>
</table>
<p>request_id / user_id / trace_id 不應該是 label，它們應該在 log content 裡用 structured JSON 欄位表達，查詢時用 LogQL 的 line filter 或 parser 提取。</p>
<h3 id="logql-常見查詢模式">LogQL 常見查詢模式</h3>
<p><strong>Stream selector + line filter</strong>（最基本）：</p>





<pre tabindex="0"><code class="language-logql" data-lang="logql">{job=&#34;checkout&#34;, namespace=&#34;production&#34;} |= &#34;error&#34; |= &#34;timeout&#34;</code></pre><p>先選 stream、再 grep 包含 &ldquo;error&rdquo; 和 &ldquo;timeout&rdquo; 的 log lines。<code>|=</code> 是包含、<code>!=</code> 是不包含、<code>|~</code> 是 regex。</p>
<p><strong>Structured metadata parser</strong>（JSON log）：</p>





<pre tabindex="0"><code class="language-logql" data-lang="logql">{job=&#34;checkout&#34;} | json | status_code &gt;= 500 | line_format &#34;{{.method}} {{.path}} {{.status_code}}&#34;</code></pre><p><code>| json</code> 解析 JSON log entry 的欄位，後續可以用欄位做 filter 和格式化。</p>
<p><strong>Metric 聚合</strong>（log → metric）：</p>





<pre tabindex="0"><code class="language-logql" data-lang="logql">sum by (status_code) (rate({job=&#34;checkout&#34;} | json | __error__=&#34;&#34; [5m]))</code></pre><p>計算每 5 分鐘每個 status_code 的 log entry 速率。這是 Loki 的「metric from logs」能力 — 不需要額外的 metrics pipeline，直接從 log 產生 time series。</p>
<h3 id="loki-config-核心段">Loki config 核心段</h3>





<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="c"># loki-config.yaml</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">schema_config</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">configs</span><span class="p">:</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">from</span><span class="p">:</span><span class="w"> </span><span class="ld">2024-01-01</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">      </span><span class="nt">store</span><span class="p">:</span><span class="w"> </span><span class="l">tsdb</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">      </span><span class="nt">object_store</span><span class="p">:</span><span class="w"> </span><span class="l">s3</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">schema</span><span class="p">:</span><span class="w"> </span><span class="l">v13</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">index</span><span class="p">:</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">prefix</span><span class="p">:</span><span class="w"> </span><span class="l">loki_index_</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">period</span><span class="p">:</span><span class="w"> </span><span class="l">24h</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="nt">storage_config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">tsdb_shipper</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">active_index_directory</span><span class="p">:</span><span class="w"> </span><span class="l">/loki/index</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="nt">cache_location</span><span class="p">:</span><span class="w"> </span><span class="l">/loki/cache</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">  </span><span class="nt">aws</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="nt">s3</span><span class="p">:</span><span class="w"> </span><span class="l">s3://loki-chunks-bucket</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="nt">region</span><span class="p">:</span><span class="w"> </span><span class="l">us-east-1</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w"></span><span class="nt">limits_config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">  </span><span class="nt">ingestion_rate_mb</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">  </span><span class="nt">ingestion_burst_size_mb</span><span class="p">:</span><span class="w"> </span><span class="m">20</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">  </span><span class="nt">max_streams_per_user</span><span class="p">:</span><span class="w"> </span><span class="m">10000</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">  </span><span class="nt">max_label_name_length</span><span class="p">:</span><span class="w"> </span><span class="m">1024</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">  </span><span class="nt">max_label_value_length</span><span class="p">:</span><span class="w"> </span><span class="m">2048</span></span></span></code></pre></div><p><code>limits_config</code> 是防護網。<code>max_streams_per_user</code> 限制每個 tenant 的 stream 數量，超過時新 stream 的 log 被拒（HTTP 429）。這是 label cardinality 爆炸的最後防線。</p>
<h2 id="故障與邊界">故障與邊界</h2>
<h3 id="label-cardinality-爆炸">Label cardinality 爆炸</h3>
<p><strong>觸發條件</strong>：label 包含高 cardinality 值（pod UID、request ID、container ID）。每個唯一 label set 產生一條 stream，stream 數量快速增長。</p>
<p><strong>表現</strong>：<code>loki_ingester_memory_streams</code> 持續上升、ingester memory 增長、最終觸發 <code>max_streams_per_user</code> 限制（429 error）。跟 Prometheus series explosion 是同一個問題的 log 版本。</p>
<p><strong>修法</strong>：檢查產出大量 stream 的 label。Loki 的 <code>/loki/api/v1/labels</code> 和 <code>/loki/api/v1/label/{name}/values</code> API 可以列出所有 label 值。找到高 cardinality label 後，從 promtail / alloy 的 pipeline 中移除該 label、改放進 log content 的 structured field。</p>
<h3 id="stream-rate-limit">Stream rate limit</h3>
<p><strong>觸發條件</strong>：單一 stream 的 ingestion rate 超過 <code>per_stream_rate_limit</code>（預設 3 MB/s）。通常是某個 service 大量噴 debug log。</p>
<p><strong>表現</strong>：Loki 回傳 429 + <code>rate limit exceeded</code> error。部分 log entries 被丟棄。</p>
<p><strong>修法</strong>：先解決 log 噴量問題（降低 debug log level 或加 sampling）。如果噴量合理（高 QPS 服務），調高 <code>per_stream_rate_limit</code> 或拆分 stream（加一層 label 分散流量）。</p>
<h3 id="大時間範圍查詢-timeout">大時間範圍查詢 timeout</h3>
<p><strong>觸發條件</strong>：LogQL 查詢沒有精確的 label filter、時間範圍 &gt; 24 小時。Loki 要掃描大量 chunks、query timeout（預設 3 分鐘）觸發。</p>
<p><strong>表現</strong>：Grafana 顯示 query timeout error。</p>
<p><strong>修法</strong>：查詢時先用 label selector 縮小 stream 範圍（<code>{job=&quot;checkout&quot;, namespace=&quot;production&quot;}</code> 而非 <code>{namespace=&quot;production&quot;}</code>），再用 line filter 進一步篩。如果業務需要長時間範圍的 log analytics，考慮用 LogQL 的 metric aggregation（<code>rate(...)</code> / <code>count_over_time(...)</code>）替代原始 log 掃描。</p>
<h3 id="chunk-target-size-與-ingestion-rate-的關係">Chunk target size 與 ingestion rate 的關係</h3>
<p><code>chunk_target_size</code>（預設 1.5 MB）控制 chunk 的大小。ingestion rate 低的 stream 可能幾個小時才填滿一個 chunk — 這段期間 chunk 停在 ingester memory 裡。大量低 ingestion rate 的 stream（= 高 cardinality label）會讓 ingester 同時持有大量未 flush 的 chunks，佔用記憶體。</p>
<p>修法方向：降低 <code>chunk_idle_period</code>（預設 30 分鐘，時間到即使 chunk 未滿也 flush），或減少低 cardinality stream 的數量。</p>
<h2 id="容量與成本">容量與成本</h2>
<p>Loki 的成本結構跟 Elasticsearch 根本不同：</p>
<table>
  <thead>
      <tr>
          <th>成本項</th>
          <th>Loki</th>
          <th>Elasticsearch</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>儲存</td>
          <td>Object storage（S3/GCS）— 便宜</td>
          <td>SSD / local disk — 貴</td>
      </tr>
      <tr>
          <td>Index</td>
          <td>小（只索引 labels）</td>
          <td>大（inverted index + doc values）</td>
      </tr>
      <tr>
          <td>查詢 compute</td>
          <td>每次查詢 grep chunks — CPU 密集</td>
          <td>走 index — 相對輕</td>
      </tr>
      <tr>
          <td>適合的 workload</td>
          <td>高 volume、低 query frequency</td>
          <td>高 query frequency、需要 full-text</td>
      </tr>
  </tbody>
</table>
<p>Loki 在「每天寫 TB 級 log、偶爾查一下」的場景成本遠低於 Elasticsearch。但在「每天查數百次、需要快速 full-text search」的場景，Elasticsearch 的 pre-indexed 查詢效能更好，Loki 每次 grep 的 compute cost 反而更高。</p>
<p>成本治理的判讀：監控 <code>loki_ingester_bytes_received_total</code>（ingestion volume）和 <code>loki_querier_query_duration_seconds</code>（query cost）。如果 query duration 持續上升，先檢查是 label filter 不夠精確還是 query 時間範圍太大。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li><a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack 服務頁</a>：overview 與全棧操作</li>
<li><a href="../lgtm-stack-operations/">LGTM Stack Operations</a>：Loki 在 LGTM 全棧中的部署位置</li>
<li><a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a>：Loki 不適合 audit log 的 compliance 查詢（無 immutable storage 保證、無 fine-grained access control）— 合規需求用 BigQuery 或 dedicated audit backend</li>
<li><a href="/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/" data-link-title="Healthcare：存取可追溯性與保留邊界" data-link-desc="在資料主權限制下，建立可追溯存取證據與分層保留策略。">Healthcare 存取追溯案例</a>：分層 retention 在 Loki 用 tenant-level retention policy 實現</li>
<li><a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 Log Schema</a>：log 欄位設計影響 Loki 的 label 設計與 parser 效率</li>
<li><a href="/blog/backend/04-observability/vendors/elastic-stack/ilm-log-pipeline/" data-link-title="Index Lifecycle Management 與 Log Pipeline" data-link-desc="說明 Elasticsearch ILM policy 設計、data stream / rollover、Beats vs Elastic Agent 採集選擇、ingest pipeline 與 shard sizing、cross-cluster 策略與 cost governance">Elasticsearch ILM 與 Log Pipeline</a>：需要 full-text search 時的替代方案</li>
</ul>
]]></content:encoded></item><item><title>Self-managed Prometheus → Grafana Cloud Metrics：feature × ops × cost 對照</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/migrate-prometheus-to-cloud-metrics/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/migrate-prometheus-to-cloud-metrics/</guid><description>&lt;blockquote>
&lt;p>本文是跨 vendor migration playbook、cross-link &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/prometheus/" data-link-title="Prometheus" data-link-desc="Pull-based metrics 主流 OSS、PromQL 與 alerting">Prometheus&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack&lt;/a>（Grafana Cloud Metrics、Mimir-backed）。跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit&lt;/a> 後對映 &lt;em>Operational = High → Type C operational redesign hybrid&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;h2 id="feature--ops--cost-三維對照">Feature / ops / cost 三維對照&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Self-managed Prometheus&lt;/th>
 &lt;th>Grafana Cloud Metrics&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Storage backend&lt;/td>
 &lt;td>Local disk + remote_write (optional)&lt;/td>
 &lt;td>Mimir + S3 (auto cold tier)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Retention&lt;/td>
 &lt;td>TSDB local 15 天 default&lt;/td>
 &lt;td>13 個月 default、可延長&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HA&lt;/td>
 &lt;td>Two Prometheus + sidecar&lt;/td>
 &lt;td>Built-in multi-AZ&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cardinality limit&lt;/td>
 &lt;td>自管 limit + recording rule&lt;/td>
 &lt;td>1.5M active series / tier、scale-up 配額&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query API&lt;/td>
 &lt;td>PromQL + Prometheus HTTP API&lt;/td>
 &lt;td>完全相容&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alert&lt;/td>
 &lt;td>Alertmanager self-managed&lt;/td>
 &lt;td>Grafana Cloud Alerting&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dashboard&lt;/td>
 &lt;td>Grafana self-managed&lt;/td>
 &lt;td>Grafana Cloud (included)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Long-term storage&lt;/td>
 &lt;td>Thanos / Cortex / Mimir 自管&lt;/td>
 &lt;td>Mimir 內建&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost (mid-tier)&lt;/td>
 &lt;td>$500-2000 / mo + ops FTE&lt;/td>
 &lt;td>$300-1500 / mo (按 series)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operational FTE&lt;/td>
 &lt;td>0.3-0.8&lt;/td>
 &lt;td>0.05-0.15&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit&lt;/a>：&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>Schema / API&lt;/td>
 &lt;td>Low（PromQL + API 完全相容）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operational&lt;/td>
 &lt;td>&lt;strong>High&lt;/strong>（HA / retention / scaling 全託管）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Paradigm&lt;/td>
 &lt;td>Low（同 Prometheus metric paradigm）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Components&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Application change&lt;/td>
 &lt;td>Low（remote_write endpoint 改）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Data topology&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Operational = High → Type C standard。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是跨 vendor migration playbook、cross-link <a href="/blog/backend/04-observability/vendors/prometheus/" data-link-title="Prometheus" data-link-desc="Pull-based metrics 主流 OSS、PromQL 與 alerting">Prometheus</a> 跟 <a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack</a>（Grafana Cloud Metrics、Mimir-backed）。跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit</a> 後對映 <em>Operational = High → Type C operational redesign hybrid</em>。</p></blockquote>
<h2 id="feature--ops--cost-三維對照">Feature / ops / cost 三維對照</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Self-managed Prometheus</th>
          <th>Grafana Cloud Metrics</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Storage backend</td>
          <td>Local disk + remote_write (optional)</td>
          <td>Mimir + S3 (auto cold tier)</td>
      </tr>
      <tr>
          <td>Retention</td>
          <td>TSDB local 15 天 default</td>
          <td>13 個月 default、可延長</td>
      </tr>
      <tr>
          <td>HA</td>
          <td>Two Prometheus + sidecar</td>
          <td>Built-in multi-AZ</td>
      </tr>
      <tr>
          <td>Cardinality limit</td>
          <td>自管 limit + recording rule</td>
          <td>1.5M active series / tier、scale-up 配額</td>
      </tr>
      <tr>
          <td>Query API</td>
          <td>PromQL + Prometheus HTTP API</td>
          <td>完全相容</td>
      </tr>
      <tr>
          <td>Alert</td>
          <td>Alertmanager self-managed</td>
          <td>Grafana Cloud Alerting</td>
      </tr>
      <tr>
          <td>Dashboard</td>
          <td>Grafana self-managed</td>
          <td>Grafana Cloud (included)</td>
      </tr>
      <tr>
          <td>Long-term storage</td>
          <td>Thanos / Cortex / Mimir 自管</td>
          <td>Mimir 內建</td>
      </tr>
      <tr>
          <td>Cost (mid-tier)</td>
          <td>$500-2000 / mo + ops FTE</td>
          <td>$300-1500 / mo (按 series)</td>
      </tr>
      <tr>
          <td>Operational FTE</td>
          <td>0.3-0.8</td>
          <td>0.05-0.15</td>
      </tr>
  </tbody>
</table>
<p>跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit</a>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>等級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema / API</td>
          <td>Low（PromQL + API 完全相容）</td>
      </tr>
      <tr>
          <td>Operational</td>
          <td><strong>High</strong>（HA / retention / scaling 全託管）</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td>Low（同 Prometheus metric paradigm）</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Low</td>
      </tr>
      <tr>
          <td>Application change</td>
          <td>Low（remote_write endpoint 改）</td>
      </tr>
      <tr>
          <td>Data topology</td>
          <td>Low</td>
      </tr>
  </tbody>
</table>
<p>Operational = High → Type C standard。</p>
<h2 id="為什麼遷retention--ops--vendor-consolidation-三條-driver">為什麼遷：retention / ops / vendor consolidation 三條 driver</h2>
<table>
  <thead>
      <tr>
          <th>Driver</th>
          <th>觸發</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Retention</td>
          <td>Prometheus TSDB local 預設 15 天、長期 retention 需要 Thanos / Cortex / Mimir 自管</td>
      </tr>
      <tr>
          <td>Ops FTE</td>
          <td>Self-managed Prometheus + Alertmanager + Grafana 自管全部加起來 0.5-1 FTE</td>
      </tr>
      <tr>
          <td>Vendor consolidation</td>
          <td>已用 Grafana Cloud（logs / traces）、metric 加進 stack 統一</td>
      </tr>
  </tbody>
</table>
<h2 id="operational-redesign">Operational redesign</h2>
<table>
  <thead>
      <tr>
          <th>Concept</th>
          <th>Self-managed</th>
          <th>Grafana Cloud Metrics</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cluster bootstrap</td>
          <td>Helm chart + manual config</td>
          <td>UI 一鍵建</td>
      </tr>
      <tr>
          <td>HA</td>
          <td>Two Prometheus 配置</td>
          <td>內建 multi-AZ Mimir</td>
      </tr>
      <tr>
          <td>Long-term retention</td>
          <td>Thanos / Cortex / Mimir 自管</td>
          <td>Built-in (S3-backed)</td>
      </tr>
      <tr>
          <td>Cardinality control</td>
          <td>Manual recording rule + relabel</td>
          <td>Adaptive sampling + cardinality limit</td>
      </tr>
      <tr>
          <td>Alerting</td>
          <td>Alertmanager 自管</td>
          <td>Grafana Cloud Alerting (integrated)</td>
      </tr>
      <tr>
          <td>Dashboard</td>
          <td>Grafana self-host</td>
          <td>Grafana Cloud (free tier 包含)</td>
      </tr>
  </tbody>
</table>
<h2 id="migration-4-phase">Migration 4-phase</h2>
<h3 id="phase-0audit">Phase 0：Audit</h3>
<ul>
<li>列所有 Prometheus job / scrape config</li>
<li>統計 active series 數（Mimir tier 計費基準）</li>
<li>估 retention 需求</li>
</ul>
<h3 id="phase-1grafana-cloud-setup">Phase 1：Grafana Cloud setup</h3>
<ul>
<li>Account + organization 設定</li>
<li>API key for <code>remote_write</code></li>
<li>Grafana Cloud Mimir endpoint 啟用</li>
</ul>
<h3 id="phase-2dual-write">Phase 2：Dual-write</h3>





<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="c"># prometheus.yml</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">remote_write</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">url</span><span class="p">:</span><span class="w"> </span><span class="l">https://prometheus-prod-XX-prod-us-central-0.grafana.net/api/prom/push</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">basic_auth</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">      </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;INSTANCE_ID&gt;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">      </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;API_KEY&gt;</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">write_relabel_configs</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="c"># Optional: drop high-cardinality before sending</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">source_labels</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">__name__]</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">regex</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;high_card_metric_.*&#39;</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">action</span><span class="p">:</span><span class="w"> </span><span class="l">drop</span></span></span></code></pre></div><p>跑 4-8 週、確認 query 結果一致 + cost 在預期。</p>
<h3 id="phase-3cutover">Phase 3：Cutover</h3>
<ul>
<li>Dashboard / alert 切到 Grafana Cloud endpoint</li>
<li>應用層 / Grafana 自管 instance 關閉 query 對 self-managed Prometheus</li>
</ul>
<h3 id="phase-4cleanup">Phase 4：Cleanup</h3>
<ul>
<li>Self-managed Prometheus stop scrape</li>
<li>留 1-2 月歷史查詢能力（用 archive snapshot）</li>
<li>Decommission</li>
</ul>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1cardinality-爆cost-暴漲">Case 1：Cardinality 爆、cost 暴漲</h3>
<p><strong>徵兆</strong>：dual-write 第 2 週 Grafana Cloud series 從預估 100K 漲到 800K、cost 翻 8 倍。</p>
<p><strong>根因</strong>：application-level high-cardinality label（user_id / request_id）沒被 drop、scraped 進來。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><code>write_relabel_configs</code> drop unbounded label</li>
<li>Application metric 設計改 fixed-bucket histogram、不用 unbounded label</li>
<li>Mimir cardinality limit 設保護 + alert</li>
</ol>
<h3 id="case-2recording-rule-對應失效">Case 2：Recording rule 對應失效</h3>
<p><strong>徵兆</strong>：cutover 後 Grafana dashboard 某些 panel 顯示空；發現用了 Prometheus 端 recording rule (<code>job:request_count:rate5m</code>)、Grafana Cloud 端沒對應 rule。</p>
<p><strong>根因</strong>：Prometheus 端 recording rule 是 <em>server-side</em>、不會跟著 remote_write 帶過去；Grafana Cloud 需要自己 setup recording rule。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Export 所有 recording rule、import 到 Grafana Cloud Mimir</li>
<li>或改用 <em>raw query</em> + Grafana query template、不依賴 recording rule</li>
</ol>
<h3 id="case-3promql-微差行為">Case 3：PromQL 微差行為</h3>
<p><strong>徵兆</strong>：某些 query 在 self-managed Prometheus 跑得好好的、切 Grafana Cloud Mimir 後 returns slightly different results。</p>
<p><strong>根因</strong>：Mimir 對某些 edge case（empty result handling / staleness marker timing）行為跟 Prometheus 略不同；多數 query 一致、&lt; 1% query 受影響。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Pre-cutover dual-query 驗證、用 critical dashboard 比對</li>
<li>Affected query 重寫、用更 robust PromQL pattern</li>
<li>文件 known incompatibility list</li>
</ol>
<h3 id="case-4alert-routing-改變">Case 4：Alert routing 改變</h3>
<p><strong>徵兆</strong>：Cutover 後 PagerDuty / Slack 收不到 alert；發現 Alertmanager 端 webhook 沒切。</p>
<p><strong>根因</strong>：alert 邏輯從 self-managed Alertmanager 搬到 Grafana Cloud Alerting、routing / contact 配置完全重做。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Pre-cutover 在 Grafana Cloud 端 rebuild alert + routing</li>
<li>雙 alert pipeline 跑 1-2 週、確認 Grafana Cloud 收到</li>
<li>Cutover 切 routing、SOC drill 一次</li>
</ol>
<h3 id="case-5歷史資料查不到">Case 5：歷史資料查不到</h3>
<p><strong>徵兆</strong>：Cutover 後 SOC 想 query 6 個月前事件、Grafana Cloud 只有 2 個月（dual-write 後的）資料。</p>
<p><strong>根因</strong>：Grafana Cloud 從 dual-write 開始才有資料、之前的 self-managed Prometheus historical data 沒 backfill。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Phase 2 期間用 <code>promtool tsdb dump</code> + <code>mimirtool</code> 把 self-managed historical 灌進 Mimir</li>
<li>或保留 self-managed Prometheus read-only 6 個月（給 historical query）</li>
<li>Long-term：retention 從 cutover 開始算、historical 是 <em>one-time backfill</em></li>
</ol>
<h2 id="capacity--cost">Capacity / cost</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Self-managed</th>
          <th>Grafana Cloud Metrics</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Compute (100 host, 100K series)</td>
          <td>$500-1000 / mo + ops</td>
          <td>$300-800 / mo</td>
      </tr>
      <tr>
          <td>Operational FTE</td>
          <td>0.3-0.8 = $3K-8K</td>
          <td>0.05-0.15 = $500-1500</td>
      </tr>
      <tr>
          <td>Long-term retention</td>
          <td>Thanos / Cortex / Mimir 自管</td>
          <td>Built-in 13 個月</td>
      </tr>
      <tr>
          <td>Total (mid-tier)</td>
          <td>$4K-9K / mo (含 FTE)</td>
          <td>$1K-2.5K / mo</td>
      </tr>
      <tr>
          <td>Migration cost</td>
          <td>-</td>
          <td>1-2 FTE × 1-2 個月</td>
      </tr>
  </tbody>
</table>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-datadog--grafana-stack-migration-對位">跟 <a href="/blog/backend/04-observability/vendors/datadog/migrate-to-grafana-stack/" data-link-title="Datadog → Grafana Stack：把 $50K/month bill 拆解到 self-hosted observability" data-link-desc="Datadog 五層計費（host APM / metric / log ingest / log retention / RUM）拆解、對位 Grafana Stack（Mimir / Loki / Tempo / Grafana / Alloy）的 5 層責任；OTel-based agent migration、5 個 production 踩雷（cardinality 爆 / log volume cost / dashboard 不直接轉 / alert routing 換邏輯 / SLO definition 差異）、cost reality check">Datadog → Grafana Stack migration</a> 對位</h3>
<p>兩條 Grafana Stack 路線：</p>
<ul>
<li>Self-host (Mimir + Loki + Tempo) on K8s：開源、自管</li>
<li>Grafana Cloud：SaaS、operational simplification</li>
</ul>
<p>本篇是「self-managed Prometheus → Grafana Cloud」、互補；如果跑兩階段（self-host → Cloud）跟「Datadog → Grafana Cloud」差不多。</p>
<h3 id="跟-opentelemetry-整合">跟 OpenTelemetry 整合</h3>
<p>OTel Collector 可同時 ship 到 Mimir (metric) + Loki (log) + Tempo (trace)；Migration 順便升 OTel 化避免下次 vendor 切換重複。</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>Source vendor：<a href="/blog/backend/04-observability/vendors/prometheus/" data-link-title="Prometheus" data-link-desc="Pull-based metrics 主流 OSS、PromQL 與 alerting">Prometheus</a></li>
<li>Target vendor：<a href="/blog/backend/04-observability/vendors/grafana-stack/" data-link-title="Grafana Stack" data-link-desc="Grafana / Loki / Tempo / Mimir / Pyroscope 全棧">Grafana Stack</a></li>
<li>平行 migration playbook (Type C)：<a href="/blog/backend/01-database/vendors/postgresql/migrate-to-aurora/" data-link-title="PostgreSQL → Aurora Migration：protocol 相容、operational 重設計" data-link-desc="Aurora 號稱 PostgreSQL-compatible 但 operational model 不同（storage decouple / cluster endpoint / instance class / 自家備份）；遷移流程是混合（protocol drop-in &#43; operational phased）、5 個 production 踩雷（extension 不支援 / replication slot 不直通 / autovacuum 行為差 / IAM 認證強制 / cost model 換算）、跟 Patroni / read replica / DR 對位">PostgreSQL → Aurora</a> / <a href="/blog/backend/03-message-queue/vendors/kafka/migrate-to-msk/" data-link-title="Self-managed Kafka → AWS MSK：把 $15K/month operational cost 拆解到 managed" data-link-desc="Kafka self-managed → MSK 是 Type C operational redesign — protocol 完全相容、operational stack（ZooKeeper / brokers / monitoring / patching）全託管；本文用 cost 拆解開頭、5 個 production 踩雷（client connection pattern / version pinning / metric pipeline / IAM auth / cross-cluster mirror）">Kafka → MSK</a> / <a href="/blog/backend/04-observability/vendors/elastic-stack/migrate-to-elastic-cloud/" data-link-title="Self-managed ELK → Elastic Cloud：5 年 ELK 集群的 lifecycle 收尾" data-link-desc="Self-managed ELK Stack → Elastic Cloud 是 Type C operational redesign — protocol drop-in、operational stack（cluster sizing / shard 治理 / upgrade / backup）全託管；本文按 5 年 ELK lifecycle (build → scale → degrade → save → migrate) 組織、5 個 production 踩雷">ELK → Elastic Cloud</a></li>
<li>平行 D-type 對位：<a href="/blog/backend/04-observability/vendors/datadog/migrate-to-grafana-stack/" data-link-title="Datadog → Grafana Stack：把 $50K/month bill 拆解到 self-hosted observability" data-link-desc="Datadog 五層計費（host APM / metric / log ingest / log retention / RUM）拆解、對位 Grafana Stack（Mimir / Loki / Tempo / Grafana / Alloy）的 5 層責任；OTel-based agent migration、5 個 production 踩雷（cardinality 爆 / log volume cost / dashboard 不直接轉 / alert routing 換邏輯 / SLO definition 差異）、cost reality check">Datadog → Grafana Stack</a></li>
<li>Methodology：<a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration playbook methodology</a></li>
</ul>
]]></content:encoded></item></channel></rss>