<?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>Quota on Tarragon</title><link>https://tarrragon.github.io/blog/tags/quota/</link><description>Recent content in Quota on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/quota/index.xml" rel="self" type="application/rss+xml"/><item><title>Kafka Multi-tenant 治理：quota 限流、ACL 授權與 topic 生命週期</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/multi-tenant-quota-acl/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/multi-tenant-quota-acl/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Apache Kafka&lt;/a> overview「Multi-tenant 與配額治理」「Topic 生命週期治理」兩段的 implementation-layer deep article。Overview 說明這些議題對應哪些案例跟子議題、本文展開具體的 quota / ACL 配置、授權模型推導、故障徵兆與修法。&lt;/p>&lt;/blockquote>
&lt;h2 id="共享叢集的治理問題一個叢集多個互不信任的租戶">共享叢集的治理問題：一個叢集、多個互不信任的租戶&lt;/h2>
&lt;p>Multi-tenant Kafka 的核心問題是把一個物理叢集切成多個彼此隔離的邏輯空間、讓每個團隊用同一組 broker 卻不互相干擾。當 Kafka 從單一團隊的工具長成全公司的事件總線、叢集承載的不再是一條 pipeline、而是數十到數百個團隊的 producer 跟 consumer。這時叢集的瓶頸從「broker 夠不夠快」轉成「怎麼防止某個團隊的流量、權限、或 topic 失控波及其他所有人」。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">Uber 的 Kafka 平台演進&lt;/a>把這個轉換描述為「從單隊列問題提升到平台治理問題」。當事件平台服務眾多團隊、重點是配額、隔離、觀測與運維標準化、而非只擴 broker。擴 broker 解決的是總容量、解決不了「單一租戶吃光共享資源」這類隔離問題。&lt;/p>
&lt;p>共享叢集的治理分三個獨立的軸、各自處理不同的失控來源：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &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>Quota（資源配額）&lt;/td>
 &lt;td>單租戶吃滿頻寬 / request 容量、餓死其他租戶&lt;/td>
 &lt;td>&lt;code>kafka-configs.sh&lt;/code> 設 byte rate&lt;/td>
 &lt;td>鄰居 producer 寫入卡死、consumer lag&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ACL（存取授權）&lt;/td>
 &lt;td>租戶讀寫不屬於自己的 topic、或被未授權方寫入&lt;/td>
 &lt;td>&lt;code>kafka-acls.sh&lt;/code> + broker authorizer&lt;/td>
 &lt;td>資料外洩、跨租戶污染、誤刪 topic&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生命週期（治理）&lt;/td>
 &lt;td>死 topic 累積、partition 數爆炸壓垮 metadata 面&lt;/td>
 &lt;td>命名規範 + 活躍判準 + 自動回收&lt;/td>
 &lt;td>controller 變慢、rebalance 風暴&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三軸正交：quota 設好不代表權限對、ACL 鎖好不代表 topic 不會爆炸。下面逐軸展開、每軸都對應 production 踩過的失控場景。本文 quota 與 ACL 操作以 Kafka 4.2.0（KRaft 模式、&lt;code>apache/kafka:latest&lt;/code>）實機驗證。&lt;/p>
