<?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>Pprof on Tarragon</title><link>https://tarrragon.github.io/blog/tags/pprof/</link><description>Recent content in Pprof on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 22 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/pprof/index.xml" rel="self" type="application/rss+xml"/><item><title>3.2 pprof 基礎診斷流程</title><link>https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/pprof/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/pprof/</guid><description>&lt;p>pprof 的核心用途是用實際執行資料定位效能問題。它能協助觀察 heap、goroutine、CPU、block、mutex 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace&lt;/a>，讓工程師從「感覺哪裡慢」改成「依 profile 判斷哪裡有壓力」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>安全地條件啟用 pprof endpoint&lt;/li>
&lt;li>判斷 heap、goroutine、CPU、block、mutex、trace 各自回答什麼問題&lt;/li>
&lt;li>用 &lt;code>go tool pprof&lt;/code> 取得 profile 並閱讀 &lt;code>top&lt;/code>&lt;/li>
&lt;li>區分 &lt;code>inuse_space&lt;/code> 與 &lt;code>alloc_space&lt;/code>&lt;/li>
&lt;li>把 profile 結果連回程式設計邊界&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察效能問題需要先問對問題">【觀察】效能問題需要先問對問題&lt;/h2>
&lt;p>pprof 診斷的核心起點是先確認你要回答哪個問題。不同 profile 回答不同問題，拿錯工具會讓分析變成猜測。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題&lt;/th>
 &lt;th>優先 profile&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>記憶體持續上升&lt;/td>
 &lt;td>heap &lt;code>inuse_space&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GC 壓力高、配置很多&lt;/td>
 &lt;td>heap &lt;code>alloc_space&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>goroutine 數量持續增加&lt;/td>
 &lt;td>goroutine profile&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU 使用率高&lt;/td>
 &lt;td>CPU profile&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>goroutine 常卡在 channel 或 syscall&lt;/td>
 &lt;td>goroutine / trace&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mutex 等待嚴重&lt;/td>
 &lt;td>mutex profile&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>channel/send/receive 阻塞多&lt;/td>
 &lt;td>block profile&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Profile 不是一次全抓就會自動給答案。先問清楚問題，再抓對應資料，分析成本會低很多。&lt;/p>
&lt;h2 id="判讀pprof-endpoint-是受控診斷入口">【判讀】pprof endpoint 是受控診斷入口&lt;/h2>
&lt;p>pprof endpoint 的核心安全責任是受控地暴露診斷資訊。它可能包含 goroutine stack、函式名稱、路徑、記憶體配置模式與部分請求脈絡；正式服務應把 &lt;code>/debug/pprof/&lt;/code> 放在明確啟用、內部網路或驗證保護之後。&lt;/p>
&lt;p>條件啟用範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nx">_&lt;/span> &lt;span class="s">&amp;#34;net/http/pprof&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">RegisterDebugEndpoints&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mux&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ServeMux&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;APP_PPROF&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="nx">mux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/debug/pprof/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DefaultServeMux&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>實務上還可以只綁定 localhost、掛在內部管理 port、加上身份驗證，或只在開發與診斷環境啟用。重點是 pprof 要受控，而不是跟公開 API 一起裸露。&lt;/p>
&lt;h2 id="執行heap-profile-看記憶體保留與配置壓力">【執行】heap profile 看記憶體保留與配置壓力&lt;/h2>
&lt;p>Heap profile 的核心問題是「哪些物件佔用或配置了記憶體」。當服務記憶體持續上升時，heap profile 是第一個常用工具。&lt;/p>
&lt;p>看目前仍被保留的記憶體：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">go tool pprof http://localhost:8080/debug/pprof/heap&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>進入 pprof 後：&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">(pprof) top&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>inuse_space&lt;/code> 代表目前仍被保留的記憶體，適合分析 leak、cache、map、slice、send &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a>、長期持有資料。&lt;/p>
&lt;p>看累積配置量：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">go tool pprof -alloc_space http://localhost:8080/debug/pprof/heap&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>alloc_space&lt;/code> 代表累積配置量，適合分析 JSON marshal、slice append、短命 object、熱路徑反覆配置造成的 GC 壓力。&lt;/p>
&lt;h2 id="判讀heap-profile-要連回資料結構">【判讀】heap profile 要連回資料結構&lt;/h2>
&lt;p>Heap profile 的核心解讀是問「誰持有資料」或「誰反覆配置」。看到某個函式在 top 裡，下一步要回到資料結構與生命週期。&lt;/p>
&lt;p>常見對應：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>profile 現象&lt;/th>
 &lt;th>可能設計問題&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>map 持續佔用&lt;/td>
 &lt;td>cache 沒有淘汰或 key 無限制成長&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>slice/history 佔用高&lt;/td>
 &lt;td>history 無上限或 list 回傳太大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JSON marshal alloc 高&lt;/td>
 &lt;td>高頻推送每個 client 重複 marshal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>bytes.Buffer 配置高&lt;/td>
 &lt;td>熱路徑重複建立 buffer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">websocket&lt;/a> message 佔用高&lt;/td>
 &lt;td>send buffer 滿載或慢 client&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Profile 給的是線索，不是最終修正。修正要回到資料模型、copy boundary、buffer policy 或 cache policy。&lt;/p>
