<?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>Logs on Tarragon</title><link>https://tarrragon.github.io/blog/tags/logs/</link><description>Recent content in Logs on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 23 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/logs/index.xml" rel="self" type="application/rss+xml"/><item><title>CloudWatch Logs Insights 查詢與日誌治理</title><link>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/logs-insights-governance/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/logs-insights-governance/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch&lt;/a> 的 vendor deep article，深化 overview「Logs Insights query」跟「Logs lifecycle」段。初次接觸 CloudWatch 的讀者建議先讀 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>CloudWatch Logs 的成本模型跟 self-hosted log stack 不同 — ingestion、storage 跟 query 分開計費，每一層都有明確的 cost lever。理解 log group 設計、retention 設定與 subscription filter 的組合，才能在 AWS-native 環境下控制日誌成本而不犧牲事故判讀能力。&lt;/p>
&lt;h2 id="log-group-設計">Log group 設計&lt;/h2>
&lt;h3 id="拆分粒度">拆分粒度&lt;/h3>
&lt;p>Log group 是 CloudWatch Logs 的計費與 retention 邊界。同一個 log group 內的所有 log stream 共用 retention policy 和 access control（IAM resource policy）。&lt;/p>
&lt;p>合理的拆分粒度是 &lt;strong>一個服務一個 log group&lt;/strong>，而非一個帳號一個或一個 container 一個。服務級拆分讓 retention、查詢範圍與 IAM 權限自然對齊服務 ownership。&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>一個服務一個 log group&lt;/td>
 &lt;td>多數 production 服務&lt;/td>
 &lt;td>log group 數量增長需要 naming convention&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一個環境一個 log group&lt;/td>
 &lt;td>非常小的團隊、staging/dev 環境&lt;/td>
 &lt;td>混合多個服務的日誌，查詢時需要額外 filter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一個 Lambda function 一個 log group&lt;/td>
 &lt;td>Lambda 預設行為&lt;/td>
 &lt;td>Lambda 數量多時 log group 爆量，管理成本高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Lambda 的預設行為是每個 function 自動建一個 log group（&lt;code>/aws/lambda/&amp;lt;function-name&amp;gt;&lt;/code>）。function 數量超過數十個後，需要用 naming convention 加 tag 控制，否則 retention policy 難以統一套用。&lt;/p>
&lt;h3 id="naming-convention">Naming convention&lt;/h3>
&lt;p>推薦格式：&lt;code>/&amp;lt;environment&amp;gt;/&amp;lt;service&amp;gt;/&amp;lt;component&amp;gt;&lt;/code>，例如 &lt;code>/prod/checkout-api/app&lt;/code>、&lt;code>/prod/checkout-api/access-log&lt;/code>。統一前綴讓 Logs Insights 的 multi-log-group query 用 prefix matching 篩選。&lt;/p>
&lt;h2 id="logs-insights-查詢語法">Logs Insights 查詢語法&lt;/h2>
&lt;h3 id="核心語法">核心語法&lt;/h3>
&lt;p>Logs Insights 的查詢結構是 pipe-based：每行用 &lt;code>|&lt;/code> 分隔，依序處理。&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">fields @timestamp, @message, @logStream
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">| filter @message like /ERROR/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">| parse @message &amp;#34;order_id=* status=*&amp;#34; as order_id, status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">| stats count(*) as error_count by status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">| sort error_count desc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">| limit 20&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>常用 command 對照：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Command&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;th>注意事項&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>fields&lt;/code>&lt;/td>
 &lt;td>選擇要顯示的欄位&lt;/td>
 &lt;td>&lt;code>@timestamp&lt;/code>、&lt;code>@message&lt;/code> 是內建欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>filter&lt;/code>&lt;/td>
 &lt;td>條件篩選&lt;/td>
 &lt;td>支援 &lt;code>like /regex/&lt;/code>、&lt;code>=&lt;/code>、&lt;code>&amp;gt;&lt;/code>、&lt;code>in []&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>parse&lt;/code>&lt;/td>
 &lt;td>從非結構化 log 擷取欄位&lt;/td>
 &lt;td>glob pattern 用 &lt;code>*&lt;/code>、regex 用 &lt;code>/pattern/&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>stats&lt;/code>&lt;/td>
 &lt;td>聚合計算&lt;/td>
 &lt;td>&lt;code>count&lt;/code>、&lt;code>avg&lt;/code>、&lt;code>sum&lt;/code>、&lt;code>min&lt;/code>、&lt;code>max&lt;/code>、&lt;code>pct&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>sort&lt;/code>&lt;/td>
 &lt;td>排序&lt;/td>
 &lt;td>預設 &lt;code>@timestamp desc&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>display&lt;/code>&lt;/td>
 &lt;td>只顯示指定欄位（跟 &lt;code>fields&lt;/code> 互補）&lt;/td>
 &lt;td>用在 &lt;code>stats&lt;/code> 後只要看聚合結果&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="json-自動解析">JSON 自動解析&lt;/h3>