&lt;h2 id="quota把頻寬與-request-容量切給租戶">Quota：把頻寬與 request 容量切給租戶&lt;/h2>
&lt;p>Quota 是 broker 端對 client 的流量上限、由 broker 在超限時主動 throttle（延遲回應）而非拒絕、讓單一租戶無法把共享頻寬吃光。Kafka 的 quota 是 broker-side 強制、不依賴 client 自律 —— 即使 client 不配合、broker 也會在回應裡插入 throttle 延遲、把該 client 的有效吞吐壓回配額內。&lt;/p>
&lt;h3 id="三類-quota-度量">三類 quota 度量&lt;/h3>
&lt;p>Kafka quota 度量三種資源、對應三類飽和：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Quota 鍵&lt;/th>
 &lt;th>單位&lt;/th>
 &lt;th>限制對象&lt;/th>
 &lt;th>飽和訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>producer_byte_rate&lt;/code>&lt;/td>
 &lt;td>bytes/sec&lt;/td>
 &lt;td>單一 client 每秒寫入 broker 的 bytes&lt;/td>
 &lt;td>寫入端 network / disk I/O 飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>consumer_byte_rate&lt;/code>&lt;/td>
 &lt;td>bytes/sec&lt;/td>
 &lt;td>單一 client 每秒從 broker 讀取的 bytes&lt;/td>
 &lt;td>讀取端 network 飽和、fan-out 過大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>request_percentage&lt;/code>&lt;/td>
 &lt;td>百分比&lt;/td>
 &lt;td>單一 client 佔用 broker request handler 的 CPU 時間&lt;/td>
 &lt;td>broker CPU 飽和、小訊息高頻請求&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>前兩個 byte rate 防的是頻寬類飽和、適合「大訊息、穩定流量」的租戶。&lt;code>request_percentage&lt;/code> 防的是另一種失控 —— 某租戶送大量極小的 request（例如每筆一個 byte、每秒幾萬筆）、byte rate 看起來很低、卻把 broker 的 request handler thread 佔滿。這種「請求數爆炸但流量不大」的攻擊型 pattern 只有 &lt;code>request_percentage&lt;/code> 抓得到。一個 broker 預設有 N 個 request handler thread、&lt;code>request_percentage=200&lt;/code> 代表允許該 client 用掉 2 條 thread 的時間（100% = 1 條）。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Apache Kafka</a> overview「Multi-tenant 與配額治理」「Topic 生命週期治理」兩段的 implementation-layer deep article。Overview 說明這些議題對應哪些案例跟子議題、本文展開具體的 quota / ACL 配置、授權模型推導、故障徵兆與修法。</p></blockquote>
<h2 id="共享叢集的治理問題一個叢集多個互不信任的租戶">共享叢集的治理問題：一個叢集、多個互不信任的租戶</h2>
<p>Multi-tenant Kafka 的核心問題是把一個物理叢集切成多個彼此隔離的邏輯空間、讓每個團隊用同一組 broker 卻不互相干擾。當 Kafka 從單一團隊的工具長成全公司的事件總線、叢集承載的不再是一條 pipeline、而是數十到數百個團隊的 producer 跟 consumer。這時叢集的瓶頸從「broker 夠不夠快」轉成「怎麼防止某個團隊的流量、權限、或 topic 失控波及其他所有人」。</p>
<p><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">Uber 的 Kafka 平台演進</a>把這個轉換描述為「從單隊列問題提升到平台治理問題」。當事件平台服務眾多團隊、重點是配額、隔離、觀測與運維標準化、而非只擴 broker。擴 broker 解決的是總容量、解決不了「單一租戶吃光共享資源」這類隔離問題。</p>
<p>共享叢集的治理分三個獨立的軸、各自處理不同的失控來源：</p>
<table>
  <thead>
      <tr>
          <th>治理軸</th>
          <th>防的是什麼</th>
          <th>工具</th>
          <th>失控後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Quota（資源配額）</td>
          <td>單租戶吃滿頻寬 / request 容量、餓死其他租戶</td>
          <td><code>kafka-configs.sh</code> 設 byte rate</td>
          <td>鄰居 producer 寫入卡死、consumer lag</td>
      </tr>
      <tr>
          <td>ACL（存取授權）</td>
          <td>租戶讀寫不屬於自己的 topic、或被未授權方寫入</td>
          <td><code>kafka-acls.sh</code> + broker authorizer</td>
          <td>資料外洩、跨租戶污染、誤刪 topic</td>
      </tr>
      <tr>
          <td>生命週期（治理）</td>
          <td>死 topic 累積、partition 數爆炸壓垮 metadata 面</td>
          <td>命名規範 + 活躍判準 + 自動回收</td>
          <td>controller 變慢、rebalance 風暴</td>
      </tr>
  </tbody>