&lt;h2 id="執行goroutine-profile-看存活與卡住路徑">【執行】goroutine profile 看存活與卡住路徑&lt;/h2>
&lt;p>Goroutine profile 的核心問題是「哪些 goroutine 還活著，以及它們卡在哪裡」。它常用來診斷 goroutine leak、channel 等待、鎖等待與 network read 阻塞。&lt;/p></description><content:encoded><![CDATA[<p>pprof 的核心用途是用實際執行資料定位效能問題。它能協助觀察 heap、goroutine、CPU、block、mutex 與 <a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace</a>，讓工程師從「感覺哪裡慢」改成「依 profile 判斷哪裡有壓力」。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>安全地條件啟用 pprof endpoint</li>
<li>判斷 heap、goroutine、CPU、block、mutex、trace 各自回答什麼問題</li>
<li>用 <code>go tool pprof</code> 取得 profile 並閱讀 <code>top</code></li>
<li>區分 <code>inuse_space</code> 與 <code>alloc_space</code></li>
<li>把 profile 結果連回程式設計邊界</li>
</ol>
<hr>
<h2 id="觀察效能問題需要先問對問題">【觀察】效能問題需要先問對問題</h2>
<p>pprof 診斷的核心起點是先確認你要回答哪個問題。不同 profile 回答不同問題，拿錯工具會讓分析變成猜測。</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>優先 profile</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體持續上升</td>
          <td>heap <code>inuse_space</code></td>
      </tr>
      <tr>
          <td>GC 壓力高、配置很多</td>
          <td>heap <code>alloc_space</code></td>
      </tr>
      <tr>
          <td>goroutine 數量持續增加</td>
          <td>goroutine profile</td>
      </tr>
      <tr>
          <td>CPU 使用率高</td>
          <td>CPU profile</td>
      </tr>
      <tr>
          <td>goroutine 常卡在 channel 或 syscall</td>
          <td>goroutine / trace</td>
      </tr>
      <tr>
          <td>mutex 等待嚴重</td>
          <td>mutex profile</td>
      </tr>
      <tr>
          <td>channel/send/receive 阻塞多</td>
          <td>block profile</td>
      </tr>
  </tbody>
</table>
<p>Profile 不是一次全抓就會自動給答案。先問清楚問題，再抓對應資料，分析成本會低很多。</p>
<h2 id="判讀pprof-endpoint-是受控診斷入口">【判讀】pprof endpoint 是受控診斷入口</h2>
<p>pprof endpoint 的核心安全責任是受控地暴露診斷資訊。它可能包含 goroutine stack、函式名稱、路徑、記憶體配置模式與部分請求脈絡；正式服務應把 <code>/debug/pprof/</code> 放在明確啟用、內部網路或驗證保護之後。</p>
<p>條件啟用範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nx">_</span> <span class="s">&#34;net/http/pprof&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">func</span> <span class="nf">RegisterDebugEndpoints</span><span class="p">(</span><span class="nx">mux</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">ServeMux</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="nx">os</span><span class="p">.</span><span class="nf">Getenv</span><span class="p">(</span><span class="s">&#34;APP_PPROF&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="s">&#34;1&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="nx">mux</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;/debug/pprof/&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">DefaultServeMux</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>實務上還可以只綁定 localhost、掛在內部管理 port、加上身份驗證，或只在開發與診斷環境啟用。重點是 pprof 要受控，而不是跟公開 API 一起裸露。</p>
<h2 id="執行heap-profile-看記憶體保留與配置壓力">【執行】heap profile 看記憶體保留與配置壓力</h2>
<p>Heap profile 的核心問題是「哪些物件佔用或配置了記憶體」。當服務記憶體持續上升時，heap profile 是第一個常用工具。</p>
<p>看目前仍被保留的記憶體：</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">go tool pprof http://localhost:8080/debug/pprof/heap</span></span></code></pre></div><p>進入 pprof 後：</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">(pprof) top</span></span></code></pre></div><p><code>inuse_space</code> 代表目前仍被保留的記憶體，適合分析 leak、cache、map、slice、send <a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a>、長期持有資料。</p>
<p>看累積配置量：</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">go tool pprof -alloc_space http://localhost:8080/debug/pprof/heap</span></span></code></pre></div><p><code>alloc_space</code> 代表累積配置量，適合分析 JSON marshal、slice append、短命 object、熱路徑反覆配置造成的 GC 壓力。</p>
<h2 id="判讀heap-profile-要連回資料結構">【判讀】heap profile 要連回資料結構</h2>
<p>Heap profile 的核心解讀是問「誰持有資料」或「誰反覆配置」。看到某個函式在 top 裡，下一步要回到資料結構與生命週期。</p>
<p>常見對應：</p>
<table>
  <thead>
      <tr>
          <th>profile 現象</th>
          <th>可能設計問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>map 持續佔用</td>
          <td>cache 沒有淘汰或 key 無限制成長</td>
      </tr>
      <tr>
          <td>slice/history 佔用高</td>
          <td>history 無上限或 list 回傳太大</td>
      </tr>
      <tr>
          <td>JSON marshal alloc 高</td>
          <td>高頻推送每個 client 重複 marshal</td>
      </tr>
      <tr>
          <td>bytes.Buffer 配置高</td>
          <td>熱路徑重複建立 buffer</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">websocket</a> message 佔用高</td>
          <td>send buffer 滿載或慢 client</td>
      </tr>
  </tbody>
</table>
<p>Profile 給的是線索，不是最終修正。修正要回到資料模型、copy boundary、buffer policy 或 cache policy。</p>
<h2 id="執行goroutine-profile-看存活與卡住路徑">【執行】goroutine profile 看存活與卡住路徑</h2>
<p>Goroutine profile 的核心問題是「哪些 goroutine 還活著，以及它們卡在哪裡」。它常用來診斷 goroutine leak、channel 等待、鎖等待與 network read 阻塞。</p>
<p>互動模式：</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">go tool pprof http://localhost:8080/debug/pprof/goroutine</span></span></code></pre></div><p>文字 stack：</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">curl <span class="s2">&#34;http://localhost:8080/debug/pprof/goroutine?debug=2&#34;</span></span></span></code></pre></div><p>若大量 goroutine 卡在同一個 channel receive、send、network read、ticker loop，通常代表某個退出條件、close path、<a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 或 unregister 設計有問題。</p>
<p>Goroutine profile 不只看數量。少量但卡在錯誤位置的 goroutine，也可能代表資源沒有被釋放。</p>
<h2 id="執行cpu-profile-看熱路徑">【執行】CPU profile 看熱路徑</h2>
<p>CPU profile 的核心問題是「程式把 CPU 時間花在哪裡」。它需要採樣一段時間，適合分析 CPU 使用率高或 request latency 異常。</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">go tool pprof <span class="s2">&#34;http://localhost:8080/debug/pprof/profile?seconds=30&#34;</span></span></span></code></pre></div><p>常用指令：</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">(pprof) top
</span></span><span class="line"><span class="ln">2</span><span class="cl">(pprof) list Encode</span></span></code></pre></div><p>CPU profile 要搭配流量情境解讀。低流量時抓到的 profile 可能沒有代表性；高流量時則要注意診斷本身也會造成額外負擔。</p>
<p>若 top 顯示大量時間花在 JSON encode、sort、lock、regex 或 compression，下一步應回到對應熱路徑，判斷是否可以減少工作、快取結果、改資料結構或降低呼叫頻率。</p>
<h2 id="策略block-與-mutex-profile-需要先啟用取樣">【策略】block 與 mutex profile 需要先啟用取樣</h2>
<p>Block/mutex profile 的核心用途是分析等待，而不是分析 CPU 計算。它們通常需要在程式中設定取樣比例。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">ConfigureBlockingProfiles</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">runtime</span><span class="p">.</span><span class="nf">SetBlockProfileRate</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">runtime</span><span class="p">.</span><span class="nf">SetMutexProfileFraction</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Block profile 看 goroutine 在同步原語上阻塞的時間，例如 channel send/receive、select、mutex。Mutex profile 看鎖競爭。</p>
<p>啟用取樣有成本，不一定要常駐開最高強度。診斷時可以條件啟用，或在壓測環境中使用。</p>
<h2 id="執行trace-看排程與延遲">【執行】trace 看排程與延遲</h2>
<p>Trace 的核心用途是觀察 goroutine 排程、network block、syscall、GC pause 與延遲事件。它比單一 profile 更完整，但也更重。</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">curl -o trace.out <span class="s2">&#34;http://localhost:8080/debug/pprof/trace?seconds=5&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">go tool trace trace.out</span></span></code></pre></div><p>Trace 適合用在你已經知道有延遲問題，但 heap、CPU、goroutine profile 都不足以解釋時。它能顯示 goroutine 何時 runnable、何時 blocked、何時被排程。</p>
<p>Trace 檔案可能很大，不適合長時間收集。通常先抓短時間，確認問題窗口後再精準分析。</p>
<h2 id="策略診斷流程要先保留現場">【策略】診斷流程要先保留現場</h2>
<p>pprof 診斷的核心流程是先保留現場，再改程式。若你先重啟服務或調參，可能會清掉最重要的證據。</p>
<p>建議流程：</p>
<ol>
<li>記錄當下流量、版本、操作、時間區間。</li>
<li>讀 runtime <a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a>：heap、GC、goroutine、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 長度。</li>
<li>依問題抓 profile：heap、goroutine、CPU 或 trace。</li>
<li>用 profile 找出函式與 stack pattern。</li>
<li>回到程式碼確認資料結構、goroutine lifecycle 或 hot path。</li>
<li>修改後用相同情境再抓一次 profile 驗證。</li>
</ol>
<p>這個流程能避免「看到 top 第一名就改」的衝動。Profile 需要和情境一起讀，才不會誤判。</p>
<h2 id="本章不處理">本章不處理</h2>
<p>本章先處理單一服務內的 profile 讀法；商用 APM 與分散式 tracing，會在下列章節再往外延伸：</p>
<ul>
<li><a href="/blog/go-advanced/07-distributed-operations/observability-pipeline/" data-link-title="7.4 Observability pipeline、metrics 與 tracing" data-link-desc="把 structured log、metric、trace 與 profile 組成可操作的診斷系統">Go 進階：Observability pipeline、metrics 與 tracing</a></li>
<li><a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Backend：可觀測性平台</a></li>
</ul>
<h2 id="和-go-教材的關係">和 Go 教材的關係</h2>
<p>這一章承接的是 goroutine、allocation 與 runtime metrics；如果你要先回看語言教材，可以讀：</p>
<ul>
<li><a href="/blog/go/04-concurrency/goroutine/" data-link-title="4.1 goroutine：輕量並發工作" data-link-desc="用 goroutine 啟動並發工作，並設計清楚的退出條件">Go：goroutine：輕量並發工作</a></li>
<li><a href="/blog/go/04-concurrency/select/" data-link-title="4.3 select：同時等待多種事件" data-link-desc="用 select 建立事件迴圈">Go：select：同時等待多種事件</a></li>
<li><a href="/blog/go/06-practical/new-background-worker/" data-link-title="6.4 如何新增背景工作流程" data-link-desc="接入 context、channel 與 shutdown">Go：如何新增背景工作流程</a></li>
<li><a href="/blog/go/07-refactoring/state-boundary/" data-link-title="7.4 狀態管理的安全邊界" data-link-desc="用 lock、copy 與 API 限制保護共享狀態">Go：狀態管理的安全邊界</a></li>
</ul>
<h2 id="小結">小結</h2>
<p>pprof 是診斷工具，不是公開 API。Heap profile 看保留與配置，goroutine profile 看存活與卡住路徑，CPU profile 看熱點，block/mutex profile 看等待，trace 看排程與延遲。好的診斷流程會先問對問題、抓對 profile，再把結果連回資料結構、goroutine lifecycle 與服務行為。</p>
]]></content:encoded></item><item><title>模組三：Runtime 與效能診斷</title><link>https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/</guid><description>&lt;p>Runtime 診斷的核心目標是用資料判斷服務壓力來源。Go 服務長時間運行後，問題常出現在 heap 成長、GC 壓力、goroutine 數量、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a> 堆積、JSON 配置與共享狀態保留；診斷流程應先看趨勢，再用 profile 定位來源。&lt;/p>
&lt;p>本模組承接前面的並發、WebSocket 與測試可靠性：如果 goroutine lifecycle、send buffer、repository copy boundary 沒設計好，runtime 訊號會在 heap profile、goroutine profile、CPU profile 或 allocation profile 中反映出來。&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/gc-memory-limit/" data-link-title="3.1 GC 與 memory limit" data-link-desc="理解 debug.SetMemoryLimit 在長時間服務中的用途">3.1&lt;/a>&lt;/td>
 &lt;td>GC 與 memory limit&lt;/td>
 &lt;td>理解 heap、GOGC、memory limit 與 runtime &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics&lt;/a> 的關係&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/pprof/" data-link-title="3.2 pprof 基礎診斷流程" data-link-desc="用 pprof endpoint 診斷 heap、goroutine 與 CPU 問題">3.2&lt;/a>&lt;/td>
 &lt;td>pprof 基礎診斷流程&lt;/td>
 &lt;td>用 heap、goroutine、CPU、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace&lt;/a> profile 定位壓力來源&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/goroutine-leak/" data-link-title="3.3 goroutine leak 偵測" data-link-desc="判斷背景工作與 client pump 是否正確退出">3.3&lt;/a>&lt;/td>
 &lt;td>goroutine leak 偵測&lt;/td>
 &lt;td>從 stack pattern 回到 context、close、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline&lt;/a> 與 ticker lifecycle&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/allocation/" data-link-title="3.4 資料結構與 allocation 壓力" data-link-desc="分析列表、歷史資料與 WebSocket payload 的配置成本">3.4&lt;/a>&lt;/td>
 &lt;td>資料結構與 allocation 壓力&lt;/td>
 &lt;td>區分必要 copy、安全邊界與可優化熱路徑配置&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="本模組使用的範例主題">本模組使用的範例主題&lt;/h2>
&lt;p>本模組使用虛構的即時通知服務作為範例。範例包含 WebSocket client lifecycle、background worker、repository list、JSON push payload 與 cache。&lt;/p>
&lt;p>範例只用來展示 Go runtime 診斷方法，不假設讀者正在維護任何特定專案。&lt;/p>
&lt;h2 id="本模組的-go-核心概念">本模組的 Go 核心概念&lt;/h2>
&lt;ul>
&lt;li>用 &lt;code>runtime.ReadMemStats&lt;/code> 或 &lt;code>runtime/metrics&lt;/code> 觀察 heap、GC 與 goroutine 趨勢。&lt;/li>
&lt;li>用 &lt;code>debug.SetMemoryLimit&lt;/code> 給 runtime 軟性記憶體目標。&lt;/li>
&lt;li>用 pprof 分析 heap、goroutine、CPU、block、mutex 與 trace。&lt;/li>
&lt;li>用 goroutine profile 找出卡在 channel、network read、ticker、mutex 的路徑。&lt;/li>
&lt;li>用 &lt;code>alloc_space&lt;/code> 與 &lt;code>inuse_space&lt;/code> 區分配置壓力與保留記憶體。&lt;/li>
&lt;li>用資料結構設計降低不必要 allocation，但保留必要 copy boundary。&lt;/li>
&lt;/ul>
&lt;h2 id="學習重點">學習重點&lt;/h2>
&lt;p>學完本模組後，你應該能判斷：&lt;/p>
&lt;ol>
&lt;li>記憶體問題是 GC 壓力、長期保留，還是短暫尖峰&lt;/li>
&lt;li>什麼情境適合調整 memory limit，什麼情境應該找 leak&lt;/li>
&lt;li>heap、goroutine、CPU、trace 各自回答什麼問題&lt;/li>
&lt;li>goroutine leak 應回到哪個 lifecycle 邊界修&lt;/li>
&lt;li>allocation 優化何時值得做，何時會破壞安全邊界&lt;/li>
&lt;/ol>
&lt;h2 id="本模組不處理">本模組不處理&lt;/h2>
&lt;p>本模組不討論分散式 tracing 平台、完整監控系統或雲端特定 profiler。這些工具可以接在本模組之後；本模組先建立 Go runtime 原生訊號與 pprof 的診斷思路。後續可接 &lt;a href="https://tarrragon.github.io/blog/go-advanced/07-distributed-operations/observability-pipeline/" data-link-title="7.4 Observability pipeline、metrics 與 tracing" data-link-desc="把 structured log、metric、trace 與 profile 組成可操作的診斷系統">Observability pipeline、metrics 與 tracing&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Runtime 診斷的核心目標是用資料判斷服務壓力來源。Go 服務長時間運行後，問題常出現在 heap 成長、GC 壓力、goroutine 數量、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> <a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a> 堆積、JSON 配置與共享狀態保留；診斷流程應先看趨勢，再用 profile 定位來源。</p>
<p>本模組承接前面的並發、WebSocket 與測試可靠性：如果 goroutine lifecycle、send buffer、repository copy boundary 沒設計好，runtime 訊號會在 heap profile、goroutine profile、CPU profile 或 allocation profile 中反映出來。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/go-advanced/03-runtime-profiling/gc-memory-limit/" data-link-title="3.1 GC 與 memory limit" data-link-desc="理解 debug.SetMemoryLimit 在長時間服務中的用途">3.1</a></td>
          <td>GC 與 memory limit</td>
          <td>理解 heap、GOGC、memory limit 與 runtime <a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a> 的關係</td>
      </tr>
      <tr>
          <td><a href="/blog/go-advanced/03-runtime-profiling/pprof/" data-link-title="3.2 pprof 基礎診斷流程" data-link-desc="用 pprof endpoint 診斷 heap、goroutine 與 CPU 問題">3.2</a></td>
          <td>pprof 基礎診斷流程</td>
          <td>用 heap、goroutine、CPU、<a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace</a> profile 定位壓力來源</td>
      </tr>
      <tr>
          <td><a href="/blog/go-advanced/03-runtime-profiling/goroutine-leak/" data-link-title="3.3 goroutine leak 偵測" data-link-desc="判斷背景工作與 client pump 是否正確退出">3.3</a></td>
          <td>goroutine leak 偵測</td>
          <td>從 stack pattern 回到 context、close、<a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 與 ticker lifecycle</td>
      </tr>
      <tr>
          <td><a href="/blog/go-advanced/03-runtime-profiling/allocation/" data-link-title="3.4 資料結構與 allocation 壓力" data-link-desc="分析列表、歷史資料與 WebSocket payload 的配置成本">3.4</a></td>
          <td>資料結構與 allocation 壓力</td>
          <td>區分必要 copy、安全邊界與可優化熱路徑配置</td>
      </tr>
  </tbody>
</table>
<h2 id="本模組使用的範例主題">本模組使用的範例主題</h2>
<p>本模組使用虛構的即時通知服務作為範例。範例包含 WebSocket client lifecycle、background worker、repository list、JSON push payload 與 cache。</p>
<p>範例只用來展示 Go runtime 診斷方法，不假設讀者正在維護任何特定專案。</p>
<h2 id="本模組的-go-核心概念">本模組的 Go 核心概念</h2>
<ul>
<li>用 <code>runtime.ReadMemStats</code> 或 <code>runtime/metrics</code> 觀察 heap、GC 與 goroutine 趨勢。</li>
<li>用 <code>debug.SetMemoryLimit</code> 給 runtime 軟性記憶體目標。</li>
<li>用 pprof 分析 heap、goroutine、CPU、block、mutex 與 trace。</li>
<li>用 goroutine profile 找出卡在 channel、network read、ticker、mutex 的路徑。</li>
<li>用 <code>alloc_space</code> 與 <code>inuse_space</code> 區分配置壓力與保留記憶體。</li>
<li>用資料結構設計降低不必要 allocation，但保留必要 copy boundary。</li>
</ul>
<h2 id="學習重點">學習重點</h2>
<p>學完本模組後，你應該能判斷：</p>
<ol>
<li>記憶體問題是 GC 壓力、長期保留，還是短暫尖峰</li>
<li>什麼情境適合調整 memory limit，什麼情境應該找 leak</li>
<li>heap、goroutine、CPU、trace 各自回答什麼問題</li>
<li>goroutine leak 應回到哪個 lifecycle 邊界修</li>
<li>allocation 優化何時值得做，何時會破壞安全邊界</li>
</ol>
<h2 id="本模組不處理">本模組不處理</h2>
<p>本模組不討論分散式 tracing 平台、完整監控系統或雲端特定 profiler。這些工具可以接在本模組之後；本模組先建立 Go runtime 原生訊號與 pprof 的診斷思路。後續可接 <a href="/blog/go-advanced/07-distributed-operations/observability-pipeline/" data-link-title="7.4 Observability pipeline、metrics 與 tracing" data-link-desc="把 structured log、metric、trace 與 profile 組成可操作的診斷系統">Observability pipeline、metrics 與 tracing</a>。</p>
<h2 id="學習時間">學習時間</h2>
<p>預計 3-4 小時</p>
]]></content:encoded></item></channel></rss>