&lt;p>CloudWatch Logs 會自動辨識 JSON 格式的 log event。JSON 欄位用 dot notation 存取：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">AWS CloudWatch</a> 的 vendor deep article，深化 overview「Logs Insights query」跟「Logs lifecycle」段。初次接觸 CloudWatch 的讀者建議先讀 <a href="/blog/backend/04-observability/vendors/aws-cloudwatch/" data-link-title="AWS CloudWatch" data-link-desc="AWS 原生觀測性服務、Logs / Metrics / Traces (X-Ray)">CloudWatch 服務頁</a>。</p></blockquote>
<h2 id="問題情境">問題情境</h2>
<p>CloudWatch Logs 的成本模型跟 self-hosted log stack 不同 — ingestion、storage 跟 query 分開計費，每一層都有明確的 cost lever。理解 log group 設計、retention 設定與 subscription filter 的組合，才能在 AWS-native 環境下控制日誌成本而不犧牲事故判讀能力。</p>
<h2 id="log-group-設計">Log group 設計</h2>
<h3 id="拆分粒度">拆分粒度</h3>
<p>Log group 是 CloudWatch Logs 的計費與 retention 邊界。同一個 log group 內的所有 log stream 共用 retention policy 和 access control（IAM resource policy）。</p>
<p>合理的拆分粒度是 <strong>一個服務一個 log group</strong>，而非一個帳號一個或一個 container 一個。服務級拆分讓 retention、查詢範圍與 IAM 權限自然對齊服務 ownership。</p>
<table>
  <thead>
      <tr>
          <th>拆分策略</th>
          <th>適合場景</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一個服務一個 log group</td>
          <td>多數 production 服務</td>
          <td>log group 數量增長需要 naming convention</td>
      </tr>
      <tr>
          <td>一個環境一個 log group</td>
          <td>非常小的團隊、staging/dev 環境</td>
          <td>混合多個服務的日誌，查詢時需要額外 filter</td>
      </tr>
      <tr>
          <td>一個 Lambda function 一個 log group</td>
          <td>Lambda 預設行為</td>
          <td>Lambda 數量多時 log group 爆量，管理成本高</td>
      </tr>
  </tbody>