</table>
<p>三軸正交：quota 設好不代表權限對、ACL 鎖好不代表 topic 不會爆炸。下面逐軸展開、每軸都對應 production 踩過的失控場景。本文 quota 與 ACL 操作以 Kafka 4.2.0（KRaft 模式、<code>apache/kafka:latest</code>）實機驗證。</p>
<h2 id="quota把頻寬與-request-容量切給租戶">Quota：把頻寬與 request 容量切給租戶</h2>
<p>Quota 是 broker 端對 client 的流量上限、由 broker 在超限時主動 throttle（延遲回應）而非拒絕、讓單一租戶無法把共享頻寬吃光。Kafka 的 quota 是 broker-side 強制、不依賴 client 自律 —— 即使 client 不配合、broker 也會在回應裡插入 throttle 延遲、把該 client 的有效吞吐壓回配額內。</p>
<h3 id="三類-quota-度量">三類 quota 度量</h3>
<p>Kafka quota 度量三種資源、對應三類飽和：</p>
<table>
  <thead>
      <tr>
          <th>Quota 鍵</th>
          <th>單位</th>
          <th>限制對象</th>
          <th>飽和訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>producer_byte_rate</code></td>
          <td>bytes/sec</td>
          <td>單一 client 每秒寫入 broker 的 bytes</td>
          <td>寫入端 network / disk I/O 飽和</td>
      </tr>
      <tr>
          <td><code>consumer_byte_rate</code></td>
          <td>bytes/sec</td>
          <td>單一 client 每秒從 broker 讀取的 bytes</td>
          <td>讀取端 network 飽和、fan-out 過大</td>
      </tr>
      <tr>
          <td><code>request_percentage</code></td>
          <td>百分比</td>
          <td>單一 client 佔用 broker request handler 的 CPU 時間</td>
          <td>broker CPU 飽和、小訊息高頻請求</td>
      </tr>
  </tbody>
</table>
<p>前兩個 byte rate 防的是頻寬類飽和、適合「大訊息、穩定流量」的租戶。<code>request_percentage</code> 防的是另一種失控 —— 某租戶送大量極小的 request（例如每筆一個 byte、每秒幾萬筆）、byte rate 看起來很低、卻把 broker 的 request handler thread 佔滿。這種「請求數爆炸但流量不大」的攻擊型 pattern 只有 <code>request_percentage</code> 抓得到。一個 broker 預設有 N 個 request handler thread、<code>request_percentage=200</code> 代表允許該 client 用掉 2 條 thread 的時間（100% = 1 條）。</p>
<h3 id="三種套用層級">三種套用層級</h3>
<p>Quota 可以套在三種 entity 上、精度遞增：</p>
<table>
  <thead>
      <tr>
          <th>套用層級</th>
          <th>entity 指定</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>client-id</td>
          <td><code>--entity-type clients --entity-name &lt;id&gt;</code></td>
          <td>沒有認證、用 client.id 區分服務</td>
      </tr>
      <tr>
          <td>user</td>
          <td><code>--entity-type users --entity-name &lt;user&gt;</code></td>
          <td>有 SASL 認證、整個租戶共用一個 quota</td>
      </tr>
      <tr>
          <td>user + client-id</td>
          <td>兩個 entity 同時指定</td>
          <td>同租戶內不同服務分別配額（最細）</td>
      </tr>
  </tbody>
