<?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>Optimization on omegaatt</title>
    <link>https://www.omegaatt.com/tags/optimization/</link>
    <description>Recent content in Optimization on omegaatt</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>zh-TW</language>
    <managingEditor>raiven.kao@gmail.com (Raiven Kao)</managingEditor>
    <webMaster>raiven.kao@gmail.com (Raiven Kao)</webMaster>
    <copyright>Raiven Kao 2020 - 2026</copyright>
    <lastBuildDate>Sun, 18 Jan 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.omegaatt.com/tags/optimization/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Golang 1.25 testing/synctest 初體驗：告別在測試中寫 time.Sleep 的日子</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</link>
      <pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在我們目前的應用程式架構中，由於高度與 Kubernetes 耦合，服務啟動與運作期間需要頻繁地去讀取 K8s 中的 ConfigMap。為了達成配置熱更新（Hot Reload），我們引入了 Kubernetes client-go 中的 &lt;a href=&#34;https://pkg.go.dev/k8s.io/client-go/informers&#34;&gt;informers&lt;/a&gt; 機制來監聽 ConfigMap 的 CRUD 事件。&lt;/p&gt;
&lt;p&gt;雖然 K8s 官方提供了 fake client 讓我們能測試 informers 的邏輯，但在 Service Code 的層級，我們往往需要封裝一層更適合業務邏輯的 &lt;code&gt;ConfigWatcher&lt;/code&gt;。Golang 引以為傲的輕量級 Goroutine 與 Channel 搭配非常適合用來處理這種非同步的事件傳遞。&lt;/p&gt;
&lt;p&gt;然而，一旦涉及到 Goroutine 的非同步測試，「時間」往往就成了最大的敵人。&lt;/p&gt;
&lt;h2 id=&#34;遇到的問題不穩定的測試與魔法數字&#34;&gt;遇到的問題：不穩定的測試與魔法數字&lt;/h2&gt;
&lt;p&gt;為了模擬 ConfigMap 的變更通知，我們定義了一個 &lt;code&gt;ConfigMapWatcher&lt;/code&gt; 介面與對應的 Event 結構：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeAdded&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeModified&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeDeleted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Type&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Value&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;map&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，我們很自然地在 testing 中實作了一個 fake 物件來模擬事件發送：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;watchOnce&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sync&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Once&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newFakeConfigMapWatcher&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;: make(&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;sendEvent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchOnce&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Do&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Done&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;問題來了，當我們在寫單元測試時，呼叫 &lt;code&gt;sendEvent&lt;/code&gt; 將事件送入 channel 後，消費者端（也就是我們的業務邏輯 Goroutine）並不會「立刻」收到並處理完成。為了確保 &lt;code&gt;assert&lt;/code&gt; 斷言執行時，業務邏輯已經跑完了，我們被迫在測試中加入 &lt;code&gt;time.Sleep&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Container Image Optimization 那些年我們寫錯的 Dockerfile</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/container_image_optimization/</link>
      <pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/container_image_optimization/</guid>
      <description>&lt;p&gt;最近在檢視公司內部的專案時，針對其中一個 container image 進行了優化。在一個簡單的 commit 後，我們的 image size 從 &lt;strong&gt;1.82GB&lt;/strong&gt; 修正到了 &lt;strong&gt;1.18GB&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;透過 &lt;a href=&#34;https://github.com/wagoodman/dive&#34;&gt;dive&lt;/a&gt; 查看，Image Efficiency 更是從不及格的 &lt;strong&gt;69%&lt;/strong&gt; 飆升到了 &lt;strong&gt;99%&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這讓我回想起過去寫 Dockerfile 時，常常因為不了解 Docker Layer 的機制，或是為了寫起來「方便」，而踩到了許多效能與安全的地雷。&lt;/p&gt;
&lt;h2 id=&#34;致命的-chown&#34;&gt;致命的 &lt;code&gt;chown&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;這次優化的核心，其實源自於一個非常常見的操作：修改檔案權限。&lt;/p&gt;
&lt;p&gt;在我們的案例中，Dockerfile 原本是這樣寫的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Bad Practice: recursive chown after copy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; ubuntu:22.04&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;WORKDIR&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; /app&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; . .&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ... install dependencies ...&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; dpkg -i packages/*.deb&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Change ownership for security reasons&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; groupadd -r appuser &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd -r -g appuser appuser&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; chown -R appuser:appuser /app&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看起來邏輯很正確：把檔案複製進去，安裝套件，最後為了安全性將檔案權限交給非 root 使用者。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang Iterator 簡介與 samber/lo 比較</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</link>
      <pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</guid>
      <description>&lt;p&gt;自從 Golang 1.18 版本引入泛型（Generics）後，Go 語言的生態系統迎來了許多令人興奮的變化。其中，Golang 1.23 版本對 Iterator（迭代器）的標準化，以及 &lt;code&gt;iter&lt;/code&gt; 套件的加入，無疑是近期改動中相當重要的一環。本文將淺談 Golang Iterator 的基本概念，深入探討 Pure Iterator 與 Impure Iterator 之間的區別與設計考量，並與社群中流行的 &lt;code&gt;samber/lo&lt;/code&gt; 工具庫進行比較。&lt;/p&gt;
&lt;h2 id=&#34;什麼是-iterator&#34;&gt;什麼是 Iterator？&lt;/h2&gt;
&lt;p&gt;Iterator Pattern（迭代器模式）是一種常見的設計模式，它提供了一種循序存取集合物件中各個元素的方法，而又無需暴露該物件的內部表示。簡單來說，Iterator 就像一個指針，可以依序指向集合中的下一個元素，直到遍歷完所有元素為止。&lt;/p&gt;
&lt;h2 id=&#34;golang-中的-iterator&#34;&gt;Golang 中的 Iterator&lt;/h2&gt;
&lt;p&gt;在 Golang 1.23 之前，我們通常透過 &lt;code&gt;for-range&lt;/code&gt; 迴圈來迭代 array、slice、string、map、channel 等內建資料結構。然而，對於自訂的資料結構或複雜的序列生成邏輯，缺乏一個統一的迭代標準。&lt;/p&gt;
&lt;p&gt;Golang 1.23 版本正式將 Iterator 標準化，並在標準庫中加入了 &lt;code&gt;iter&lt;/code&gt; 套件。同時，&lt;code&gt;slices&lt;/code&gt; 和 &lt;code&gt;maps&lt;/code&gt; 套件也增加了一些回傳 Iterator 的工廠函數（Iterator Factories）。到了 Golang 1.24，更有如 &lt;code&gt;strings.SplitSeq&lt;/code&gt; 等函數加入，進一步豐富了 Iterator 的應用場景。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// strings.SplitSeq 回傳一個迭代器，用於遍歷由 sep 分隔的 s 子字串。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 此迭代器產生的字串與 Split(s, sep) 回傳的相同，但不會建構整個 slice。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 它回傳一個單次使用的迭代器。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SplitSeq&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sep&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;iter&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Seq&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果對 Golang 1.23+ 中 Iterator 的語法和語義還不熟悉，建議可以閱讀 Ian Lance Taylor 在 Go 官方部落格發表的&lt;a href=&#34;https://go.dev/blog/range-functions&#34;&gt;介紹文章&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過內建的 pprof 工具來分析 Golang 發生的 memory leak</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</link>
      <pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;某天下午，公司的 cronjob daemon 無預警的被 GCP OOM Kill 了，且程式碼沒有看出明顯的原因。&lt;/p&gt;
&lt;p&gt;根據過去的經驗，local 開發時會使用 &lt;code&gt;go tool pprof&lt;/code&gt; 來分析 CPU profile 或是 memory 與 trace 的問題，詳細可以參考 &lt;a href=&#34;https://go.dev/blog/pprof&#34;&gt;Go 官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;由於我們的程式碼是一個基於 gin 的 http service，因此可以使用 &lt;a href=&#34;https://github.com/gin-contrib/pprof&#34;&gt;gin 提供的 pprof&lt;/a&gt; 來快速建立 endpoint。&lt;/p&gt;
&lt;h2 id=&#34;gin-pprof&#34;&gt;gin pprof&lt;/h2&gt;
&lt;p&gt;gin 的 pprof package 提供了數個基於 &lt;code&gt;net/http/pprof&lt;/code&gt; 的 endpoint，可以分別為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt;: 基本的 pprof 的 static page，可以分析 CPU 與 memory 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/cmdline&lt;/code&gt;: 分析 command line 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/profile&lt;/code&gt;: 分析 CPU profile 的問題，可以透過 query string 來指定 CPU profile 的 duration。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/symbol&lt;/code&gt;: 分析 symbol table 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/trace&lt;/code&gt;: 分析 trace 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/goroutine&lt;/code&gt;: 分析 goroutine 的問題，這也是本文中重點查看的 endpoint。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/heap&lt;/code&gt;: 分析 heap 的問題。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以在 http server 中加入以下程式碼來啟用 pprof：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