</table>
<p>Lambda 的預設行為是每個 function 自動建一個 log group（<code>/aws/lambda/&lt;function-name&gt;</code>）。function 數量超過數十個後，需要用 naming convention 加 tag 控制，否則 retention policy 難以統一套用。</p>
<h3 id="naming-convention">Naming convention</h3>
<p>推薦格式：<code>/&lt;environment&gt;/&lt;service&gt;/&lt;component&gt;</code>，例如 <code>/prod/checkout-api/app</code>、<code>/prod/checkout-api/access-log</code>。統一前綴讓 Logs Insights 的 multi-log-group query 用 prefix matching 篩選。</p>
<h2 id="logs-insights-查詢語法">Logs Insights 查詢語法</h2>
<h3 id="核心語法">核心語法</h3>
<p>Logs Insights 的查詢結構是 pipe-based：每行用 <code>|</code> 分隔，依序處理。</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">fields @timestamp, @message, @logStream
</span></span><span class="line"><span class="ln">2</span><span class="cl">| filter @message like /ERROR/
</span></span><span class="line"><span class="ln">3</span><span class="cl">| parse @message &#34;order_id=* status=*&#34; as order_id, status
</span></span><span class="line"><span class="ln">4</span><span class="cl">| stats count(*) as error_count by status
</span></span><span class="line"><span class="ln">5</span><span class="cl">| sort error_count desc
</span></span><span class="line"><span class="ln">6</span><span class="cl">| limit 20</span></span></code></pre></div><p>常用 command 對照：</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>用途</th>
          <th>注意事項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>fields</code></td>
          <td>選擇要顯示的欄位</td>
          <td><code>@timestamp</code>、<code>@message</code> 是內建欄位</td>
      </tr>
      <tr>
          <td><code>filter</code></td>
          <td>條件篩選</td>
          <td>支援 <code>like /regex/</code>、<code>=</code>、<code>&gt;</code>、<code>in []</code></td>
      </tr>
      <tr>
          <td><code>parse</code></td>
          <td>從非結構化 log 擷取欄位</td>
          <td>glob pattern 用 <code>*</code>、regex 用 <code>/pattern/</code></td>
      </tr>
      <tr>
          <td><code>stats</code></td>
          <td>聚合計算</td>
          <td><code>count</code>、<code>avg</code>、<code>sum</code>、<code>min</code>、<code>max</code>、<code>pct</code></td>
      </tr>
      <tr>
          <td><code>sort</code></td>
          <td>排序</td>
          <td>預設 <code>@timestamp desc</code></td>
      </tr>
      <tr>
          <td><code>display</code></td>
          <td>只顯示指定欄位（跟 <code>fields</code> 互補）</td>
          <td>用在 <code>stats</code> 後只要看聚合結果</td>
      </tr>
  </tbody>
</table>
<h3 id="json-自動解析">JSON 自動解析</h3>
<p>CloudWatch Logs 會自動辨識 JSON 格式的 log event。JSON 欄位用 dot notation 存取：</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">fields @timestamp, requestId, level, message
</span></span><span class="line"><span class="ln">2</span><span class="cl">| filter level = &#34;ERROR&#34;
</span></span><span class="line"><span class="ln">3</span><span class="cl">| stats count(*) by bin(5m)</span></span></code></pre></div><p>如果 log 是 JSON 格式，<code>parse</code> 通常不需要 — 直接用欄位名稱。混合格式（部分 JSON、部分 plain text）時，需要用 <code>isPresent()</code> 判斷欄位是否存在。</p>
<h3 id="效能考量">效能考量</h3>
<p>Logs Insights 的查詢成本按掃描的 data 量計費（每 GB scanned），不按結果數。減少掃描量的方式：</p>
<ul>
<li>縮短時間範圍：事故判讀先查最近 30 分鐘，確認 pattern 後再擴大</li>
<li>指定 log group：避免對所有 log group 做全域查詢</li>
<li>用 <code>limit</code> 限制結果集大小（不影響掃描量，但減少資料傳輸）</li>
</ul>
<p>跨 log group 查詢最多同時查 50 個 log group。超過時需要拆成多次查詢或用 subscription filter 把資料匯到集中儲存。</p>
<h2 id="retention-policy">Retention policy</h2>
<h3 id="設定方式">設定方式</h3>
<p>Retention policy 在 log group 級別設定。每個 log group 可以獨立選擇 1 天到 10 年、或永不過期。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws logs put-retention-policy <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --log-group-name /prod/checkout-api/app <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --retention-in-days <span class="m">30</span></span></span></code></pre></div><p>常見 retention 策略按服務性質分：</p>
<table>
  <thead>
      <tr>
          <th>服務類型</th>
          <th>建議 retention</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>核心交易路徑（checkout、payment）</td>
          <td>90-365 天</td>
          <td>事故回溯、合規稽核</td>
      </tr>
      <tr>
          <td>一般 API 服務</td>
          <td>30-90 天</td>
          <td>事故回溯足夠，cost 可控</td>
      </tr>
      <tr>
          <td>Background job / worker</td>
          <td>14-30 天</td>
          <td>失敗時看最近數天即可</td>
      </tr>
      <tr>
          <td>Lambda / short-lived function</td>
          <td>7-14 天</td>
          <td>高量低價值，過期快速清理</td>
      </tr>
      <tr>
          <td>Audit log</td>
          <td>365 天以上或永不過期</td>
          <td>法規要求，見 <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></td>
      </tr>
  </tbody>