</table>
<p>層級的選擇取決於認證模型。沒開認證的叢集只能用 client-id —— 但 client.id 由 client 自行宣告、可偽造、只適合內部信任環境的粗略區分。開了 SASL 認證後、user 才是可信的租戶邊界、quota 綁 user 才有隔離意義。最細的 user + client-id 組合用在「同一個租戶內、batch 匯入服務跟即時 API 服務要分開限流」這種情境：整個 billing 租戶有一個總配額、但裡面的 <code>batch-importer</code> 再單獨壓低、避免夜間批次把租戶配額吃光、害同租戶的即時服務沒頻寬。</p>
<h3 id="設定與查詢實機驗證">設定與查詢（實機驗證）</h3>
<p>設 client-id 層級、同時給 producer 跟 consumer byte rate：</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">kafka-configs.sh --bootstrap-server localhost:9092 --alter <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --add-config <span class="s1">&#39;producer_byte_rate=1048576,consumer_byte_rate=2097152&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --entity-type clients --entity-name svc-orders
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Completed updating config for client svc-orders.</span></span></span></code></pre></div><p>設 user 層級、含 <code>request_percentage</code>：</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">kafka-configs.sh --bootstrap-server localhost:9092 --alter <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --add-config <span class="s1">&#39;producer_byte_rate=5242880,consumer_byte_rate=10485760,request_percentage=200&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --entity-type users --entity-name tenant-billing
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Completed updating config for user tenant-billing.</span></span></span></code></pre></div><p>設 user + client-id 組合層級（同租戶內單獨壓低 batch 服務）：</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">kafka-configs.sh --bootstrap-server localhost:9092 --alter <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --add-config <span class="s1">&#39;producer_byte_rate=524288&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --entity-type users --entity-name tenant-billing <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --entity-type clients --entity-name batch-importer
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># Completed updating config for user tenant-billing.</span></span></span></code></pre></div><p>查詢時 entity 指定要對齊設定時的層級。查 user 層級：</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">kafka-configs.sh --bootstrap-server localhost:9092 --describe <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --entity-type users --entity-name tenant-billing
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># Quota configs for user-principal &#39;tenant-billing&#39; are</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">#   consumer_byte_rate=1.048576E7, request_percentage=200.0, producer_byte_rate=5242880.0</span></span></span></code></pre></div><p>組合層級要兩個 entity 都帶、否則查不到：</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">kafka-configs.sh --bootstrap-server localhost:9092 --describe <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --entity-type users --entity-name tenant-billing <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --entity-type clients --entity-name batch-importer
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Quota configs for user-principal &#39;tenant-billing&#39;, client-id &#39;batch-importer&#39; are</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">#   producer_byte_rate=524288.0</span></span></span></code></pre></div><p>不帶 <code>--entity-name</code> 而只給 <code>--entity-type clients</code> 會列出所有 client-id 層級的 quota、適合稽核整個叢集的 quota 分布。</p>
<h2 id="acl把存取權限綁到-principal">ACL：把存取權限綁到 principal</h2>
<p>ACL 是 broker 對每個操作的授權檢查、把「誰（principal）能對什麼資源（resource）做什麼操作（operation）從哪裡來（host）」綁成一條規則、broker 在每次 produce / fetch / admin 操作前比對。Quota 管的是「用多少」、ACL 管的是「能不能用」—— 兩者正交、quota 不限制權限、ACL 不限制流量。</p>
<h3 id="授權模型四要素">授權模型四要素</h3>
<p>一條 ACL 由四個維度構成、四個維度交集才決定一次操作是否放行：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>含義</th>
          <th>範例值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>principal</td>
          <td>操作的發起身分</td>
          <td><code>User:svc-orders</code></td>
      </tr>
      <tr>
          <td>resource</td>
          <td>被操作的對象（type + name + pattern）</td>
          <td>topic <code>orders.events</code>、group <code>fulfillment-workers</code></td>
      </tr>
      <tr>
          <td>operation</td>
          <td>動作</td>
          <td><code>Write</code> / <code>Read</code> / <code>Describe</code> / <code>All</code></td>
      </tr>
      <tr>
          <td>host</td>
          <td>來源 IP（<code>*</code> 為不限）</td>
          <td><code>10.0.3.21</code></td>
      </tr>
  </tbody>