</table>
<p>未設定 retention 的 log group 預設永不過期 — 這是 CloudWatch 日誌成本超支的常見原因。新 log group 建立後應立即設定 retention。</p>
<h3 id="fintech-合規場景的-log-group-分離">FinTech 合規場景的 log group 分離</h3>
<p><a href="/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">FinTech 審計證據案例</a>揭露一個常見問題：audit log 跟 operational log 混在同一個 log group，retention 只能統一設定。結果要嘛 operational log 為了合規被迫留太久（成本浪費）、要嘛 audit log 跟著 operational log 的短 retention 被刪掉（合規風險）。</p>
<p>CloudWatch 的 log group 設計天然支援這種分離 — audit log 跟 operational log 用不同 log group、各自設定 retention：</p>
<table>
  <thead>
      <tr>
          <th>Log 類型</th>
          <th>Log group 命名</th>
          <th>Retention</th>
          <th>Log class</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>交易 audit log</td>
          <td><code>/prod/checkout-api/audit</code></td>
          <td>2555 天（7 年）</td>
          <td>Infrequent Access</td>
      </tr>
      <tr>
          <td>Application operational log</td>
          <td><code>/prod/checkout-api/app</code></td>
          <td>30 天</td>
          <td>Standard</td>
      </tr>
      <tr>
          <td>Access log（ALB / API Gateway）</td>
          <td><code>/prod/checkout-api/access</code></td>
          <td>90 天</td>
          <td>Standard</td>
      </tr>
  </tbody>
</table>
<p>Audit log group 的額外治理：</p>
<ul>
<li><strong>IAM 權限分離</strong>：audit log group 的讀取權限（<code>logs:GetLogEvents</code>）限縮到 compliance team 跟 security team，application developer 只能讀 operational log group。避免 audit log 被隨意查詢或汙染</li>
<li><strong>Immutability</strong>：CloudWatch Logs 本身不支援 WORM（write once read many），合規要求 immutable 存檔時用 subscription filter 把 audit log 同步送到 S3 + Object Lock</li>
<li><strong>Cross-account 集中</strong>：audit log 的 cross-account aggregation（見下方段落）的 IAM 權限要比 operational log 嚴格 — aggregated sink 的 destination 只能由 security team 控制</li>
</ul>
<h3 id="infrequent-access-log-class">Infrequent Access log class</h3>
<p>CloudWatch Logs 提供兩種 log class：<strong>Standard</strong>（完整查詢、即時 subscription filter、metric filter）跟 <strong>Infrequent Access</strong>（僅支援 Logs Insights 查詢、不支援即時 subscription filter 跟 metric filter、ingestion 成本約降 50%）。</p>
<p>Audit log 的存取模式通常是「寫入頻繁、查詢極少（只在稽核或事故時才查）」— 正好符合 Infrequent Access 的定位。把 7 年 retention 的 audit log group 設成 Infrequent Access，ingestion 成本直接砍半。</p>
<p>注意 Infrequent Access 的限制：不能用 subscription filter 即時轉發到 Lambda 或 Kinesis，不能用 metric filter 從 log 產生 CloudWatch metric。如果 audit log 需要即時異常偵測（例如偵測大量失敗交易），要用 Standard class + subscription filter 做即時處理、再用 Lambda 寫到長期 audit log group（Infrequent Access）。</p>
<h3 id="自動化套用">自動化套用</h3>
<p>用 AWS Config rule 或 CloudFormation / CDK 的 log group 定義統一設定 retention。Lambda function 自動建立的 log group 不會自動套用 retention，需要額外自動化（Lambda post-hook 或 EventBridge rule + Lambda 設定 retention）。</p>
<h2 id="cross-account-log-aggregation">Cross-account log aggregation</h2>
<h3 id="架構模式">架構模式</h3>
<p>多帳號環境下，常見做法是設立一個「觀測帳號」（observability account），把其他帳號的 logs 匯入。</p>
<p>兩種匯入方式：</p>
<p><strong>Subscription filter + Kinesis Data Firehose</strong>：每個 source 帳號的 log group 設 subscription filter，把 log event 送到 observability 帳號的 Kinesis Data Firehose，再寫到 S3 或 OpenSearch。適合需要長期存檔或進階查詢的場景。</p>
<p><strong>CloudWatch cross-account observability</strong>：AWS 原生功能，在 monitoring account 直接查詢 source accounts 的 CloudWatch 資料（metrics、logs、traces）。設定較簡單，但查詢延遲較高，且 Logs Insights 的 cross-account 查詢有 region 限制。</p>
<table>
  <thead>
      <tr>
          <th>匯入方式</th>
          <th>適合場景</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Subscription filter + Firehose</td>
          <td>需要 S3 archive、OpenSearch 全文搜尋、離線分析</td>
          <td>每個 log group 最多 2 個 subscription filter</td>
      </tr>
      <tr>
          <td>Cross-account observability</td>
          <td>只需要 CloudWatch console 統一查詢</td>
          <td>同 region 限制、查詢延遲較高</td>
      </tr>
  </tbody>
</table>
<h3 id="subscription-filter-實務">Subscription filter 實務</h3>
<p>Subscription filter 可以把 log event 送到 Lambda（即時處理）、Kinesis Data Stream（緩衝）、Kinesis Data Firehose（直接寫 S3/OpenSearch）或另一個 log group。</p>
<p>每個 log group 最多 2 個 subscription filter — 這是硬限制。如果同一個 log group 需要同時送 S3 archive 跟即時 alerting，要用 Kinesis Data Stream 做 fan-out，讓 stream 下游各自消費。</p>
<p>filter pattern 語法支援 JSON 欄位匹配：</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">{ $.level = &#34;ERROR&#34; }</span></span></code></pre></div><p>只把 ERROR 級別的 log 送到 alerting pipeline，可以大幅降低下游處理量跟成本。</p>
<h2 id="cost-governance">Cost governance</h2>
<h3 id="計費結構">計費結構</h3>
<p>CloudWatch Logs 的成本由三個維度組成：</p>
<table>
  <thead>
      <tr>
          <th>計費項目</th>
          <th>計費方式</th>
          <th>常見比例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ingestion</td>
          <td>每 GB ingested</td>
          <td>通常佔 50-70%</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>每 GB-month stored</td>
          <td>通常佔 20-40%</td>
      </tr>
      <tr>
          <td>Query（Logs Insights）</td>
          <td>每 GB scanned</td>
          <td>通常佔 5-15%</td>
      </tr>
  </tbody>
</table>
<p>Ingestion 是最大成本。降低 ingestion 的手段：</p>
<ul>
<li><strong>調整 log level</strong>：production 只保留 INFO 以上，DEBUG 只在問題排查時短暫開啟</li>
<li><strong>去除重複資訊</strong>：access log 跟 application log 不要記錄相同欄位</li>
<li><strong>用 metric filter 替代 log query</strong>：高頻計數（error count、request count）用 CloudWatch Metric Filter 從 log 產生 metric，查詢成本從 log scan 轉成 metric query</li>
</ul>
<h3 id="成本觀測">成本觀測</h3>
<p>用 CloudWatch 自己的 metric 觀測 log 成本：</p>
<ul>
<li><code>IncomingBytes</code>（per log group）：監控哪個 log group ingestion 最大</li>
<li><code>IncomingLogEvents</code>（per log group）：監控 event 數量</li>
<li>AWS Cost Explorer 按 CloudWatch 拆分：看 log ingestion vs storage vs API call 的比例</li>
</ul>
<h3 id="降本決策樹">降本決策樹</h3>
<p>判斷成本是否合理的順序：</p>
<ol>
<li>最大 ingestion 的 log group 是哪個？是否合理（核心服務的 access log 量大是正常的）</li>
<li>Retention 是否都有設定？未設定的 log group 會持續累積 storage 成本</li>
<li>是否有 DEBUG 級別 log 在 production 長期開啟？</li>
<li>是否有 subscription filter 把全量 log 送到外部？能否加 filter pattern 只送需要的部分</li>
</ol>
<h2 id="整合與下一步">整合與下一步</h2>
<ul>
<li>觀測管線整合：CloudWatch Logs → Subscription Filter → Kinesis Firehose → S3 / OpenSearch，見 <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></li>
<li>Audit log 治理：合規場景的 log retention 跟 access control，見 <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></li>
<li>Evidence package：把 Logs Insights query link 跟時間窗放進 evidence，見 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>OTel 整合：ADOT 可以把 log 送到 CloudWatch Logs 或其他 backend，見 <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 的故障演練與容量邊界">OpenTelemetry Collector 部署模式</a></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></channel></rss>