</table>
<p>resource 的 pattern type 是隔離設計的關鍵：<code>LITERAL</code> 精確匹配單一資源名、<code>PREFIXED</code> 匹配整個前綴。多租戶的 topic 隔離靠 prefixed ACL 加命名規範 —— 給 <code>tenant-billing</code> 一條 <code>billing.</code> 前綴的 <code>All</code> 權限、它就能自由管理所有 <code>billing.</code> 開頭的 topic、卻碰不到 <code>orders.</code> 或別租戶的命名空間。命名規範在這裡不只是整潔、是授權邊界本身。</p>
<p>operation 的選擇要對齊角色。一個 producer 需要 topic 的 <code>Write</code> 跟 <code>Describe</code>（描述 partition metadata）；一個 consumer 需要 topic 的 <code>Read</code> <code>Describe</code> 加上 consumer group 的 <code>Read</code> <code>Describe</code>（commit offset 要對 group 有權）。漏掉 group 的 ACL 是常見錯誤：consumer 能讀到訊息、卻 commit 不了 offset、表現成不斷重複消費。</p>
<h3 id="kraft-的-standardauthorizer">KRaft 的 StandardAuthorizer</h3>
<p>ACL 的儲存與判定由 broker 的 authorizer 負責。KRaft 模式用 <code>org.apache.kafka.metadata.authorizer.StandardAuthorizer</code>、ACL 存在 metadata log（取代 ZooKeeper 時代的 <code>AclAuthorizer</code> 把 ACL 存在 ZK）。預設的 <code>apache/kafka</code> 容器不開 authorizer —— 不開時所有操作放行、ACL 指令也無從生效。啟用需要在 broker 設三項：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="ln">1</span><span class="cl"><span class="na">authorizer.class.name</span><span class="o">=</span><span class="s">org.apache.kafka.metadata.authorizer.StandardAuthorizer</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">super.users</span><span class="o">=</span><span class="s">User:admin</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">allow.everyone.if.no.acl.found</span><span class="o">=</span><span class="s">false</span></span></span></code></pre></div><p><code>super.users</code> 列出繞過所有 ACL 檢查的管理身分、用來開機跟救援；少了它、開 authorizer 後第一個操作就會把自己鎖在外面。<code>allow.everyone.if.no.acl.found=false</code> 是隔離的前提 —— 設 <code>true</code> 時「沒有任何 ACL 的資源對所有人開放」、等於 deny-list 模式、漏設一個 topic 就全公司可讀。多租戶必須走 <code>false</code> 的 allow-list 模式：預設拒絕、明確授權才放行。</p>
<blockquote>
<p>本文 ACL 操作以實機驗證：用上述三項 env（<code>KAFKA_AUTHORIZER_CLASS_NAME</code> / <code>KAFKA_SUPER_USERS='User:ANONYMOUS'</code> / <code>KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND=false</code>）配完整 KRaft single-node 設定起容器、PLAINTEXT 連線的 principal 為 <code>User:ANONYMOUS</code>、設為 super user 後即可用 <code>kafka-acls.sh</code> 操作。</p></blockquote>
<h3 id="acl-配置實機驗證">ACL 配置（實機驗證）</h3>
<p>給 producer 對單一 topic 的 write + describe：</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">kafka-acls.sh --bootstrap-server localhost:9092 --add <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --allow-principal User:svc-orders <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --operation Write --operation Describe <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --topic orders.events</span></span></code></pre></div><p>給 consumer topic 的 read + describe、外加 consumer group 的權限（一條指令同時建兩個 resource 的 ACL）：</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">kafka-acls.sh --bootstrap-server localhost:9092 --add <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --allow-principal User:svc-fulfillment <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --operation Read --operation Describe <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --topic orders.events <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --group fulfillment-workers</span></span></code></pre></div><p>prefixed ACL 把整個命名空間授權給一個租戶：</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">kafka-acls.sh --bootstrap-server localhost:9092 --add <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --allow-principal User:tenant-billing <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --operation All <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --resource-pattern-type prefixed <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --topic billing.
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># Adding ACLs for resource</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">#   `ResourcePattern(resourceType=TOPIC, name=billing., patternType=PREFIXED)`</span></span></span></code></pre></div><p>host 限制把同一 principal 的權限綁到特定來源 IP：</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">kafka-acls.sh --bootstrap-server localhost:9092 --add <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --allow-principal User:svc-orders <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --allow-host 10.0.3.21 <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --operation Write <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --topic orders.events</span></span></code></pre></div><p>deny 規則的優先序高於 allow —— 同一 principal 即使有 allow、命中 deny 就拒絕。用來在大範圍 allow（如 prefixed <code>All</code>）之上挖一個例外：</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">kafka-acls.sh --bootstrap-server localhost:9092 --add <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --deny-principal User:svc-orders <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --deny-host 10.0.9.99 <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --operation Write <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --topic orders.events</span></span></code></pre></div><p>列出特定 topic 的全部 ACL、用於稽核：</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">kafka-acls.sh --bootstrap-server localhost:9092 --list --topic orders.events</span></span></code></pre></div><h2 id="topic-生命週期治理命名ownership-與回收">Topic 生命週期治理：命名、ownership 與回收</h2>
<p>Topic 生命週期治理把「topic 的建立、歸屬、淘汰」變成有規則的流程、避免死 topic 累積與 partition 數爆炸壓垮叢集的 metadata 面。Kafka 的每個 partition 都是 controller 要追蹤的 metadata 單位；topic 只增不減時、partition 總數隨團隊數線性成長、最終 controller 的 metadata 處理、broker 的 leader election、client 的 metadata fetch 都跟著變慢。</p>
<h3 id="命名規範劃出-ownership">命名規範劃出 ownership</h3>
<p>Topic 命名規範把 ownership 跟隔離邊界編碼進名字本身。一個可治理的命名規範通常含三段：租戶 / 領域前綴、語意名、版本。例如 <code>billing.invoices.v1</code> —— <code>billing.</code> 前綴對齊 prefixed ACL 的隔離邊界跟 quota 的租戶歸屬、<code>invoices</code> 是語意、<code>v1</code> 給 schema 演進留出平行存在的空間。命名規範在多租戶不是風格問題、是三個治理軸的共同錨點：ACL 靠前綴授權、quota 靠前綴歸屬、回收靠前綴找 owner。</p>
<p>實機建 topic 時 Kafka 4.2.0 對 <code>.</code> 跟 <code>_</code> 混用會出 metric 名稱碰撞警告：</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">WARNING: Due to limitations in metric names, topics with a period (&#39;.&#39;)
</span></span><span class="line"><span class="ln">2</span><span class="cl">or underscore (&#39;_&#39;) could collide. To avoid issues it is best to use
</span></span><span class="line"><span class="ln">3</span><span class="cl">either, but not both.</span></span></code></pre></div><p>成因是 metric 名把 topic 名裡的 <code>.</code> 跟 <code>_</code> 都正規化掉、<code>billing.invoices</code> 跟 <code>billing_invoices</code> 可能對映到同一條 metric。命名規範應在 <code>.</code> 跟 <code>_</code> 之間選一個當分隔符、全叢集一致、避免監控數據互相污染。</p>
<h3 id="活躍判準與自動回收">活躍判準與自動回收</h3>
<p>死 topic 的回收靠可量化的活躍判準。<a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">LinkedIn 的 TopicGC</a>以自動治理取代手動清理未使用 topic、降低 metadata 壓力並改善 produce / consume 效能。它的判讀是：當 queue 規模擴大、僅靠容量擴充不夠、topic 生命週期與治理自動化會成為可靠性關鍵。</p>
<p>TopicGC 是 LinkedIn 的內部系統、不是 Kafka 內建指令；它揭示的是一套可借鏡的回收流程結構：</p>
<ol>
<li>定義活躍判準：以 last produce / last consume timestamp 判斷 topic 是否仍在使用、設一段觀察窗（例如 N 天無寫入且無讀取）。</li>
<li>分級回收：先標記（soft）、進入待回收狀態並通知 owner、保留一段 grace period、無人認領才真正刪除（hard）。兩段式避免誤刪仍有低頻流量的 topic。</li>
<li>保留稽核：每次標記與刪除留紀錄、回收前後比對 controller log、partition 數量、produce / consume 效能指標、確認治理有效且無誤傷。</li>
</ol>
<p>回收條件的設定要對齊業務節奏。純看 produce timestamp 會誤判「低頻但關鍵」的 topic（如月結批次）；活躍判準要同時看 produce 跟 consume、且觀察窗要長於最長的合法閒置週期。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1單一租戶暴衝吃滿頻寬quota-缺位">Case 1：單一租戶暴衝吃滿頻寬（quota 缺位）</h3>
<p><strong>徵兆</strong>：某團隊上線一支新 backfill job、開始全速寫入；同叢集其他租戶的 producer 端 <code>request-latency</code> p99 從個位數 ms 跳到數百 ms、consumer lag 全面上升；broker network out 打到網卡上限、但 CPU 不高。受害的不是暴衝者自己、是所有共用 broker 的鄰居。</p>
<p><strong>根因</strong>：叢集沒設任何 producer quota、或只對部分租戶設了 quota。沒有 broker-side throttle 時、單一 client 能用滿 broker 的 network / disk I/O、把共享頻寬擠光。byte rate 飽和的特徵是 network 打滿但 CPU 不高 —— 區別於 <code>request_percentage</code> 缺位導致的 CPU 飽和。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>立即對暴衝 client 設 <code>producer_byte_rate</code>、broker 即時 throttle、無需重啟。</li>
<li>建立 quota 預設值：對所有 client-id（或 user）設一個保守的 default byte rate、新租戶上線自動受限、避免「漏設就無限」。</li>
<li>區分 byte rate 與 request_percentage 飽和：network 打滿設 byte rate、CPU 打滿（高頻小訊息）補 <code>request_percentage</code>。</li>
<li>容量規劃：把各租戶 quota 總和對齊 broker 的 network / disk 容量、留 headroom、避免「每個 quota 都合理但加總超過物理上限」。</li>
</ol>
<h3 id="case-2acl-設太鬆或太緊">Case 2：ACL 設太鬆或太緊</h3>
<p><strong>徵兆（太鬆）</strong>：稽核發現某 consumer 服務能讀到不屬於它的租戶 topic；或某 topic 被預期外的 principal 寫入、資料被污染。最壞情況是 <code>allow.everyone.if.no.acl.found=true</code> 下漏設 ACL 的 topic 對全叢集可讀寫。</p>
<p><strong>徵兆（太緊）</strong>：consumer 能讀訊息卻不斷重複消費、log 顯示 commit offset 被拒；或 producer 報 <code>TOPIC_AUTHORIZATION_FAILED</code>、明明該有權限。</p>
<p><strong>根因</strong>：太鬆來自 deny-list 心態 —— <code>allow.everyone.if.no.acl.found=true</code> 把「沒設 ACL」當成「開放」、漏設就外洩。太緊通常是漏掉 operation 或 resource：consumer 只給了 topic 的 <code>Read</code>、漏給 consumer group 的 <code>Read</code> <code>Describe</code>、於是讀得到但 commit 不了、表現成重複消費；producer 漏給 <code>Describe</code>、拿不到 partition metadata。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>走 allow-list：<code>allow.everyone.if.no.acl.found=false</code>、預設拒絕、明確授權才放行。</li>
<li>ACL 對齊角色模板：producer = topic Write + Describe；consumer = topic Read + Describe 加 group Read + Describe；漏 group ACL 是重複消費的常見根因。</li>
<li>用 prefixed ACL 而非逐 topic 設、把授權邊界對齊命名規範前綴、減少漏設。</li>
<li>稽核流程：定期 <code>kafka-acls.sh --list</code> 比對預期授權矩陣、把 ACL 納入版本控制與 review、而非手動逐條加。</li>
</ol>
<h3 id="case-3topic-數量爆炸壓垮-metadata-面">Case 3：Topic 數量爆炸壓垮 metadata 面</h3>
<p><strong>徵兆</strong>：叢集 topic / partition 總數隨團隊增長爬到數萬以上；controller failover 時間從秒級拉長到分鐘級；broker 啟動載入 metadata 變慢；client 的 metadata fetch 變大變慢、rebalance 期間出現連鎖延遲。容量沒滿、但整個叢集的 control plane 變鈍。</p>
<p><strong>根因</strong>：partition 是 controller 要追蹤的 metadata 單位、數量只增不減。每個團隊隨手建 topic、每個 topic 又開高 partition 數、總 partition 數線性甚至超線性成長、壓垮 metadata 處理。KRaft 相比 ZooKeeper 提高了 metadata 上限、但上限仍存在、不是無限。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>Partition 數規劃納入 topic 建立流程：partition 數對應並行度上限、不是越多越好；多餘 partition 是純 metadata 成本。詳見 <a href="/blog/backend/knowledge-cards/partition/" data-link-title="Partition" data-link-desc="說明事件流如何切分成多個可並行處理的有序片段">Partition</a> 卡。</li>
<li>回收死 topic 釋放 partition slot：見 Case 4 與生命週期治理段。</li>
<li>監控 metadata 壓力訊號：controller log、partition 總數、controller failover 時間設告警、在壓垮前介入。</li>
<li>規模化路徑：單叢集 metadata 逼近上限時、評估分群（依關鍵程度分多叢集）、見 overview 的 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Cross-region 與分層叢集</a>段與 <a href="/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/" data-link-title="3.C4 LinkedIn：Kafka 分層叢集治理" data-link-desc="Kafka 從單叢集走向 tiered clusters 的轉換案例。">LinkedIn Tiered Clusters</a>案例。</li>
</ol>
<h3 id="case-4unused-topic-未回收">Case 4：Unused topic 未回收</h3>
<p><strong>徵兆</strong>：叢集裡大量 topic 數月無 produce 也無 consume、卻持續佔 partition slot 跟 metadata；沒人記得某些 topic 屬於哪個團隊、不敢刪；新 topic 想建時撞到 partition 上限、被迫先擴叢集而非先回收。</p>
<p><strong>根因</strong>：沒有活躍判準與回收流程、topic 只建不刪。歸屬資訊沒編碼進命名、回收時找不到 owner、於是「不敢刪」成為預設、死 topic 無限累積。這是 Case 3（metadata 爆炸）的慢性來源。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>建立活躍判準：以 last produce / last consume timestamp 加觀察窗判定死 topic、觀察窗長於最長合法閒置週期（避免誤刪月結類低頻 topic）。</li>
<li>兩段式回收：先 soft 標記並通知 owner、grace period 內無人認領才 hard 刪除、避免誤刪。</li>
<li>命名規範補 ownership：前綴對齊團隊、回收時能直接找到 owner、消除「不敢刪」。</li>
<li>自動化加稽核：參考 <a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">TopicGC</a>的流程結構、回收前後比對 metadata 與效能指標、留稽核紀錄。</li>
</ol>
<h2 id="容量與規模邊界">容量與規模邊界</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>估算 / 訊號</th>
          <th>警戒與下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Quota 總和 vs 物理容量</td>
          <td>各租戶 byte rate 加總對 broker network / disk 容量</td>
          <td>加總逼近物理上限要重新切分、留 headroom</td>
      </tr>
      <tr>
          <td>ACL 條目數</td>
          <td>逐 topic 設會隨 topic 數線性成長</td>
          <td>改 prefixed ACL 對齊命名規範、降條目數與漏設風險</td>
      </tr>
      <tr>
          <td>Partition 總數</td>
          <td>controller failover 時間、metadata fetch 延遲</td>
          <td>逼近上限先回收死 topic、再評估分群</td>
      </tr>
      <tr>
          <td>Topic 活躍率</td>
          <td>有 produce / consume 的 topic 佔比</td>
          <td>死 topic 比例高代表缺回收流程、補活躍判準</td>
      </tr>
  </tbody>
</table>
<p>Quota 與 ACL 是 broker-side 即時生效、不需重啟、可隨租戶調整、運維成本低。生命週期治理是持續流程、不是一次性操作 —— 死 topic 會持續產生、回收要常態化。三軸的共同前提是命名規範：沒有可治理的命名、quota 找不到歸屬、ACL 邊界對不齊、回收找不到 owner。多租戶治理的第一步是先把命名規範立起來、再談 quota 與 ACL。</p>
<h2 id="整合與下一步">整合與下一步</h2>
<h3 id="跟-overview-與案例的對位">跟 overview 與案例的對位</h3>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Apache Kafka</a> —— 本文展開其「Multi-tenant 與配額治理」「Topic 生命週期治理」兩段</li>
<li>平台治理案例：<a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6 Uber Kafka 事件平台</a> —— 單隊列問題提升到平台治理</li>
<li>生命週期案例：<a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3 LinkedIn TopicGC</a> —— 自動回收與 metadata 壓力</li>
<li>規模化分群：<a href="/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/" data-link-title="3.C4 LinkedIn：Kafka 分層叢集治理" data-link-desc="Kafka 從單叢集走向 tiered clusters 的轉換案例。">3.C4 LinkedIn Tiered Clusters</a> —— metadata 逼近上限時的多叢集路徑</li>
<li>自管轉 managed 的 ACL cutover：<a href="/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2 VMware → MSK</a></li>
</ul>
<h3 id="跟安全模組對位">跟安全模組對位</h3>
<p>ACL 是 Kafka 內建的授權層、處理 broker 級的 principal × resource 授權。完整的 secret 管理（SASL 認證憑證怎麼發、輪替、撤銷）屬於 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護與安全模組</a>的範疇 —— ACL 綁的 principal 從哪來、由認證層決定、ACL 只負責「這個 principal 能做什麼」。多租戶的完整信任鏈是「認證確認身分（07）→ ACL 授權操作（本文）→ quota 限制用量（本文）」三層。</p>
<h3 id="下一步議題">下一步議題</h3>
<ul>
<li>Schema 治理：跨租戶共用 topic 時、schema compatibility 是另一層契約治理、見 overview 的 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">KRaft 與 Schema Registry</a>段</li>
<li>Consumer group ACL 細節：跟 <a href="/blog/backend/knowledge-cards/consumer-group/" data-link-title="Consumer Group" data-link-desc="說明一組 consumer 如何共同分攤 stream 或 topic 的處理責任">Consumer group</a> rebalance 的互動</li>
<li>Quota 與 <a href="/blog/backend/knowledge-cards/delivery-semantics/" data-link-title="Delivery Semantics" data-link-desc="說明事件投遞語意如何定義遺失、重複、順序與補償策略">delivery semantics</a>：throttle 延遲對 producer timeout / retry 的影響</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Apache Kafka</a></li>
<li>對位 deep article（同模組）：本模組其他 Kafka deep article 見 vendor 頁進階主題段</li>
<li>跨模組授權鏈：<a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護與安全模組</a></li>
<li>方法論：<a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論</a></li>
<li>知識卡：<a href="/blog/backend/knowledge-cards/topic/" data-link-title="Topic" data-link-desc="說明 topic 如何把事件依主題分流給不同訂閱者">Topic</a>、<a href="/blog/backend/knowledge-cards/partition/" data-link-title="Partition" data-link-desc="說明事件流如何切分成多個可並行處理的有序片段">Partition</a>、<a href="/blog/backend/knowledge-cards/consumer-group/" data-link-title="Consumer Group" data-link-desc="說明一組 consumer 如何共同分攤 stream 或 topic 的處理責任">Consumer group</a></li>
</ul>
]]></content:encoded></item></channel></rss>