<?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>Kinoko's TIL Log</title><link>https://kinoko-tech-blog-theta.vercel.app/</link><description>Recent content on Kinoko's TIL Log</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>pippimotta@gmail.com (Kinoko)</managingEditor><webMaster>pippimotta@gmail.com (Kinoko)</webMaster><copyright>&amp;copy; Brewed by Kinoko</copyright><lastBuildDate>Thu, 06 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://kinoko-tech-blog-theta.vercel.app/index.xml" rel="self" type="application/rss+xml"/><item><title>TIL: Go's context.AfterFunc</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/til-sample-post/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/til-sample-post/</guid><description>&lt;p&gt;Go 1.21 added &lt;code&gt;context.AfterFunc&lt;/code&gt; which registers a function to run (in its own goroutine) after a context is done.&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AfterFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// cleanup logic here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Call stop() if you want to prevent the function from running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is cleaner than spinning up a goroutine with &lt;code&gt;select&lt;/code&gt; on &lt;code&gt;ctx.Done()&lt;/code&gt; &amp;ndash; the stdlib handles the lifecycle for you.&lt;/p&gt;
&lt;p&gt;Key things to note:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Go 1.21 added <code>context.AfterFunc</code> which registers a function to run (in its own goroutine) after a context is done.</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="nx">stop</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">AfterFunc</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="c1">// cleanup logic here</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// Call stop() if you want to prevent the function from running</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">stop</span><span class="p">()</span></span></span></code></pre></div><p>This is cleaner than spinning up a goroutine with <code>select</code> on <code>ctx.Done()</code> &ndash; the stdlib handles the lifecycle for you.</p>
<p>Key things to note:</p>
<ul>
<li>The function runs in its own goroutine</li>
<li><code>stop()</code> returns <code>true</code> if it successfully prevented the call</li>
<li>Multiple <code>AfterFunc</code> calls on the same context are independent</li>
</ul>
]]></content:encoded></item><item><title>dbtpl Code Generation</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/dbtpl-code-generation/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/dbtpl-code-generation/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;dbtpl (part of the xo ecosystem) connects to a database, introspects the schema, and generates type-safe structs and query methods through Go templates. Hand-written DB structs easily drift from the schema; dbtpl makes the schema the single source of truth.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Database (PostgreSQL / MySQL / SQLite...)
 ↓ introspect schema (tables, columns, types, indexes, FK...)
dbtpl
 ↓ apply Go templates
Generated Go code (structs, query methods)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No need to maintain DB structs by hand &amp;ndash; when the schema changes, re-run dbtpl to sync.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>dbtpl (part of the xo ecosystem) connects to a database, introspects the schema, and generates type-safe structs and query methods through Go templates. Hand-written DB structs easily drift from the schema; dbtpl makes the schema the single source of truth.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>How it works</strong></p>





<pre tabindex="0"><code>Database (PostgreSQL / MySQL / SQLite...)
    ↓ introspect schema (tables, columns, types, indexes, FK...)
dbtpl
    ↓ apply Go templates
Generated Go code (structs, query methods)</code></pre><p>No need to maintain DB structs by hand &ndash; when the schema changes, re-run dbtpl to sync.</p>
<p><strong>Two modes</strong></p>
<p><strong>Schema mode</strong>: generate from the entire DB schema</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">dbtpl schema postgres://user:pass@host/dbname -o ./models</span></span></code></pre></div><p>Generates a Go struct for each table, including methods related to primary keys, foreign keys, and indexes.</p>
<p><strong>Query mode</strong>: generate type-safe result structs from custom SQL</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">dbtpl query postgres://user:pass@host/dbname <span class="s">&lt;&lt; ENDSQL
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="s">SELECT a.name::varchar AS name, b.type::integer AS my_type
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">FROM authors a
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s">JOIN authortypes b ON a.id = b.author_id
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s">WHERE a.id = %%authorID int%%
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s">ENDSQL</span></span></span></code></pre></div><p><code>%%param type%%</code> is dbtpl&rsquo;s query parameter syntax, which generates a corresponding function signature.</p>
<p><strong>What gets generated</strong></p>
<ul>
<li>Go structs matching each table (field types aligned with DB schema)</li>
<li>CRUD query methods (Insert, Update, Delete, Get by PK)</li>
<li>Enum types</li>
<li>Struct tags (<code>db:&quot;column_name&quot;</code> etc.)</li>
</ul>
<p><strong>Template customization</strong></p>
<p>dbtpl&rsquo;s generation logic uses Go <code>text/template</code>. You can dump the built-in templates and modify them:</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">dbtpl dump --src base -o ./custom-templates
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># edit the .tpl files in custom-templates/</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">dbtpl schema postgres://... --src ./custom-templates</span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why not just hand-write structs?</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Hand-written structs</th>
					<th>dbtpl generated</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Schema sync</td>
					<td>Manual maintenance, easy to drift</td>
					<td>Re-run to sync</td>
			</tr>
			<tr>
					<td>Type safety</td>
					<td>Relies on the engineer to verify</td>
					<td>Derived from DB schema</td>
			</tr>
			<tr>
					<td>After migration</td>
					<td>Must remember to update structs</td>
					<td>Just re-run</td>
			</tr>
	</tbody>
</table>
<p><strong>dbtpl vs sqlc</strong></p>
<p>Both generate Go code from a database, but they differ in approach:</p>
<ul>
<li><strong>sqlc</strong>: SQL-query-centric &ndash; write SQL first, then generate the corresponding functions</li>
<li><strong>dbtpl</strong>: schema-centric &ndash; introspects the entire DB, with more template flexibility</li>
</ul>
]]></content:encoded></item><item><title>Go Context</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/go-context/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/go-context/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Go&amp;rsquo;s &lt;code&gt;context&lt;/code&gt; is the standard solution for propagating cancellation signals, timeouts, and request-scoped data across goroutines. It lets an entire call chain stop cleanly at a unified point, leaving no goroutine leaks.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An HTTP handler often runs multiple goroutines underneath &amp;ndash; hitting the DB, calling external APIs, doing computation. Three scenarios cause trouble:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Timeout&lt;/strong&gt;: the request times out, and downstream work should stop too&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cancellation&lt;/strong&gt;: the user closes the browser mid-request; continuing is wasted work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request-scoped data&lt;/strong&gt;: auth tokens and trace IDs need to flow down the call chain without adding a parameter to every function&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; unifies all three into a single standard interface.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Go&rsquo;s <code>context</code> is the standard solution for propagating cancellation signals, timeouts, and request-scoped data across goroutines. It lets an entire call chain stop cleanly at a unified point, leaving no goroutine leaks.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>The problem</strong></p>
<p>An HTTP handler often runs multiple goroutines underneath &ndash; hitting the DB, calling external APIs, doing computation. Three scenarios cause trouble:</p>
<ol>
<li><strong>Timeout</strong>: the request times out, and downstream work should stop too</li>
<li><strong>Cancellation</strong>: the user closes the browser mid-request; continuing is wasted work</li>
<li><strong>Request-scoped data</strong>: auth tokens and trace IDs need to flow down the call chain without adding a parameter to every function</li>
</ol>
<p><code>context</code> unifies all three into a single standard interface.</p>
<hr>
<p><strong>Basic usage</strong></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="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">5</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w"> </span><span class="c1">// must call this, otherwise it leaks resources</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">db</span><span class="p">.</span><span class="nf">QueryContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;SELECT ...&#34;</span><span class="p">)</span></span></span></code></pre></div><p>When the timeout expires, <code>ctx.Done()</code> closes, and all context-aware functions stop automatically.</p>
<hr>
<p><strong>How to receive it in a function</strong></p>
<p>Functions that accept a context take <code>ctx context.Context</code> as the first parameter &ndash; this is a mandatory Go convention.</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="w"> </span><span class="nf">doWork</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </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="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</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="k">return</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w"> </span><span class="c1">// context.DeadlineExceeded or context.Canceled</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nf">longOperation</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nf">process</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<p><strong>Four ways to create a context</strong></p>
<table>
	<thead>
			<tr>
					<th>Constructor</th>
					<th>Purpose</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>context.Background()</code></td>
					<td>Root context, used at program entry points</td>
			</tr>
			<tr>
					<td><code>context.TODO()</code></td>
					<td>Placeholder when you are not sure what to use yet</td>
			</tr>
			<tr>
					<td><code>context.WithCancel(parent)</code></td>
					<td>Manual cancellation</td>
			</tr>
			<tr>
					<td><code>context.WithTimeout / WithDeadline</code></td>
					<td>Auto-cancel when time expires</td>
			</tr>
			<tr>
					<td><code>context.WithValue(parent, key, val)</code></td>
					<td>Attach request-scoped values (use sparingly)</td>
			</tr>
	</tbody>
</table>
<p>Every new context is a child of its parent &ndash; <strong>cancellation only propagates from parent to child</strong>. When the parent is cancelled, all children are cancelled too.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Context must not be stored in a struct</strong></p>
<blockquote>
<p>Always pass it as a parameter, never put it in a struct field.</p>
</blockquote>
<p>A context represents the lifetime of a request, not object state. Storing it in a struct makes the lifetime unclear.</p>
<p><strong><code>WithValue</code> is only for cross-cutting concerns</strong></p>
<p><code>WithValue</code> is for things like tracing IDs and auth tokens &ndash; data every request needs but that would pollute function signatures. Do not use it to pass regular business parameters &ndash; for those, just add a proper parameter.</p>
<p><strong>Mental model</strong></p>
<blockquote>
<p>Context is a control signal for a tree. When the root says &ldquo;stop,&rdquo; every goroutine in the tree stops &ndash; cleanly, no leaks.</p>
</blockquote>
]]></content:encoded></item><item><title>Go Deep Copy vs Shallow Copy</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/go-deep-copy-vs-shallow-copy/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/go-deep-copy-vs-shallow-copy/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;A shallow copy copies the &amp;ldquo;reference to the data&amp;rdquo; &amp;ndash; the original and the copy still share the underlying data. A deep copy copies &amp;ldquo;the data itself&amp;rdquo; &amp;ndash; the two are completely independent. Go&amp;rsquo;s assignment is shallow copy by default; whether the original is affected depends on whether the type is a value type or a reference type.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Value type vs Reference type&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Value type (assignment = full copy)&lt;/th&gt;
					&lt;th&gt;Reference type (assignment = copy reference)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float64&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;slice&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;pointer&lt;/code&gt;, &lt;code&gt;channel&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;array&lt;/code&gt; (&lt;code&gt;[N]T&lt;/code&gt;)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;interface&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;struct&lt;/code&gt; (but fields may contain references)&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Slice: the most common gotcha&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>A shallow copy copies the &ldquo;reference to the data&rdquo; &ndash; the original and the copy still share the underlying data. A deep copy copies &ldquo;the data itself&rdquo; &ndash; the two are completely independent. Go&rsquo;s assignment is shallow copy by default; whether the original is affected depends on whether the type is a value type or a reference type.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Value type vs Reference type</strong></p>
<table>
	<thead>
			<tr>
					<th>Value type (assignment = full copy)</th>
					<th>Reference type (assignment = copy reference)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>int</code>, <code>float64</code>, <code>bool</code>, <code>string</code></td>
					<td><code>slice</code>, <code>map</code>, <code>pointer</code>, <code>channel</code></td>
			</tr>
			<tr>
					<td><code>array</code> (<code>[N]T</code>)</td>
					<td><code>interface</code></td>
			</tr>
			<tr>
					<td><code>struct</code> (but fields may contain references)</td>
					<td></td>
			</tr>
	</tbody>
</table>
<p><strong>Slice: the most common gotcha</strong></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="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">a</span><span class="w">          </span><span class="c1">// shallow copy: b and a share the same underlying array</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">99</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span><span class="w">  </span><span class="c1">// [99 2 3]  &lt;- a was modified!</span></span></span></code></pre></div>




<pre tabindex="0"><code>a → [ header: ptr → [99, 2, 3], len=3, cap=3 ]
b → [ header: ptr ↗ ]   ← same array</code></pre><p>To deep copy a slice, use <code>copy()</code>:</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="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">a</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">copy</span><span class="p">(</span><span class="nx">c</span><span class="p">,</span><span class="w"> </span><span class="nx">a</span><span class="p">)</span><span class="w">      </span><span class="c1">// deep copy</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">c</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span><span class="w">  </span><span class="c1">// [99 2 3]  &lt;- a is not affected</span></span></span></code></pre></div><p><strong>Map: assignment also only copies the reference</strong></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="nx">m1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">{</span><span class="s">&#34;a&#34;</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">m2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">m1</span><span class="w">        </span><span class="c1">// shallow copy</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">m2</span><span class="p">[</span><span class="s">&#34;a&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">99</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">m1</span><span class="p">[</span><span class="s">&#34;a&#34;</span><span class="p">])</span><span class="w">  </span><span class="c1">// 99 &lt;- m1 was also modified</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// deep copy a map by looping manually</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">m3</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">m1</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nx">m3</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>Struct: value copy by default, but watch out for pointer/slice fields</strong></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">type</span><span class="w"> </span><span class="nx">Person</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="w">   </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nx">Scores</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="w">   </span><span class="c1">// slice is a reference type!</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">p1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Person</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Alice&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Scores</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">90</span><span class="p">,</span><span class="w"> </span><span class="mi">80</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">p2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">p1</span><span class="w">           </span><span class="c1">// struct is value-copied, but Scores only copies the slice header</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">p2</span><span class="p">.</span><span class="nx">Name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">p1</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">      </span><span class="c1">// &#34;Alice&#34; &lt;- string is a value type, not affected</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="nx">p2</span><span class="p">.</span><span class="nx">Scores</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">100</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">p1</span><span class="p">.</span><span class="nx">Scores</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="w"> </span><span class="c1">// 100 &lt;- the underlying array of Scores is shared!</span></span></span></code></pre></div><p>To deep copy a struct with slices, explicitly copy each reference field:</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="nx">p3</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Person</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="p">:</span><span class="w">   </span><span class="nx">p1</span><span class="p">.</span><span class="nx">Name</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="nx">Scores</span><span class="p">:</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">p1</span><span class="p">.</span><span class="nx">Scores</span><span class="p">)),</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">copy</span><span class="p">(</span><span class="nx">p3</span><span class="p">.</span><span class="nx">Scores</span><span class="p">,</span><span class="w"> </span><span class="nx">p1</span><span class="p">.</span><span class="nx">Scores</span><span class="p">)</span></span></span></code></pre></div><p><strong><code>copy()</code> limitation: only copies the outer layer</strong></p>
<p><code>copy()</code> is a built-in function, but it only copies element values &ndash; it does not recursively handle nested reference types.</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="c1">// Elements are value type → copy() works as a deep copy</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">a</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nb">copy</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">a</span><span class="p">)</span><span class="w">  </span><span class="c1">// fully independent, no problem</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// Elements are reference type → copy() only copies the outer layer, inner layer is still shared</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[][]</span><span class="kt">int</span><span class="p">{{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">([][]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">a</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">copy</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">a</span><span class="p">)</span><span class="w">  </span><span class="c1">// b[0] and a[0] still point to the same underlying array!</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">99</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">a</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span><span class="w">  </span><span class="c1">// 99 &lt;- still modified</span></span></span></code></pre></div><p>Conclusion: <code>copy()</code> is sufficient when elements are value types; when elements contain reference types, you need to copy recursively by hand.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Go has no built-in deep copy</strong></p>
<p>For complex nested structures, there is no silver bullet. Common approaches:</p>
<table>
	<thead>
			<tr>
					<th>Method</th>
					<th>Best for</th>
					<th>Downside</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Manual field-by-field</td>
					<td>Simple structures, performance-sensitive</td>
					<td>Tedious, must update when fields are added</td>
			</tr>
			<tr>
					<td><code>encoding/json</code> marshal then unmarshal</td>
					<td>Quick validation / performance not critical</td>
					<td>Slow, only copies exported fields</td>
			</tr>
			<tr>
					<td><code>proto.Clone()</code></td>
					<td>Protobuf messages</td>
					<td>Only works for proto</td>
			</tr>
			<tr>
					<td>Third-party library</td>
					<td>Complex structures</td>
					<td>Adds a dependency</td>
			</tr>
	</tbody>
</table>
<p><strong>Interview essential: the underlying structure of slice assignment</strong></p>
<p>A Go slice is essentially a struct:</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="c1">// Conceptually looks like this (not actual Go code)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">SliceHeader</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </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="nx">Data</span><span class="w"> </span><span class="kt">uintptr</span><span class="w">  </span><span class="c1">// pointer to the underlying array</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nx">Len</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="nx">Cap</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>b := a</code> copies this header, not the underlying array &ndash; that is the root cause of shallow copy.</p>
<p><strong>One-sentence test</strong></p>
<blockquote>
<p>If modifying the copy also changes the original, it is a shallow copy. If not, it is a deep copy.</p>
</blockquote>
]]></content:encoded></item><item><title>atomic.Value &amp; Goroutine Token Refresh</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/atomic-value-goroutine-token-refresh/</link><pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/atomic-value-goroutine-token-refresh/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;When multiple goroutines share a value that gets updated periodically (like an auth token), use &lt;code&gt;atomic.Value&lt;/code&gt; for lock-free reads and writes &amp;ndash; lighter than a mutex. Combined with &lt;code&gt;time.Ticker&lt;/code&gt; for periodic refresh and &lt;code&gt;ctx.Done()&lt;/code&gt; for goroutine lifecycle control, this forms a complete concurrent token management pattern.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;10 goroutines publish events concurrently, each needing an AUTHZ token. Another goroutine refreshes the token every 40s (TTL 60s, refreshing 20s early as buffer).&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>When multiple goroutines share a value that gets updated periodically (like an auth token), use <code>atomic.Value</code> for lock-free reads and writes &ndash; lighter than a mutex. Combined with <code>time.Ticker</code> for periodic refresh and <code>ctx.Done()</code> for goroutine lifecycle control, this forms a complete concurrent token management pattern.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>The problem</strong></p>
<p>10 goroutines publish events concurrently, each needing an AUTHZ token. Another goroutine refreshes the token every 40s (TTL 60s, refreshing 20s early as buffer).</p>
<p>How do you let the refresher (writer) and 10 publishers (readers) safely share the same token without a data race?</p>
<p><strong>atomic.Value: lock-free shared value</strong></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">var</span><span class="w"> </span><span class="nx">token</span><span class="w"> </span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Value</span><span class="w">  </span><span class="c1">// declared, zero value is nil</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// Write (refresher goroutine)</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">token</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="s">&#34;new-token-string&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// Read (publisher goroutines, can read concurrently, no lock needed)</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nx">t</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">token</span><span class="p">.</span><span class="nf">Load</span><span class="p">().(</span><span class="kt">string</span><span class="p">)</span><span class="w">  </span><span class="c1">// type assertion to get the actual type</span></span></span></code></pre></div><p><code>atomic.Value</code>&rsquo;s Store/Load are atomic operations &ndash; no mutex needed. Multiple goroutines reading concurrently is completely safe, and an occasional Store will not give any reader a corrupted value.</p>
<p><strong>Complete pattern</strong></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">var</span><span class="w"> </span><span class="nx">token</span><span class="w"> </span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Value</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Refresher goroutine: periodically refresh the token</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nx">ticker</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">NewTicker</span><span class="p">(</span><span class="mi">40</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">ticker</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span><span class="w">  </span><span class="c1">// remember to release ticker resources</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </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="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ticker</span><span class="p">.</span><span class="nx">C</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">            </span><span class="nx">newToken</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fetchToken</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </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="nx">token</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">newToken</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="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">  </span><span class="c1">// exit when context is cancelled</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">// Publisher goroutines: 10 running concurrently</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">            </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">            </span><span class="k">case</span><span class="w"> </span><span class="nx">event</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">eventCh</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">                </span><span class="nx">t</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">token</span><span class="p">.</span><span class="nf">Load</span><span class="p">().(</span><span class="kt">string</span><span class="p">)</span><span class="w">  </span><span class="c1">// read current token</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">                </span><span class="nf">publish</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">event</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">            </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w">  </span><span class="c1">// exit when context is cancelled</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>time.Ticker: periodically firing channel</strong></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="nx">ticker</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">NewTicker</span><span class="p">(</span><span class="mi">40</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// ticker.C is a channel that receives a signal every 40s</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// must call ticker.Stop() to release the underlying timer resources</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">ticker</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span></span></span></code></pre></div><p><strong>ctx.Done(): unified exit signal</strong></p>
<p><code>ctx.Done()</code> returns a channel that gets closed when the context is cancelled. All goroutines listening on it in a <code>select</code> will receive the signal and exit simultaneously.</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="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ticker</span><span class="p">.</span><span class="nx">C</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="c1">// periodic refresh</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w">  </span><span class="c1">// stop when told to, no goroutine leak</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>sync.Mutex: a lock to protect critical sections</strong></p>
<p>When multiple goroutines read and write the same data, a mutex ensures only one goroutine can enter at a time:</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">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">sharedData</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// Write</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">sharedData</span><span class="p">[</span><span class="s">&#34;key&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">42</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">// Read (also needs lock because map is not concurrent-safe)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sharedData</span><span class="p">[</span><span class="s">&#34;key&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</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></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// Idiomatic: defer unlock to avoid forgetting or leaking on panic</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nx">sharedData</span><span class="p">[</span><span class="s">&#34;key&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">42</span></span></span></code></pre></div><p><code>sync.RWMutex</code> is the upgraded version &ndash; allows multiple goroutines to read simultaneously (RLock), but writing is exclusive (Lock):</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">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">RWMutex</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Multiple goroutines can hold RLock simultaneously</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sharedData</span><span class="p">[</span><span class="s">&#34;key&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// Write is exclusive</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">sharedData</span><span class="p">[</span><span class="s">&#34;key&#34;</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">99</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span></span></span></code></pre></div><p><strong>Why atomic.Value instead of mutex?</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th><code>sync.Mutex</code></th>
					<th><code>atomic.Value</code></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Best for</td>
					<td>Complex critical sections (multi-step operations)</td>
					<td>Single value read/write</td>
			</tr>
			<tr>
					<td>On read</td>
					<td>Needs Lock/Unlock</td>
					<td>Completely lock-free</td>
			</tr>
			<tr>
					<td>10 goroutines reading frequently</td>
					<td>Lock contention</td>
					<td>No contention, better performance</td>
			</tr>
	</tbody>
</table>
<p>The token scenario: writes are rare (once every 40s), reads are frequent (10 goroutines on every publish) &ndash; <code>atomic.Value</code> is the better choice.</p>
<p><strong>What makes atomic operations work?</strong></p>
<p>&ldquo;Atomic&rdquo; means <strong>indivisible</strong> &ndash; the operation appears to others as either completed or not started, never &ldquo;halfway done.&rdquo;</p>
<p>A normal assignment <code>token = newToken</code> may compile to multiple machine instructions. On a multi-core CPU, another core might read in the middle and get a half-written value (data race).</p>
<p>Atomic operations use special CPU instructions (like x86&rsquo;s <code>CMPXCHG</code>) to guarantee at the hardware level:</p>
<ol>
<li><strong>Indivisibility</strong>: Store is fully written before it becomes visible to other cores</li>
<li><strong>Memory visibility</strong>: adds a memory barrier, ensuring all cores see the latest value instead of getting stuck on a CPU cache</li>
</ol>





<pre tabindex="0"><code>Normal assignment: Core A writes halfway → Core B might read a corrupted value
Atomic:            Core A finishes writing before it is visible → Core B always sees a complete value</code></pre><p>This is why <code>atomic.Value</code> is safe without a mutex &ndash; the guarantee is made at the CPU instruction level, not through software locks.</p>
<p><strong>Why refresh every 40s with a 60s TTL?</strong></p>
<p>The 20s buffer absorbs:</p>
<ul>
<li>Network latency when fetching the new token</li>
<li>The time gap before all goroutines read the new token</li>
</ul>
<p>If you wait until 59s to refresh, any slight delay could cause publishers to send requests with an expired token.</p>
<p><strong>Common cause of goroutine leaks</strong></p>
<p>If a goroutine has no exit mechanism, it runs until the process ends. <code>ctx.Done()</code> is the standard exit signal &ndash; once the context is cancelled, all listening goroutines exit cleanly.</p>
]]></content:encoded></item><item><title>Error &amp; Log by Middleware</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/error-log-by-middleware/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/error-log-by-middleware/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Each request produces exactly one log entry, handled by middleware. Errors are only wrapped at boundaries with third-party code. Logging follows the OpenTelemetry standard so logs can plug into any observability backend.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Four core principles&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Every request gets exactly one log&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of logging in every handler or function, middleware outputs a single structured log entry when the request completes, containing status code, latency, request ID, and other fields. This avoids scattered log entries for the same request that are hard to correlate.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Each request produces exactly one log entry, handled by middleware. Errors are only wrapped at boundaries with third-party code. Logging follows the OpenTelemetry standard so logs can plug into any observability backend.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Four core principles</strong></p>
<p><strong>1. Every request gets exactly one log</strong></p>
<p>Instead of logging in every handler or function, middleware outputs a single structured log entry when the request completes, containing status code, latency, request ID, and other fields. This avoids scattered log entries for the same request that are hard to correlate.</p>
<p><strong>2. Log should be wrapped in a proper way</strong></p>
<p>Logs should be structured (structured logging) &ndash; not <code>fmt.Println(&quot;error:&quot;, err)</code>, but a format with fields:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;level&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;request_id&#34;</span><span class="p">:</span> <span class="s2">&#34;abc-123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;method&#34;</span><span class="p">:</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/orders&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nt">&#34;latency_ms&#34;</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nt">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;calling payment sdk: connection refused&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>3. Only wrap errors on boundaries with 3rd party code</strong></p>
<p>A <strong>boundary</strong> is the seam between your code and a system you do not own &ndash; calling a DB, calling a third-party SDK, calling an external API are all boundaries.</p>
<p>Errors from third parties lack your context, so wrap once at the boundary. Between internal functions, just pass the error up.</p>
<p>Given the chain: <code>Handler -&gt; getOrder() -&gt; queryOrderFromDB()</code>:</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="c1">// Layer 3: DB boundary, the only place to wrap</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">queryOrderFromDB</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Order</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </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="nx">row</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">db</span><span class="p">.</span><span class="nf">Query</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;SELECT * FROM orders WHERE id = ?&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</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="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">        </span><span class="c1">// Wrap at the boundary, describe what operation was happening</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;querying order id=%d: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// Layer 2: internal function, pass up directly, do not re-wrap</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">getOrder</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Order</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </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="nx">order</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">queryOrderFromDB</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">id</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="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="c1">// Just return, no new wrap</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">order</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">// Layer 1: handler, also does not wrap -- let middleware handle the log</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handleGetOrder</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="nx">order</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">getOrder</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">Context</span><span class="p">(),</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">        </span><span class="c1">// Hand off to middleware, do not log here</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">        </span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;internal error&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>If every layer wraps, the error message becomes:</strong></p>





<pre tabindex="0"><code>handleGetOrder: getOrder: queryOrderFromDB: querying order id=42: connection refused</code></pre><p>The repeated function names add no new information &ndash; just noise.</p>
<p><strong>Wrapping only at the boundary is much clearer:</strong></p>





<pre tabindex="0"><code>querying order id=42: connection refused</code></pre><p>The rule: <strong>wrap = add new information. No new information, just return err.</strong></p>
<p><strong>4. OTEL compliant logging in middleware</strong></p>
<p>OTEL = <strong>OpenTelemetry</strong>, the CNCF observability standard that defines a unified format and API for traces, metrics, and logs.</p>
<p>When middleware outputs OTEL-compliant logs, you can plug into any backend (Grafana, Datadog, GCP Cloud Logging) without changing application code.</p>





<pre tabindex="0"><code>Request → Middleware (start timer, inject trace context)
           ↓
         Handler (process logic, pass errors up)
           ↓
         Middleware (at the end, log one entry with status / latency / error)</code></pre><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why not let each handler log on its own?</strong></p>
<p>Problems with scattered logging:</p>
<ul>
<li>The same request might get logged multiple times across functions &ndash; hard to correlate</li>
<li>Inconsistent formats &ndash; hard to filter with queries</li>
<li>Duplicate logs &ndash; more noise, higher cost</li>
</ul>
<p>Middleware-centralized logging means: one request = one log entry = one trace. Searching and debugging become much more intuitive.</p>
<p><strong>Error wrap decision guide</strong></p>
<table>
	<thead>
			<tr>
					<th>Location</th>
					<th>Wrap?</th>
					<th>Reason</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Calling DB / third-party SDK</td>
					<td>Yes</td>
					<td>Their errors lack your context</td>
			</tr>
			<tr>
					<td>Calling another internal service</td>
					<td>Usually yes</td>
					<td>Cross-service boundary</td>
			</tr>
			<tr>
					<td>Passing between internal functions</td>
					<td>No</td>
					<td>Just stacks messages with no new information</td>
			</tr>
	</tbody>
</table>
]]></content:encoded></item><item><title>RBAC Model</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/rbac-model/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/rbac-model/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;RBAC (Role-Based Access Control) is a permissions model where you first define what roles can do, then assign roles to people. K8s uses RBAC to control who can perform which operations on which resources. CD tools use RBAC to control who can deploy to which environment.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Three core elements of RBAC&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Subject (who) → RoleBinding (assignment) → Role (what they can do)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Subject&lt;/strong&gt;: a user, group, or service account (programmatic identity)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role&lt;/strong&gt;: defines a set of permissions (which operations on which resources)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RoleBinding&lt;/strong&gt;: assigns a Role to a Subject&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intuitive analogy: a Role is a job title (engineer, manager), a RoleBinding is the &amp;ldquo;appointment letter,&amp;rdquo; and the Subject is the person being appointed.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>RBAC (Role-Based Access Control) is a permissions model where you first define what roles can do, then assign roles to people. K8s uses RBAC to control who can perform which operations on which resources. CD tools use RBAC to control who can deploy to which environment.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Three core elements of RBAC</strong></p>





<pre tabindex="0"><code>Subject (who)  →  RoleBinding (assignment)  →  Role (what they can do)</code></pre><ul>
<li><strong>Subject</strong>: a user, group, or service account (programmatic identity)</li>
<li><strong>Role</strong>: defines a set of permissions (which operations on which resources)</li>
<li><strong>RoleBinding</strong>: assigns a Role to a Subject</li>
</ul>
<p>Intuitive analogy: a Role is a job title (engineer, manager), a RoleBinding is the &ldquo;appointment letter,&rdquo; and the Subject is the person being appointed.</p>
<hr>
<p><strong>RBAC in Kubernetes</strong></p>
<p>K8s RBAC controls &ldquo;who can do what against the K8s API.&rdquo;</p>
<p><strong>Resources &amp; Verbs</strong></p>





<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="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl">- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</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">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;pods&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;services&#34;</span><span class="p">]</span><span class="w">   </span><span class="c"># resource types</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">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;watch&#34;</span><span class="p">]</span><span class="w">   </span><span class="c"># allowed operations</span></span></span></code></pre></div><p>Common verbs: <code>get</code> <code>list</code> <code>watch</code> <code>create</code> <code>update</code> <code>patch</code> <code>delete</code></p>
<p><strong>Role vs ClusterRole</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Role</th>
					<th>ClusterRole</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Scope</td>
					<td>Single namespace</td>
					<td>Entire cluster</td>
			</tr>
			<tr>
					<td>Best for</td>
					<td>Restrict a team to their own namespace</td>
					<td>Cross-namespace or cluster-level resources (nodes, PVs)</td>
			</tr>
	</tbody>
</table>





<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"># Role: only effective in the production namespace</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nt">metadata</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">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">production</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">name</span><span class="p">:</span><span class="w"> </span><span class="l">pod-reader</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl">- <span class="nt">apiGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">resources</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;pods&#34;</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">verbs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;get&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># RoleBinding: assign this Role to alice</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">RoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nt">metadata</span><span class="p">:</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">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl">- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">User</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">name</span><span class="p">:</span><span class="w"> </span><span class="l">alice</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Role</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">name</span><span class="p">:</span><span class="w"> </span><span class="l">pod-reader</span></span></span></code></pre></div><p><strong>Service Account: identity for programs</strong></p>
<p>Programs running in pods (like a CD tool agent) are not people. K8s uses Service Accounts to give them an identity, then uses RoleBinding to control what they can do:</p>





<pre tabindex="0"><code>CD tool agent (Pod)
  → uses ServiceAccount: octopus-agent
  → RoleBinding → ClusterRole: deploy-permissions
  → can apply manifests, update Deployments</code></pre><hr>
<p><strong>RBAC in CD Tools (Octopus)</strong></p>
<p>The CD tool has its own RBAC layer, controlling &ldquo;who can operate which project / environment&rdquo;:</p>





<pre tabindex="0"><code>Engineer alice
  → belongs to Team: backend-team
  → Team is assigned Role: deployer (can deploy, cannot change project settings)
  → restricted to Environment: dev, staging (cannot touch prod)</code></pre><p>Typical role hierarchy:</p>
<table>
	<thead>
			<tr>
					<th>Role</th>
					<th>Permissions</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Viewer</td>
					<td>Can only view deploy status</td>
			</tr>
			<tr>
					<td>Deployer</td>
					<td>Can trigger deploys</td>
			</tr>
			<tr>
					<td>Project Lead</td>
					<td>Can modify deploy settings</td>
			</tr>
			<tr>
					<td>Admin</td>
					<td>Full control</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why does K8s need RBAC?</strong></p>
<p>The K8s API can do a lot &ndash; delete Pods, modify Secrets, scale Deployments. Without access control, anyone or any program that can connect to the cluster can do anything. RBAC lets you:</p>
<ul>
<li>Restrict devs to only operate within their own namespace</li>
<li>Give CI/CD agents only manifest apply permission, not Secret deletion</li>
<li>Give SRE cluster-level read access without prod write access</li>
</ul>
<p><strong>Principle of Least Privilege</strong></p>
<p>The design philosophy behind RBAC: give each subject only the minimum permissions needed to do its job. If a CD tool agent only needs <code>apply</code>, do not give it <code>delete</code>.</p>
<p>For CD tool background, see the WarpCD vs Octopus Deploy post.</p>
]]></content:encoded></item><item><title>WarpCD vs Octopus Deploy</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/warpcd-vs-octopus-deploy/</link><pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/warpcd-vs-octopus-deploy/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;WarpSpeedCD (internally called WarpCD) is a GitOps pull model &amp;ndash; Git is the single source of truth, and a bot syncs changes to the cluster. Octopus is a push model &amp;ndash; a pipeline actively triggers deploys, with approval and release management handled in the Octopus UI. The fundamental difference is who drives the deploy.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WarpCD (GitOps / Pull model)&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Code PR merged
 ↓
WarpCD bot opens a PR in the K8s repo (updates manifests)
 ↓
Manual review &amp;amp; approve PR
 ↓
PR merged → cluster auto-syncs (pull)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source of truth&lt;/strong&gt;: K8s manifests live in a Git repo; cluster state always follows Git&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;: every deploy is a Git commit, giving you history for free&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approval&lt;/strong&gt;: through the PR review process, consistent with code review&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Octopus Deploy (Push model)&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>WarpSpeedCD (internally called WarpCD) is a GitOps pull model &ndash; Git is the single source of truth, and a bot syncs changes to the cluster. Octopus is a push model &ndash; a pipeline actively triggers deploys, with approval and release management handled in the Octopus UI. The fundamental difference is who drives the deploy.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>WarpCD (GitOps / Pull model)</strong></p>





<pre tabindex="0"><code>Code PR merged
    ↓
WarpCD bot opens a PR in the K8s repo (updates manifests)
    ↓
Manual review &amp; approve PR
    ↓
PR merged → cluster auto-syncs (pull)</code></pre><ul>
<li><strong>Source of truth</strong>: K8s manifests live in a Git repo; cluster state always follows Git</li>
<li><strong>Audit trail</strong>: every deploy is a Git commit, giving you history for free</li>
<li><strong>Approval</strong>: through the PR review process, consistent with code review</li>
</ul>
<p><strong>Octopus Deploy (Push model)</strong></p>





<pre tabindex="0"><code>CI build completes → artifact (image) pushed to registry
    ↓
Octopus detects new version, creates a Release
    ↓
Promotion flow: dev → staging → prod (each stage can have an approval gate)
    ↓
Octopus actively applies manifests to the cluster (push)</code></pre><ul>
<li><strong>Source of truth</strong>: Octopus&rsquo;s Release and Project configuration</li>
<li><strong>Approval</strong>: approval gates configured in the Octopus UI, not through Git PRs</li>
<li><strong>Runbook</strong>: can run operational tasks (DB migration, rollback, smoke test)</li>
</ul>
<p><strong>Core differences</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>WarpCD (GitOps)</th>
					<th>Octopus Deploy</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Deploy driver</td>
					<td>Cluster pulls from Git</td>
					<td>Pipeline pushes to cluster</td>
			</tr>
			<tr>
					<td>Approval location</td>
					<td>Git PR review</td>
					<td>Octopus UI approval gate</td>
			</tr>
			<tr>
					<td>Source of truth</td>
					<td>Git repo (K8s manifests)</td>
					<td>Octopus Release + Git</td>
			</tr>
			<tr>
					<td>Audit trail</td>
					<td>Git commit history</td>
					<td>Octopus deployment log</td>
			</tr>
			<tr>
					<td>Operational tasks</td>
					<td>Needs additional tooling</td>
					<td>Built-in Runbook</td>
			</tr>
			<tr>
					<td>Multi-env promotion</td>
					<td>Via branch / folder structure</td>
					<td>Built-in lifecycle (dev -&gt; stg -&gt; prod)</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>How to do a CD pipeline migration?</strong></p>
<p>Switching from GitOps to Octopus is not just migrating YAML &ndash; it is <strong>replacing the entire deploy driver</strong>:</p>
<ol>
<li><strong>Artifacts stay the same</strong>: container images and Helm charts do not need changes</li>
<li><strong>K8s manifests may be kept</strong>: Octopus can use kubectl/Helm steps to apply existing manifests</li>
<li><strong>What actually migrates is the process</strong>:
<ul>
<li>Replace WarpCD bot&rsquo;s PR flow with Octopus&rsquo;s Release + Promotion</li>
<li>Replace Git PR approval with Octopus&rsquo;s approval gates</li>
<li>Create corresponding Projects + Steps in Octopus for each service&rsquo;s deploy config</li>
</ul>
</li>
</ol>
<p><strong>Typical migration steps</strong></p>





<pre tabindex="0"><code>1. Create environments in Octopus (dev / staging / prod)
2. Create an Octopus Project for each service, configure deploy steps
3. Set up approval gates to replace PR review
4. Run shadow mode (old and new in parallel) to verify results match
5. Once confirmed, turn off the WarpCD bot -- switchover complete</code></pre>]]></content:encoded></item><item><title>GCP PubSub Retry &amp; Pusher</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/gcp-pubsub-retry-pusher/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/gcp-pubsub-retry-pusher/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;GCP Pub/Sub retry is ACK-based &amp;ndash; no ACK means redeliver. With a Pusher in the middle, retry becomes two layers: the Pusher&amp;rsquo;s own retry (fast, finely configurable) + Pub/Sub redelivery (slow, last resort).&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Native Pub/Sub retry mechanism&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After a subscriber pulls a message, it must return an ACK within the ACK deadline. Otherwise Pub/Sub treats it as a failure and automatically redelivers:&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Pub/Sub
 ↓ deliver message
Subscriber
 ↓ success → ACK → message removed from subscription
 ↓ failure / timeout → no ACK → Pub/Sub redelivers&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Redelivery continues until the message is ACKed or exceeds the retention period (default 7 days).&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>GCP Pub/Sub retry is ACK-based &ndash; no ACK means redeliver. With a Pusher in the middle, retry becomes two layers: the Pusher&rsquo;s own retry (fast, finely configurable) + Pub/Sub redelivery (slow, last resort).</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Native Pub/Sub retry mechanism</strong></p>
<p>After a subscriber pulls a message, it must return an ACK within the ACK deadline. Otherwise Pub/Sub treats it as a failure and automatically redelivers:</p>





<pre tabindex="0"><code>Pub/Sub
  ↓ deliver message
Subscriber
  ↓ success → ACK → message removed from subscription
  ↓ failure / timeout → no ACK → Pub/Sub redelivers</code></pre><p>Redelivery continues until the message is ACKed or exceeds the retention period (default 7 days).</p>
<hr>
<p><strong>With a Pusher: two-layer retry</strong></p>
<p>The Pusher sits in between, creating two independent retry layers:</p>





<pre tabindex="0"><code>Pub/Sub Subscription
    ↓ pull
  Pusher
    ↓ push → Target Service</code></pre><p><strong>Layer 1 &ndash; Pusher&rsquo;s own retry (CRD config)</strong></p>
<p>When the Pusher&rsquo;s push to the target service fails, it retries internally first (count and backoff are configurable via CRD) without going back to Pub/Sub.</p>
<p><strong>Layer 2 &ndash; Pub/Sub redelivery</strong></p>
<ul>
<li>If the target service processes successfully -&gt; Pusher ACKs to Pub/Sub -&gt; message done</li>
<li>If all of Pusher&rsquo;s retries are exhausted and it still fails -&gt; NACK to Pub/Sub -&gt; Pub/Sub redelivers to the Pusher</li>
</ul>
<p>So Pub/Sub retry is the <strong>last resort</strong>. Day-to-day transient failures are absorbed by the Pusher layer.</p>
<p><strong>Key: when does the Pusher ACK to Pub/Sub?</strong></p>
<p>The ACK timing determines whether the entire retry chain works correctly:</p>
<table>
	<thead>
			<tr>
					<th>ACK timing</th>
					<th>Result</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>ACK immediately on pull</td>
					<td>Pub/Sub thinks it succeeded; if the target service fails, the message is lost forever</td>
			</tr>
			<tr>
					<td>ACK only after target service succeeds</td>
					<td>Any layer&rsquo;s failure still has a chance to retry</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Dead Letter Topic</strong></p>
<p>When a message fails repeatedly beyond the maximum retry count, instead of letting it loop forever, move it to a dedicated topic for isolation:</p>





<pre tabindex="0"><code>Normal:  Pub/Sub → Pusher → Target Service ✓ → ACK
Failure: Pub/Sub → Pusher → Target Service ✗ → NACK → retry N times
                                                        ↓ exceeds limit
                                               Dead Letter Topic</code></pre><p>Three uses for a Dead Letter Topic:</p>
<ul>
<li><strong>Unblock normal traffic</strong>: problematic messages are moved away, the rest keep flowing</li>
<li><strong>Post-mortem investigation</strong>: see which messages keep failing and why</li>
<li><strong>Manual replay</strong>: after fixing the bug, replay dead letter messages back into the normal flow</li>
</ul>
<p><strong>Benefits of two-layer retry</strong></p>
<p>Pure Pub/Sub retry uses exponential backoff, which is slow. The Pusher layer can use faster, finer-grained retry strategies. Most transient failures get resolved at this layer without going through the full Pub/Sub redelivery cycle.</p>
<p>For the Pusher architecture background, see the gRPC Pusher Pattern post. For Pub/Sub Topic &amp; Subscription basics, see the GCP Pub Sub Topic &amp; Subscription post.</p>
]]></content:encoded></item><item><title>GCP Pub Sub Topic &amp; Subscription</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/gcp-pub-sub-topic-subscription/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/gcp-pub-sub-topic-subscription/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Topic is where messages are published to; subscription is how a consumer receives messages from that topic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A topic is the publishing channel for messages. A subscription is how a consumer subscribes to that channel. Separating the two lets multiple subscribers independently consume from the same topic.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Publisher → Topic → Subscription A → Service A
 → Subscription B → Service B&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Topic&lt;/strong&gt;: the target for publishing. The publisher just sends messages to the topic without caring who reads them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscription&lt;/strong&gt;: a consumption channel attached to a topic. Each subscription receives &lt;strong&gt;an independent copy of every message&lt;/strong&gt; on the topic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each subscription tracks its own progress &amp;ndash; where Service A has read up to and where Service B has read up to are independent. Messages stay in a subscription until they are acknowledged or exceed the retention period.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<blockquote>
<p>Topic is where messages are published to; subscription is how a consumer receives messages from that topic.</p>
</blockquote>
<p>A topic is the publishing channel for messages. A subscription is how a consumer subscribes to that channel. Separating the two lets multiple subscribers independently consume from the same topic.</p>
<h2 id="explanation">Explanation</h2>





<pre tabindex="0"><code>Publisher → Topic → Subscription A → Service A
                 → Subscription B → Service B</code></pre><ul>
<li><strong>Topic</strong>: the target for publishing. The publisher just sends messages to the topic without caring who reads them.</li>
<li><strong>Subscription</strong>: a consumption channel attached to a topic. Each subscription receives <strong>an independent copy of every message</strong> on the topic.</li>
</ul>
<p>Each subscription tracks its own progress &ndash; where Service A has read up to and where Service B has read up to are independent. Messages stay in a subscription until they are acknowledged or exceed the retention period.</p>
<p><strong>Two subscription types</strong></p>
<ul>
<li><strong>Pull</strong>: the subscriber actively asks &ldquo;any new messages?&rdquo; &ndash; suited for backend services that want to control their own consumption rate</li>
<li><strong>Push</strong>: Pub/Sub proactively sends messages to a specified HTTP endpoint &ndash; suited for serverless or webhook scenarios</li>
</ul>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why separate topic and subscription?</strong></p>
<p>If there were only topics, all consumers would compete for the same messages &ndash; once read, they are gone. With subscriptions, the same message can be consumed independently by a logging service, an analytics service, and a notification service without interference. This is the <strong>fan-out</strong> pattern.</p>
<p><strong>What happens if nobody consumes from a subscription?</strong></p>
<p>Messages pile up until the retention period expires (default 7 days), then get auto-deleted. So if you create a subscription, make sure something is consuming from it &ndash; otherwise it is wasted resources.</p>
<p><strong>Topic ownership should follow the domain</strong></p>
<p>The topic should be created by the service that owns the domain. For example, if monolith ServiceA publishes events to a topic belonging to ServiceB&rsquo;s domain, the topic should live in ServiceB&rsquo;s GCP project, with IAM granting ServiceA publish permission:</p>





<pre tabindex="0"><code>ServiceA (monolith)
  │  roles/pubsub.publisher (IAM grant)
  ▼
Topic (owned by ServiceB)
  ▼
Subscription → ServiceC consume</code></pre><p>After migration completes, you only need to remove ServiceA&rsquo;s publish logic &ndash; everything else stays the same.</p>
<p><strong>Subscription ownership design</strong></p>
<p>The subscription owner does not have to match the topic owner. Two options:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Option 1: ServiceB owns subscription</th>
					<th>Option 2: ServiceC owns subscription</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Created by</td>
					<td>ServiceB</td>
					<td>ServiceC (cross-project)</td>
			</tr>
			<tr>
					<td>Control</td>
					<td>ServiceB manages the entire message flow</td>
					<td>ServiceC is fully autonomous</td>
			</tr>
			<tr>
					<td>Best for</td>
					<td>Migration transition, centralized management needed</td>
					<td>ServiceC is independent, no dependency on ServiceB</td>
			</tr>
	</tbody>
</table>
<p>During migration, <strong>Option 1</strong> is recommended &ndash; ServiceB owns both the topic and subscription, then grants ServiceC consume permission. This gives ServiceB full visibility over the message flow.</p>
<p>ServiceC connects to the subscription via IAM:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_pubsub_subscription_iam_member&#34; &#34;subscriber&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  subscription</span> <span class="o">=</span> <span class="k">google_pubsub_subscription</span><span class="p">.</span><span class="k">my_subscription</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  role</span>         <span class="o">=</span> <span class="s2">&#34;roles/pubsub.subscriber&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  member</span>       <span class="o">=</span> <span class="s2">&#34;serviceAccount:service-c@service-c-project.iam.gserviceaccount.com&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">}</span></span></code></pre></div><p>ServiceC&rsquo;s code just needs to pull using its own service account. GCP verifies IAM and grants access &ndash; no extra configuration needed.</p>
<p><strong>Creating Topic and Subscription with Terraform</strong></p>
<p>Like buckets, these are GCP resources that can be managed directly with Terraform:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_pubsub_topic&#34; &#34;my_topic&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>    <span class="o">=</span> <span class="s2">&#34;my-topic&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  project</span> <span class="o">=</span> <span class="s2">&#34;service-b-project&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_pubsub_subscription&#34; &#34;my_subscription&#34;</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">  name</span>  <span class="o">=</span> <span class="s2">&#34;my-subscription&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">  topic</span> <span class="o">=</span> <span class="k">google_pubsub_topic</span><span class="p">.</span><span class="k">my_topic</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">}<span class="c1">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># Grant ServiceA&#39;s service account publish permission
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_pubsub_topic_iam_member&#34; &#34;publisher&#34;</span> {
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">  topic</span>  <span class="o">=</span> <span class="k">google_pubsub_topic</span><span class="p">.</span><span class="k">my_topic</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">  role</span>   <span class="o">=</span> <span class="s2">&#34;roles/pubsub.publisher&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  member</span> <span class="o">=</span> <span class="s2">&#34;serviceAccount:service-a@service-a-project.iam.gserviceaccount.com&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">}</span></span></code></pre></div>]]></content:encoded></item><item><title>Go Generics</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/go-generics/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/go-generics/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Generics (Go 1.18+) let you write type-parameterized functions &amp;ndash; the type is determined at call time, but type checking happens at compile time. This gives you both flexibility and safety.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Without Generics vs with Generics&lt;/strong&gt;&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Without generics: same logic duplicated for each type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sumInts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sumFloats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Or use any, but lose type safety -- errors only show up at runtime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sumAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// With generics: one function, compile-time type safety&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// T = int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// T = float64&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Syntax breakdown&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Generics (Go 1.18+) let you write type-parameterized functions &ndash; the type is determined at call time, but type checking happens at compile time. This gives you both flexibility and safety.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Without Generics vs with Generics</strong></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="c1">// Without generics: same logic duplicated for each type</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">sumInts</span><span class="p">(</span><span class="nx">nums</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">int</span><span class="w"> </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="kd">var</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">nums</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">total</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">sumFloats</span><span class="p">(</span><span class="nx">nums</span><span class="w"> </span><span class="p">[]</span><span class="kt">float64</span><span class="p">)</span><span class="w"> </span><span class="kt">float64</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="kt">float64</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">nums</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">total</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// Or use any, but lose type safety -- errors only show up at runtime</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">sumAny</span><span class="p">(</span><span class="nx">nums</span><span class="w"> </span><span class="p">[]</span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="kt">any</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">// With generics: one function, compile-time type safety</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nx">sum</span><span class="p">[</span><span class="nx">T</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="kt">float64</span><span class="p">](</span><span class="nx">nums</span><span class="w"> </span><span class="p">[]</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="nx">T</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="nx">T</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">nums</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">total</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nf">sum</span><span class="p">([]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">})</span><span class="w">       </span><span class="c1">// T = int</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nf">sum</span><span class="p">([]</span><span class="kt">float64</span><span class="p">{</span><span class="mf">1.1</span><span class="p">,</span><span class="w"> </span><span class="mf">2.2</span><span class="p">})</span><span class="w">  </span><span class="c1">// T = float64</span></span></span></code></pre></div><p><strong>Syntax breakdown</strong></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="w"> </span><span class="nx">ptr</span><span class="p">[</span><span class="nx">T</span><span class="w"> </span><span class="kt">any</span><span class="p">](</span><span class="nx">v</span><span class="w"> </span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">T</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">v</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">//       ^^^^^^  ^ ^</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">//       |       | return type is also T</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">//       |       parameter type is T</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">//       type parameter: T is the name, any is the constraint</span></span></span></code></pre></div><ul>
<li><code>T</code> &ndash; type parameter name, convention is a single uppercase letter</li>
<li><code>any</code> &ndash; constraint, meaning <code>T</code> can be any type</li>
</ul>
<p><strong>Constraints can be narrowed</strong></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="c1">// Only accept numbers</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nx">sum</span><span class="p">[</span><span class="nx">T</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="kt">float64</span><span class="p">](</span><span class="nx">nums</span><span class="w"> </span><span class="p">[]</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="nx">T</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">...</span><span class="w"> </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></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// Only accept types that implement String()</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nx">print</span><span class="p">[</span><span class="nx">T</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Stringer</span><span class="p">](</span><span class="nx">v</span><span class="w"> </span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="p">}</span></span></span></code></pre></div><p><strong>Type inference: usually no need to specify explicitly</strong></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="nf">ptr</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">)</span><span class="w">         </span><span class="c1">// inferred: T = string</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">ptr</span><span class="p">[</span><span class="kt">string</span><span class="p">](</span><span class="s">&#34;hello&#34;</span><span class="p">)</span><span class="w"> </span><span class="c1">// explicit, same result</span></span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Generics vs <code>any</code> vs <code>interface</code></strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th><code>any</code></th>
					<th><code>interface</code> (with methods)</th>
					<th>Generics</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Type resolved at</td>
					<td>Runtime</td>
					<td>Runtime</td>
					<td>Compile time</td>
			</tr>
			<tr>
					<td>Type safety</td>
					<td>No</td>
					<td>Partial</td>
					<td>Yes</td>
			</tr>
			<tr>
					<td>Best for</td>
					<td>Truly type-agnostic code</td>
					<td>Constraining behavior</td>
					<td>Same logic across multiple types</td>
			</tr>
	</tbody>
</table>
<p><strong>When to use Generics?</strong>
When the same logic needs to work across multiple types and you want compile-time type error detection. Pointer helpers and collection operations (map, filter, reduce) are the most common examples.</p>
]]></content:encoded></item><item><title>Go Modern Syntax Fixes</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/go-modern-syntax-fixes/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/go-modern-syntax-fixes/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Go 1.22+ introduced several language-level changes. &lt;code&gt;go fix&lt;/code&gt; can automatically upgrade old patterns to modern ones: loop variable capture, &lt;code&gt;interface{}&lt;/code&gt; to &lt;code&gt;any&lt;/code&gt;, and inline pointer helper functions can all be removed or simplified.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Remove unnecessary loop variable captures&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before Go 1.22, the for loop variable was &lt;strong&gt;shared across all iterations&lt;/strong&gt; at the same memory address. Closures captured the same variable, causing a classic bug:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Before Go 1.22: all goroutines print the same value (the last one)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Old workaround: redeclare inside the loop to force a new variable&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// capture a new variable per iteration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting from Go 1.22, each iteration has an &lt;strong&gt;independent variable&lt;/strong&gt;, making &lt;code&gt;v := v&lt;/code&gt; redundant. &lt;code&gt;go fix&lt;/code&gt; removes it automatically.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Go 1.22+ introduced several language-level changes. <code>go fix</code> can automatically upgrade old patterns to modern ones: loop variable capture, <code>interface{}</code> to <code>any</code>, and inline pointer helper functions can all be removed or simplified.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>1. Remove unnecessary loop variable captures</strong></p>
<p>Before Go 1.22, the for loop variable was <strong>shared across all iterations</strong> at the same memory address. Closures captured the same variable, causing a classic bug:</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="c1">// Before Go 1.22: all goroutines print the same value (the last one)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">items</span><span class="w"> </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="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// Old workaround: redeclare inside the loop to force a new variable</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">items</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">v</span><span class="w">  </span><span class="c1">// capture a new variable per iteration</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Starting from Go 1.22, each iteration has an <strong>independent variable</strong>, making <code>v := v</code> redundant. <code>go fix</code> removes it automatically.</p>
<p><strong>2. Replace <code>interface{}</code> with <code>any</code></strong></p>
<p>Go 1.18 introduced <code>any</code> as a type alias for <code>interface{}</code>. They are identical, but <code>any</code> is shorter:</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="c1">// Old</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">process</span><span class="p">(</span><span class="nx">v</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kd">interface</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></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// New</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">process</span><span class="p">(</span><span class="nx">v</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span></span></span></code></pre></div><p><strong>3. Remove inline pointer helper functions</strong></p>
<p>Taking a pointer to a literal used to require a helper function (because Go does not allow <code>&amp;&quot;hello&quot;</code> directly):</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="c1">// Old workaround</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">strPtr</span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">s</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">intPtr</span><span class="p">(</span><span class="nx">i</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">i</span><span class="w"> </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></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">strPtr</span><span class="p">(</span><span class="s">&#34;Alice&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">age</span><span class="w">  </span><span class="o">:=</span><span class="w"> </span><span class="nf">intPtr</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span></span></span></code></pre></div><p>Modern Go can replace all type-specific helpers with a single generic version, or just declare a variable and take its address:</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="c1">// One generic helper replaces all</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nx">ptr</span><span class="p">[</span><span class="nx">T</span><span class="w"> </span><span class="kt">any</span><span class="p">](</span><span class="nx">v</span><span class="w"> </span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">T</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">v</span><span class="w"> </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></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// Or just declare and reference</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;Alice&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">req</span><span class="p">.</span><span class="nx">Name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">name</span></span></span></code></pre></div><p><strong>What is a literal?</strong></p>
<p>A literal is a fixed value written directly in code &ndash; not a variable, not a computed result, just the value as written:</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="s">&#34;hello&#34;</span><span class="w">        </span><span class="c1">// string literal</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="mi">42</span><span class="w">             </span><span class="c1">// integer literal</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kc">true</span><span class="w">           </span><span class="c1">// boolean literal</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w"> </span><span class="c1">// slice literal</span></span></span></code></pre></div><p>Go does not allow taking a pointer to a literal (<code>&amp;&quot;hello&quot;</code> is a compile error) because literals have no memory address. You must assign to a variable first &ndash; that is why pointer helper functions existed.</p>
<p>For details on generics, see the Go Generics post.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>What is <code>go fix</code>?</strong></p>
<p><code>go fix</code> is an official Go refactoring tool that applies mechanical fixes for each version&rsquo;s breaking changes or language updates. Running it once when upgrading saves a lot of manual edits.</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 fix ./...</span></span></code></pre></div><p><strong>Why is the Go 1.22 loop variable change important?</strong></p>
<p>This bug existed for over ten years and was one of Go&rsquo;s most famous gotchas &ndash; nearly every Go developer hit it at least once. Go 1.22 changed the behavior to match intuition, which is a significant language-level fix.</p>
]]></content:encoded></item><item><title>gRPC Pusher Pattern</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-pusher-pattern/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-pusher-pattern/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;A gRPC Pusher is a message dispatch intermediary &amp;ndash; it pulls messages from a Pub/Sub subscription and proactively pushes them to a target service&amp;rsquo;s gRPC endpoint. This solves the problem where workers cannot control which consumer processes which message.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why is this needed?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The testing pain point with worker-based services: a PRRC (PR Review Copy) environment and master share the same subscription. There is no way to guarantee that a test message will be consumed by the PRRC pod rather than a master worker &amp;ndash; so every test requires manually deploying the commit image to master, which is cumbersome.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>A gRPC Pusher is a message dispatch intermediary &ndash; it pulls messages from a Pub/Sub subscription and proactively pushes them to a target service&rsquo;s gRPC endpoint. This solves the problem where workers cannot control which consumer processes which message.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Why is this needed?</strong></p>
<p>The testing pain point with worker-based services: a PRRC (PR Review Copy) environment and master share the same subscription. There is no way to guarantee that a test message will be consumed by the PRRC pod rather than a master worker &ndash; so every test requires manually deploying the commit image to master, which is cumbersome.</p>
<p>The gRPC Pusher solves this as an intermediary:</p>





<pre tabindex="0"><code>Pub/Sub Subscription
        ↓  pull
   gRPC Pusher (intermediary layer)
        ↓  push (can control which endpoint to route to)
  Target Service (specified gRPC endpoint)</code></pre><p>The Pusher centrally pulls messages, then pushes them to a specified endpoint based on configuration &ndash; giving precise control over PRRC traffic without worrying about master workers stealing messages.</p>
<p><strong>Configured via CRD</strong></p>
<p>The Pusher&rsquo;s behavior is managed through Kubernetes CRD config, which can set:</p>
<ul>
<li><strong>Retry policy</strong>: retry strategy after failures (count, backoff)</li>
<li><strong>Traffic limit</strong>: rate limiting for pushes</li>
</ul>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Public implementations of this pattern</strong></p>
<p>This &ldquo;pull then push&rdquo; message dispatch pattern is common in the industry:</p>
<ul>
<li><strong>Knative Eventing</strong>: pulls from Broker/Channel, pushes to HTTP/gRPC sink endpoints, supports retry and dead letter sink, configured via CRD</li>
<li><strong>Dapr Pub/Sub</strong>: runs as a sidecar, pulls from various pub/sub backends, pushes to application endpoints via gRPC or HTTP</li>
<li><strong>GCP Push Subscription</strong>: GCP&rsquo;s native push mode, sends messages as HTTP POST to a specified endpoint, supports exponential backoff retry</li>
</ul>
<p><strong>Core design concept: decoupling consume and process</strong></p>
<p>Traditional workers couple &ldquo;pulling messages from a subscription&rdquo; and &ldquo;processing messages&rdquo; in the same process, making traffic routing hard to control. The Pusher separates the two:</p>
<ul>
<li>Pusher handles consume (single entry point)</li>
<li>Target service only handles process (can be any endpoint)</li>
</ul>
<p>This is also the core idea behind <strong>Dapr</strong> and <strong>Knative</strong> &ndash; extracting messaging infrastructure out of application logic.</p>
]]></content:encoded></item><item><title>MySQL Foreign Key vs JOIN</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/mysql-foreign-key-vs-join/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/mysql-foreign-key-vs-join/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Foreign keys and JOINs solve two different problems: FK ensures data integrity at the &lt;strong&gt;write&lt;/strong&gt; layer, while JOIN associates data at the &lt;strong&gt;read&lt;/strong&gt; layer. Not having a FK does not prevent JOINs, but you lose the database-level safety net.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Two core functions of a Foreign Key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;shipping_fees&lt;/code&gt; (FK) -&amp;gt; &lt;code&gt;shipping_classes&lt;/code&gt; (PK) as an example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Referential integrity on insert&lt;/strong&gt;: &lt;code&gt;shipping_fees&lt;/code&gt; cannot contain a &lt;code&gt;shipping_class_id&lt;/code&gt; that does not exist in &lt;code&gt;shipping_classes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cascading protection on delete&lt;/strong&gt;: as long as any &lt;code&gt;shipping_fees&lt;/code&gt; row references a &lt;code&gt;shipping_class&lt;/code&gt;, that &lt;code&gt;shipping_class&lt;/code&gt; row cannot be deleted&lt;/li&gt;
&lt;/ol&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- With FK, the database blocks both of these:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shipping_fees&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shipping_class_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ERROR: Cannot add or update a child row: foreign key constraint fails
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shipping_classes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- ERROR: Cannot delete or update a parent row: foreign key constraint fails&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;JOIN does something different&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Foreign keys and JOINs solve two different problems: FK ensures data integrity at the <strong>write</strong> layer, while JOIN associates data at the <strong>read</strong> layer. Not having a FK does not prevent JOINs, but you lose the database-level safety net.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Two core functions of a Foreign Key</strong></p>
<p>Using <code>shipping_fees</code> (FK) -&gt; <code>shipping_classes</code> (PK) as an example:</p>
<ol>
<li><strong>Referential integrity on insert</strong>: <code>shipping_fees</code> cannot contain a <code>shipping_class_id</code> that does not exist in <code>shipping_classes</code></li>
<li><strong>Cascading protection on delete</strong>: as long as any <code>shipping_fees</code> row references a <code>shipping_class</code>, that <code>shipping_class</code> row cannot be deleted</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- With FK, the database blocks both of these:
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">shipping_fees</span><span class="w"> </span><span class="p">(</span><span class="n">shipping_class_id</span><span class="p">,</span><span class="w"> </span><span class="p">...)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">999</span><span class="p">,</span><span class="w"> </span><span class="p">...);</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">-- ERROR: Cannot add or update a child row: foreign key constraint fails
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">DELETE</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">shipping_classes</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">-- ERROR: Cannot delete or update a parent row: foreign key constraint fails</span></span></span></code></pre></div><p><strong>JOIN does something different</strong></p>
<p>JOIN only associates data from two tables at query time. It does not care whether the data is valid:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">sf</span><span class="p">.</span><span class="o">*</span><span class="p">,</span><span class="w"> </span><span class="n">sc</span><span class="p">.</span><span class="n">name</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">shipping_fees</span><span class="w"> </span><span class="n">sf</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">JOIN</span><span class="w"> </span><span class="n">shipping_classes</span><span class="w"> </span><span class="n">sc</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">sf</span><span class="p">.</span><span class="n">shipping_class_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sc</span><span class="p">.</span><span class="n">id</span><span class="p">;</span></span></span></code></pre></div><p>You can JOIN without a FK, but if there are orphan records, the JOIN silently excludes them without raising an error.</p>
<p><strong>Why do DBRE teams dislike FK?</strong></p>
<p>FK causes trouble at the DB operations layer:</p>
<ul>
<li>Emergency data fixes via DML (direct INSERT / DELETE) can be blocked by FK constraints</li>
<li>Large data migrations require temporarily disabling FK checks</li>
<li>FK behavior gets more complex in replication setups</li>
</ul>
<p><strong>The compromise</strong></p>
<p>Keep the JOIN design (no FK), but the DBRE team uses <strong>DML validation</strong> at the operations layer to ensure data correctness &ndash; essentially replacing FK&rsquo;s two protections with a manual process.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>FK vs Application-level validation vs DML validation</strong></p>
<table>
	<thead>
			<tr>
					<th>Layer</th>
					<th>Approach</th>
					<th>Pros</th>
					<th>Cons</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>DB (FK)</td>
					<td>Foreign Key constraint</td>
					<td>Absolute protection, blocks all entry points</td>
					<td>Low operational flexibility, can block during emergencies</td>
			</tr>
			<tr>
					<td>Application</td>
					<td>Validate in code</td>
					<td>High flexibility</td>
					<td>Only protects traffic through the app; bypassing the app means no protection</td>
			</tr>
			<tr>
					<td>DML validation</td>
					<td>Operational process rules</td>
					<td>Best of both worlds</td>
					<td>Relies on manual process, risk of human error</td>
			</tr>
	</tbody>
</table>
<p><strong>The nature of this tradeoff</strong></p>
<p>FK means &ldquo;let the database worry about data correctness.&rdquo; No FK means &ldquo;we worry about it ourselves.&rdquo; The former is safer but sacrifices operational flexibility; the latter is more flexible but shifts responsibility to the application and operations layers.</p>
<p>For high-traffic systems or those with urgent on-call needs, DBRE teams tend to prefer the latter &ndash; they do not want to be stuck on a constraint during a critical moment.</p>
]]></content:encoded></item><item><title>gRPC vs HTTP</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-vs-http/</link><pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-vs-http/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;gRPC is an RPC framework developed by Google. It uses HTTP/2 for transport and Protobuf for serialization, making it faster and more structured than traditional REST/HTTP+JSON &amp;ndash; but less readable, so it is mainly used for internal microservice communication.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Traditional HTTP (REST + JSON)&lt;/strong&gt;&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;Client → POST /users HTTP/1.1
 Content-Type: application/json
 {&amp;#34;name&amp;#34;: &amp;#34;Alice&amp;#34;, &amp;#34;age&amp;#34;: 30}

Server → 200 OK
 {&amp;#34;id&amp;#34;: 1, &amp;#34;name&amp;#34;: &amp;#34;Alice&amp;#34;}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Operations are defined by URL + HTTP method&lt;/li&gt;
&lt;li&gt;Data format is JSON (human-readable text)&lt;/li&gt;
&lt;li&gt;Based on HTTP/1.1 (each request uses an independent connection)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How gRPC does it&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>gRPC is an RPC framework developed by Google. It uses HTTP/2 for transport and Protobuf for serialization, making it faster and more structured than traditional REST/HTTP+JSON &ndash; but less readable, so it is mainly used for internal microservice communication.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Traditional HTTP (REST + JSON)</strong></p>





<pre tabindex="0"><code>Client → POST /users HTTP/1.1
         Content-Type: application/json
         {&#34;name&#34;: &#34;Alice&#34;, &#34;age&#34;: 30}

Server → 200 OK
         {&#34;id&#34;: 1, &#34;name&#34;: &#34;Alice&#34;}</code></pre><ul>
<li>Operations are defined by URL + HTTP method</li>
<li>Data format is JSON (human-readable text)</li>
<li>Based on HTTP/1.1 (each request uses an independent connection)</li>
</ul>
<p><strong>How gRPC does it</strong></p>





<pre tabindex="0"><code>Client → calls UserService.CreateUser(CreateUserRequest)
Server → returns CreateUserResponse</code></pre><ul>
<li>Services and messages are defined in <code>.proto</code> files; calling them feels like calling a local function</li>
<li>Data format is Protobuf (binary, not directly readable)</li>
<li>Based on HTTP/2 (multiplexing &ndash; a single connection handles multiple requests)</li>
</ul>
<p><strong>Core differences</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>REST + JSON</th>
					<th>gRPC</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Protocol</td>
					<td>HTTP/1.1</td>
					<td>HTTP/2</td>
			</tr>
			<tr>
					<td>Data format</td>
					<td>JSON (text)</td>
					<td>Protobuf (binary)</td>
			</tr>
			<tr>
					<td>Schema</td>
					<td>Not enforced</td>
					<td>Enforced by <code>.proto</code></td>
			</tr>
			<tr>
					<td>Performance</td>
					<td>Slower</td>
					<td>Fast (binary + multiplexing)</td>
			</tr>
			<tr>
					<td>Readability</td>
					<td>High, easy to debug</td>
					<td>Low, needs tooling</td>
			</tr>
			<tr>
					<td>Browser support</td>
					<td>Native</td>
					<td>Requires grpc-web</td>
			</tr>
			<tr>
					<td>Best for</td>
					<td>External public APIs</td>
					<td>Internal microservices</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>What is HTTP/2 multiplexing?</strong></p>
<p>In HTTP/1.1, each request must wait for the previous response before sending the next one (or open a new connection). HTTP/2 can handle multiple request/response pairs in parallel over a single connection, significantly reducing latency.</p>
<p><strong>Streaming</strong></p>
<p>gRPC supports four communication modes, which are hard to do with REST:</p>





<pre tabindex="0"><code>Unary:              one request → one response (most common)
Server streaming:   one request → multiple responses (e.g. real-time push)
Client streaming:   multiple requests → one response (e.g. uploading chunked data)
Bidirectional:      multiple requests ↔ multiple responses (e.g. real-time chat)</code></pre><p><strong>Relation to Protobuf</strong>
gRPC&rsquo;s data format is Protobuf &ndash; see the earlier post on Protobuf Reserved Fields &amp; API Versioning for more context.</p>
]]></content:encoded></item><item><title>Accept-Language Header</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/accept-language-header/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/accept-language-header/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Language preference does not need to be a request parameter. HTTP already has the &lt;code&gt;Accept-Language&lt;/code&gt; header for this &amp;ndash; the server reads it directly from the header, and the client does not need to pass an extra field.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What is &lt;code&gt;Accept-Language&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is a request header defined by the HTTP standard (RFC 7231) that tells the server which languages the client prefers. Browsers set it automatically based on the user&amp;rsquo;s system language. API clients can set it manually:&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Language preference does not need to be a request parameter. HTTP already has the <code>Accept-Language</code> header for this &ndash; the server reads it directly from the header, and the client does not need to pass an extra field.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>What is <code>Accept-Language</code>?</strong></p>
<p>It is a request header defined by the HTTP standard (RFC 7231) that tells the server which languages the client prefers. Browsers set it automatically based on the user&rsquo;s system language. API clients can set it manually:</p>





<pre tabindex="0"><code>Accept-Language: zh-TW,zh;q=0.9,en;q=0.8</code></pre><ul>
<li>Multiple languages are separated by commas</li>
<li><code>q</code> is the quality factor, ranging from 0 to 1 (default 1.0) &ndash; higher means more preferred</li>
<li>The example above: prefer <code>zh-TW</code>, then <code>zh</code>, then <code>en</code></li>
</ul>
<p><strong>How does the server read it?</strong></p>
<p>In Go, just get it from the header and parse:</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="nx">lang</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Accept-Language&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// &#34;zh-TW,zh;q=0.9,en;q=0.8&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// Simple approach: take the first one (highest priority)</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">preferred</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="nx">lang</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;,&#34;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="w">  </span><span class="c1">// &#34;zh-TW&#34;</span></span></span></code></pre></div><p>In practice you would use an existing library to parse the full q-value ordering, match against the server&rsquo;s supported languages, and fall back to a default if nothing matches.</p>
<p><strong>Why is this better than a request parameter?</strong></p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Request param <code>?lang=zh-TW</code></th>
					<th><code>Accept-Language</code> header</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Standardization</td>
					<td>Custom format, varies across APIs</td>
					<td>HTTP standard, all clients understand</td>
			</tr>
			<tr>
					<td>Browser support</td>
					<td>Client must add it manually</td>
					<td>Browser sends it automatically</td>
			</tr>
			<tr>
					<td>Semantics</td>
					<td>Mixed in with business parameters</td>
					<td>Clearly belongs to content negotiation</td>
			</tr>
			<tr>
					<td>API cleanliness</td>
					<td>Every endpoint must handle this field</td>
					<td>Handle once in middleware</td>
			</tr>
	</tbody>
</table>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Content Negotiation</strong></p>
<p><code>Accept-Language</code> is part of HTTP content negotiation &ndash; the client tells the server &ldquo;here&rsquo;s what I can accept&rdquo; and the server picks the best match from what it can provide. Related headers in the same family:</p>
<ul>
<li><code>Accept</code>: preferred response format (<code>application/json</code>, <code>text/html</code>)</li>
<li><code>Accept-Encoding</code>: preferred compression (<code>gzip</code>, <code>br</code>)</li>
</ul>
<p><strong>Usually handled in middleware</strong></p>
<p>No need for every handler to read the header itself. Parse it in middleware, put the result in the context, and let downstream handlers read from there:</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="w"> </span><span class="nf">LanguageMiddleware</span><span class="p">(</span><span class="nx">next</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="p">)</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </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="nx">lang</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">parseAcceptLanguage</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;Accept-Language&#34;</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="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nf">Context</span><span class="p">(),</span><span class="w"> </span><span class="s">&#34;lang&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">lang</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">        </span><span class="nx">next</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div>]]></content:encoded></item><item><title>JSON &amp; Marshal Unmarshal</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/json-marshal-unmarshal/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/json-marshal-unmarshal/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Marshal converts an in-memory data structure into a transmittable format (like JSON). Unmarshal does the reverse &amp;ndash; it turns JSON back into a data structure. Most APIs use JSON because it is human-readable, natively supported by JavaScript, and universally available across languages.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Marshal / Unmarshal&lt;/strong&gt;&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ShippingClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;name&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Lang&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;lang&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Unmarshal: JSON string → struct&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;jsonStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`{&amp;#34;name&amp;#34;: &amp;#34;標準配送&amp;#34;, &amp;#34;lang&amp;#34;: &amp;#34;zh&amp;#34;}`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ShippingClass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonStr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// obj.Name == &amp;#34;標準配送&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Marshal: struct → JSON string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// data == {&amp;#34;name&amp;#34;:&amp;#34;標準配送&amp;#34;,&amp;#34;lang&amp;#34;:&amp;#34;zh&amp;#34;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is not limited to JSON &amp;ndash; serialization for XML, YAML, and protobuf is also called marshal/unmarshal. Same concept, different formats.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Marshal converts an in-memory data structure into a transmittable format (like JSON). Unmarshal does the reverse &ndash; it turns JSON back into a data structure. Most APIs use JSON because it is human-readable, natively supported by JavaScript, and universally available across languages.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Marshal / Unmarshal</strong></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">type</span><span class="w"> </span><span class="nx">ShippingClass</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;name&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nx">Lang</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;lang&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// Unmarshal: JSON string → struct</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">jsonStr</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">`{&#34;name&#34;: &#34;標準配送&#34;, &#34;lang&#34;: &#34;zh&#34;}`</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">obj</span><span class="w"> </span><span class="nx">ShippingClass</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">jsonStr</span><span class="p">),</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">obj</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// obj.Name == &#34;標準配送&#34;</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="c1">// Marshal: struct → JSON string</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Marshal</span><span class="p">(</span><span class="nx">obj</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// data == {&#34;name&#34;:&#34;標準配送&#34;,&#34;lang&#34;:&#34;zh&#34;}</span></span></span></code></pre></div><p>This is not limited to JSON &ndash; serialization for XML, YAML, and protobuf is also called marshal/unmarshal. Same concept, different formats.</p>
<p><strong>Why do most APIs use JSON?</strong></p>
<ul>
<li><strong>Human-readable</strong>: you can read it directly when debugging; binary formats like protobuf cannot do this</li>
<li><strong>Native JS support</strong>: browsers parse JSON at zero cost, no extra handling on the frontend</li>
<li><strong>Universal</strong>: almost every language has a mature JSON library</li>
<li><strong>Less verbose than XML</strong>: XML&rsquo;s opening and closing tags add a lot of overhead</li>
</ul>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>What are Go struct tags?</strong></p>
<p>The <code>json:&quot;name&quot;</code> tag tells Go&rsquo;s JSON library which key name this field maps to in JSON. Without a tag, it defaults to the field name (case-sensitive):</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">type</span><span class="w"> </span><span class="nx">Example</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nx">DisplayName</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;display_name&#34;`</span><span class="w">  </span><span class="c1">// JSON key is display_name</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nx">Age</span><span class="w">         </span><span class="kt">int</span><span class="w">    </span><span class="s">`json:&#34;age,omitempty&#34;`</span><span class="w"> </span><span class="c1">// omitempty: omit this key when value is zero</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nx">Internal</span><span class="w">    </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;-&#34;`</span><span class="w">             </span><span class="c1">// never output to JSON</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>Downsides of JSON</strong></p>
<p>JSON is not without problems. In high-performance scenarios (internal microservice communication), protobuf is the usual choice:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>JSON</th>
					<th>Protobuf</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Readability</td>
					<td>Human-readable</td>
					<td>Binary</td>
			</tr>
			<tr>
					<td>Performance</td>
					<td>Slower (string parsing)</td>
					<td>Fast (binary decode)</td>
			</tr>
			<tr>
					<td>Schema</td>
					<td>Not enforced</td>
					<td>Enforced (.proto definition)</td>
			</tr>
			<tr>
					<td>Use case</td>
					<td>External public APIs</td>
					<td>Internal microservices</td>
			</tr>
	</tbody>
</table>
]]></content:encoded></item><item><title>API Request Validation</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/api-request-validation/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/api-request-validation/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;API request validation should happen on both sides, but with different roles: the client provides instant UX feedback, while the server enforces all business logic validation. The server side is mandatory because client validation can be bypassed.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why can&amp;rsquo;t you skip server-side validation?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anyone can skip the frontend and hit the API directly. Client-side validation is just a UX optimization; the server is the real enforcement layer.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>API request validation should happen on both sides, but with different roles: the client provides instant UX feedback, while the server enforces all business logic validation. The server side is mandatory because client validation can be bypassed.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Why can&rsquo;t you skip server-side validation?</strong></p>
<p>Anyone can skip the frontend and hit the API directly. Client-side validation is just a UX optimization; the server is the real enforcement layer.</p>
<p><strong>Cross-field Validation</strong></p>
<p>When fields have logical dependencies (e.g. <code>discount</code> cannot be 0 when <code>eligibility</code> is true), handle it in a <code>Validate()</code> method on the server side:</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">type</span><span class="w"> </span><span class="nx">CreateDiscountRequest</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="nx">Eligibility</span><span class="w"> </span><span class="kt">bool</span><span class="w">    </span><span class="s">`json:&#34;eligibility&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nx">Discount</span><span class="w">    </span><span class="kt">float64</span><span class="w"> </span><span class="s">`json:&#34;discount&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="nx">CreateDiscountRequest</span><span class="p">)</span><span class="w"> </span><span class="nf">Validate</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Eligibility</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Discount</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;discount cannot be 0 when eligibility is true&#34;</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="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Call it in the handler right after decoding:</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">var</span><span class="w"> </span><span class="nx">req</span><span class="w"> </span><span class="nx">CreateDiscountRequest</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">).</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">req</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></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">req</span><span class="p">.</span><span class="nf">Validate</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">(),</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span><span class="w"> </span><span class="c1">// 400</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Should you use a validation library?</strong></p>
<p>For simple cases, a hand-written <code>Validate()</code> is enough. When rules get complex or you have many structs, consider <code>go-playground/validator</code>, which supports struct tags for basic rules:</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">type</span><span class="w"> </span><span class="nx">Request</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="w">  </span><span class="kt">string</span><span class="w">  </span><span class="s">`validate:&#34;required&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nx">Email</span><span class="w"> </span><span class="kt">string</span><span class="w">  </span><span class="s">`validate:&#34;required,email&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nx">Age</span><span class="w">   </span><span class="kt">int</span><span class="w">     </span><span class="s">`validate:&#34;gte=0,lte=130&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>But cross-field logic (dependencies between fields) still needs a custom validator &ndash; no library handles your business logic for you.</p>
<p><strong>Return 400 or 422?</strong></p>
<ul>
<li><code>400 Bad Request</code>: malformed input, parse failure</li>
<li><code>422 Unprocessable Entity</code>: valid format but failed business logic validation (like the cross-field error above)</li>
</ul>
<p>Semantically 422 is more precise, but in practice many APIs use 400 for everything. Follow your team&rsquo;s convention.</p>
]]></content:encoded></item><item><title>BFF 101</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/bff101/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/bff101/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;BFF (Backend For Frontend) is an architecture pattern where you build a dedicated backend layer for each frontend client, instead of having all clients share a single API.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why do you need BFF?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Different clients have very different data needs. Take an e-commerce platform as an example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mobile app: small screen, limited bandwidth, needs only a few compact fields&lt;/li&gt;
&lt;li&gt;Web browser: can show richer data, needs more fields&lt;/li&gt;
&lt;li&gt;Third-party partners: yet another set of data format requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With a single shared API, two problems arise:&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>BFF (Backend For Frontend) is an architecture pattern where you build a dedicated backend layer for each frontend client, instead of having all clients share a single API.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Why do you need BFF?</strong></p>
<p>Different clients have very different data needs. Take an e-commerce platform as an example:</p>
<ul>
<li>Mobile app: small screen, limited bandwidth, needs only a few compact fields</li>
<li>Web browser: can show richer data, needs more fields</li>
<li>Third-party partners: yet another set of data format requirements</li>
</ul>
<p>With a single shared API, two problems arise:</p>
<ul>
<li><strong>Over-fetching</strong>: returning fields the client does not use</li>
<li><strong>Under-fetching</strong>: one request is not enough, so the client sends multiple requests</li>
</ul>
<p>BFF adds a layer between clients and backend microservices:</p>





<pre tabindex="0"><code>Mobile App  →  Mobile BFF  ┐
Web App     →  Web BFF     ├─→  microservices
Partner API →  Partner BFF ┘</code></pre><p>Each BFF handles:</p>
<ol>
<li>Aggregating responses from multiple microservices (one request instead of many)</li>
<li>Trimming data to the format that specific client needs</li>
<li>Client-specific logic (e.g. mobile pagination)</li>
</ol>
<p><strong>Who maintains the BFF?</strong></p>
<p>Usually the frontend team, so they can adjust the API format on their own without waiting for backend changes.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>BFF vs API Gateway</strong></p>
<p>These are easy to confuse, but they serve different purposes:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>BFF</th>
					<th>API Gateway</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Purpose</td>
					<td>Trim data for a specific client</td>
					<td>Traffic routing, auth, rate limiting</td>
			</tr>
			<tr>
					<td>Maintained by</td>
					<td>Frontend team</td>
					<td>Platform / infra team</td>
			</tr>
			<tr>
					<td>Count</td>
					<td>One per client type</td>
					<td>Usually one</td>
			</tr>
	</tbody>
</table>
<p>In practice they can coexist &ndash; the API Gateway sits in front handling common concerns, while BFFs sit behind it doing client-specific data aggregation.</p>
<p><strong>When do you not need BFF?</strong></p>
<ul>
<li>Only one client type (e.g. web only)</li>
<li>The backend is a monolith with nothing to aggregate</li>
<li>The team is small and maintaining multiple BFFs costs more than it saves</li>
</ul>
]]></content:encoded></item><item><title>GitLab CI &amp; Docker-in-Docker</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/gitlab-ci-docker-in-docker/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/gitlab-ci-docker-in-docker/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;docker build&lt;/code&gt; inside GitLab CI does not work out of the box &amp;ndash; the job itself already runs in a container with no Docker daemon. You need &lt;strong&gt;Docker-in-Docker (DinD)&lt;/strong&gt; as a sidecar service to provide the daemon.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Overall structure&lt;/strong&gt;&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;test &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# runs first&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;build &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# runs after (only if all test jobs pass)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Jobs in the same stage run in parallel; stages run sequentially.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Running <code>docker build</code> inside GitLab CI does not work out of the box &ndash; the job itself already runs in a container with no Docker daemon. You need <strong>Docker-in-Docker (DinD)</strong> as a sidecar service to provide the daemon.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Overall structure</strong></p>





<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="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="l">test   </span><span class="w"> </span><span class="c"># runs first</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="l">build  </span><span class="w"> </span><span class="c"># runs after (only if all test jobs pass)</span></span></span></code></pre></div><p>Jobs in the same stage run in parallel; stages run sequentially.</p>
<hr>
<p><strong><code>run_tests</code> job &ndash; typical job structure</strong></p>





<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="nt">run_tests</span><span class="p">:</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">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</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">image</span><span class="p">:</span><span class="w"> </span><span class="l">python:3.11-slim </span><span class="w"> </span><span class="c"># set up container based on this image where job runs</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">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span>- <span class="l">apt-get update &amp;&amp; apt-get install -y make gcc python3-dev</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">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span>- <span class="l">make test</span></span></span></code></pre></div><ul>
<li><code>image</code>: which Docker image to use as the execution environment for this job</li>
<li><code>before_script</code>: runs before every <code>script</code>, good for dependency installation</li>
<li><code>script</code>: the main commands to execute</li>
</ul>
<hr>
<p><strong><code>build_image</code> job &ndash; Docker-specific details</strong></p>





<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="nt">build_image</span><span class="p">:</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">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</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">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker:20.10.16          </span><span class="w"> </span><span class="c"># Docker CLI</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">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span>- <span class="l">docker:20.10.16-dind        </span><span class="w"> </span><span class="c"># Docker daemon (sidecar)</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">variables</span><span class="p">:</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">DOCKER_TLS_CERTDIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/certs&#34;</span><span class="w">   </span><span class="c"># enable TLS for secure communication</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">before_script</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="l">docker login -u $REGISTRY_USER -p $REGISTRY_PASS</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">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span>- <span class="l">docker build -t $IMAGE_NAME:$IMAGE_TAG .</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span>- <span class="l">docker push $IMAGE_NAME:$IMAGE_TAG</span></span></span></code></pre></div><p><strong>Why do you need <code>services: docker:dind</code>?</strong></p>
<p>Each GitLab CI job runs inside a container. That container has no Docker daemon by default, so <code>docker build</code> fails.</p>
<p><code>services</code> is GitLab CI&rsquo;s sidecar mechanism &ndash; it starts an extra container alongside the job container, both on the same network. <code>docker:dind</code> is an image with a built-in Docker daemon, designed for exactly this:</p>





<pre tabindex="0"><code>job container (docker:20.10.16, has CLI)
        ↕ TLS-encrypted communication
sidecar (docker:20.10.16-dind, has daemon)</code></pre><p><strong>What is <code>DOCKER_TLS_CERTDIR: &quot;/certs&quot;</code>?</strong></p>
<p>Here &ldquo;CLI&rdquo; means the side that runs <code>docker build</code> &ndash; <code>docker build</code> itself just sends a request to the daemon via the Docker API, and the daemon does the actual build. TLS protects this API communication:</p>





<pre tabindex="0"><code>job container: docker build ...
     ↕ Docker API over TLS
sidecar: daemon actually runs the build</code></pre><p>Setting <code>DOCKER_TLS_CERTDIR: &quot;/certs&quot;</code> makes DinD auto-generate TLS certificates. The CLI side reads them too, and the two complete a handshake before communicating. Setting it to an empty string <code>&quot;&quot;</code> disables TLS, but that is insecure and not recommended.</p>
<p><strong>How are credentials passed in?</strong></p>
<p><code>$REGISTRY_USER</code> and <code>$REGISTRY_PASS</code> are CI/CD Variables configured in the GitLab project settings. They never appear in the YAML file, avoiding hardcoded secrets.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Top-level <code>variables</code> vs job-level <code>variables</code></strong></p>





<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="nt">variables</span><span class="p">:</span><span class="w">           </span><span class="c"># available to all jobs</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">IMAGE_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">alienmushroom/demo-app</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nt">build_image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">         </span><span class="c"># only this job; can override top-level</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">DOCKER_TLS_CERTDIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/certs&#34;</span></span></span></code></pre></div><p><strong>Does <code>before_script</code> behave differently from <code>script</code> on failure?</strong>
No &ndash; if any command returns a non-zero exit code, the job is marked as failed and subsequent stages do not run.</p>
<p><strong>Keep versions aligned</strong>
<code>image: docker:20.10.16</code> and <code>services: docker:20.10.16-dind</code> must use the same version number. A mismatch between CLI and daemon versions can cause unexpected issues.</p>
]]></content:encoded></item><item><title>Protobuf Reserved Fields &amp; API Versioning</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/protobuf-reserved-fields-api-versioning/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/protobuf-reserved-fields-api-versioning/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;When removing a response field from a protobuf message, you cannot simply delete it &amp;ndash; you must mark it as &lt;code&gt;reserved&lt;/code&gt;. Otherwise, if a future field reuses the same field number, old clients will misinterpret the new field&amp;rsquo;s value as the old field.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why is &lt;code&gt;reserved&lt;/code&gt; needed?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Protobuf identifies data on the wire by &lt;strong&gt;field number&lt;/strong&gt;, not field name. So when you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Originally have &lt;code&gt;string old_field = 3;&lt;/code&gt; in a response message&lt;/li&gt;
&lt;li&gt;Integrate a new external service, the backend logic changes, this field is no longer populated, so you delete it&lt;/li&gt;
&lt;li&gt;Later add &lt;code&gt;int32 new_field = 3;&lt;/code&gt; (reusing number 3)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, clients still using the old proto definition will receive the response and try to read &lt;code&gt;new_field&lt;/code&gt;&amp;rsquo;s value as &lt;code&gt;old_field&lt;/code&gt; &amp;ndash; the type mismatch causes a crash.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>When removing a response field from a protobuf message, you cannot simply delete it &ndash; you must mark it as <code>reserved</code>. Otherwise, if a future field reuses the same field number, old clients will misinterpret the new field&rsquo;s value as the old field.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Why is <code>reserved</code> needed?</strong></p>
<p>Protobuf identifies data on the wire by <strong>field number</strong>, not field name. So when you:</p>
<ol>
<li>Originally have <code>string old_field = 3;</code> in a response message</li>
<li>Integrate a new external service, the backend logic changes, this field is no longer populated, so you delete it</li>
<li>Later add <code>int32 new_field = 3;</code> (reusing number 3)</li>
</ol>
<p>At this point, clients still using the old proto definition will receive the response and try to read <code>new_field</code>&rsquo;s value as <code>old_field</code> &ndash; the type mismatch causes a crash.</p>
<p><strong>Correct approach: mark it as <code>reserved</code></strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">message</span> <span class="nc">MyResponse</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">reserved</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">;</span>                    <span class="c1">// reserve these field numbers from reuse
</span></span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">reserved</span> <span class="s">&#34;old_field&#34;</span><span class="p">,</span> <span class="s">&#34;another&#34;</span><span class="p">;</span>  <span class="c1">// also reserve the field names (prevents accidental reuse in code)
</span></span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="kt">string</span> <span class="n">active_field</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kt">int32</span> <span class="n">other_field</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>With <code>reserved</code> in place, any attempt to reuse those numbers or names will cause a <code>protoc</code> compilation error.</p>
<hr>
<p><strong>What is the relationship between v2alpha and v2beta?</strong></p>
<p>These are API version <strong>stability labels</strong>, following Google AIP (API Improvement Proposals) naming conventions, commonly seen in gRPC / proto package naming:</p>
<table>
	<thead>
			<tr>
					<th>Version</th>
					<th>Meaning</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>v2alpha</code> / <code>v2alpha1</code></td>
					<td>Experimental; may change drastically or be removed at any time; no backward compatibility guarantee</td>
			</tr>
			<tr>
					<td><code>v2beta</code> / <code>v2beta1</code></td>
					<td>Feature is mostly finalized, but details may still change; usually has compatibility commitments but incomplete</td>
			</tr>
			<tr>
					<td><code>v2</code></td>
					<td>Stable release; full backward compatibility guarantee</td>
			</tr>
	</tbody>
</table>
<p>The typical progression is: <code>v2alpha1 -&gt; v2alpha2 -&gt; v2beta1 -&gt; v2beta2 -&gt; v2</code></p>
<p><strong>How it looks in proto files:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Experimental version
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">package</span> <span class="nn">mycompany.myservice.v2alpha1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// Feature-complete but still being polished
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kn">package</span> <span class="nn">mycompany.myservice.v2beta1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// Stable release
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kn">package</span> <span class="nn">mycompany.myservice.v2</span><span class="p">;</span></span></span></code></pre></div><p>Different versioned packages are <strong>completely independent namespaces</strong> that can coexist, allowing old and new clients to each use their corresponding version.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Two ways to write <code>reserved</code>, and they can be separate or combined:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-protobuf" data-lang="protobuf"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">reserved</span> <span class="mi">1</span> <span class="k">to</span> <span class="mi">3</span><span class="p">;</span>       <span class="c1">// contiguous range
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">reserved</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">;</span>      <span class="c1">// individual listing
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">reserved</span> <span class="s">&#34;foo&#34;</span><span class="p">,</span> <span class="s">&#34;bar&#34;</span><span class="p">;</span> <span class="o">//</span> <span class="n">reserve</span> <span class="n">names</span> <span class="p">(</span><span class="n">recommended</span> <span class="k">to</span> <span class="n">do</span> <span class="n">both</span><span class="p">,</span> <span class="n">prevents</span> <span class="n">typo</span> <span class="n">reuse</span><span class="p">)</span></span></span></code></pre></div><p><strong>Difference from <code>deprecated</code>:</strong></p>
<ul>
<li><code>deprecated = true</code> is a human-readable warning telling developers &ldquo;this field is not recommended anymore,&rdquo; but it still works</li>
<li><code>reserved</code> is enforced at compile time &ndash; it completely blocks any reuse</li>
</ul>
<p><strong>Practical mindset when designing new proto contracts:</strong></p>
<ul>
<li>A field number, once released, is forever &ndash; before deleting a field, decide whether it needs <code>reserved</code></li>
<li>Alpha/Beta versions give you room to experiment; once you reach a stable <code>v2</code>, removing or changing fields requires a formal deprecation process</li>
</ul>
<p><strong>Further reading:</strong></p>
<ul>
<li>Google AIP-180: definition of breaking vs non-breaking changes</li>
<li><code>oneof</code> field numbers in protobuf also need <code>reserved</code></li>
<li>Buf (buf.build): a protobuf linter that can automatically detect breaking changes</li>
</ul>
]]></content:encoded></item><item><title>MySQL Lock</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/mysql-lock/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/mysql-lock/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;When a MySQL lock is not properly released, other transactions trying to access the same data keep waiting indefinitely, causing batch jobs to get stuck (blocking).&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;MySQL uses &lt;strong&gt;locks&lt;/strong&gt; to protect data consistency &amp;ndash; when a transaction is reading or writing a row, it acquires a lock on it first, forcing other transactions to queue up and wait.&lt;/p&gt;
&lt;p&gt;Under normal circumstances, locks are automatically released after a transaction &lt;code&gt;COMMIT&lt;/code&gt;s or &lt;code&gt;ROLLBACK&lt;/code&gt;s. But if a transaction gets stuck for some reason (e.g., a connection not properly closed, a process crash, or a long-running query), the lock persists and everything behind it piles up.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>When a MySQL lock is not properly released, other transactions trying to access the same data keep waiting indefinitely, causing batch jobs to get stuck (blocking).</p>
<h2 id="explanation">Explanation</h2>
<p>MySQL uses <strong>locks</strong> to protect data consistency &ndash; when a transaction is reading or writing a row, it acquires a lock on it first, forcing other transactions to queue up and wait.</p>
<p>Under normal circumstances, locks are automatically released after a transaction <code>COMMIT</code>s or <code>ROLLBACK</code>s. But if a transaction gets stuck for some reason (e.g., a connection not properly closed, a process crash, or a long-running query), the lock persists and everything behind it piles up.</p>
<p>In this case, a transaction was holding a lock without finishing. The batch job kept waiting. The DBRE team manually terminated that session with a <code>KILL</code> command, which released the lock and allowed the batch job to resume.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Common lock types:</strong></p>
<ul>
<li><strong>Row lock</strong>: locks only the specific rows; fine-grained; InnoDB&rsquo;s default</li>
<li><strong>Table lock</strong>: locks the entire table; coarse-grained; used by MyISAM</li>
<li><strong>Gap lock</strong>: locks the &ldquo;gaps&rdquo; between index ranges to prevent phantom reads</li>
</ul>
<p><strong>How to find out who is blocking whom:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- Check which locks are currently waiting
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">information_schema</span><span class="p">.</span><span class="n">INNODB_LOCK_WAITS</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></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">-- View all active transactions
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">information_schema</span><span class="p">.</span><span class="n">INNODB_TRX</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">-- Manually terminate a stuck session (what the DBRE team did)
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">KILL</span><span class="w"> </span><span class="o">&lt;</span><span class="n">process_id</span><span class="o">&gt;</span><span class="p">;</span></span></span></code></pre></div><p><strong>Why are batch jobs particularly prone to this?</strong>
Batch jobs typically process large volumes of data with long-running transactions. If another transaction also needs the same set of rows, deadlocks or long blocking are more likely to occur.</p>
<p><strong>Related concepts to explore further:</strong></p>
<ul>
<li>Deadlock vs Blocking: deadlock is mutual waiting (both sides stuck); blocking is one-directional waiting</li>
<li>InnoDB&rsquo;s <code>innodb_lock_wait_timeout</code> setting (auto-abort on timeout)</li>
<li><code>SELECT ... FOR UPDATE</code> explicitly acquires a row lock</li>
</ul>
]]></content:encoded></item><item><title>GOPROXY</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/goproxy/</link><pubDate>Wed, 25 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/goproxy/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;Fetching private Go modules directly from GitHub in CI/CD easily gets rate-limited (403) when there are too many requests. The fix is to set &lt;code&gt;GOPROXY&lt;/code&gt; in your CD yaml to point at an internal Athens proxy server &amp;ndash; Athens caches the modules, so subsequent builds never hit GitHub again.&lt;/p&gt;





&lt;pre tabindex="0"&gt;&lt;code&gt;CI / go build
 |
 Athens (internal proxy, with cache)
 | (only on first fetch)
 GitHub / VCS&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;Go has three environment variables that control how modules are fetched:&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>Fetching private Go modules directly from GitHub in CI/CD easily gets rate-limited (403) when there are too many requests. The fix is to set <code>GOPROXY</code> in your CD yaml to point at an internal Athens proxy server &ndash; Athens caches the modules, so subsequent builds never hit GitHub again.</p>





<pre tabindex="0"><code>CI / go build
      |
   Athens (internal proxy, with cache)
      | (only on first fetch)
   GitHub / VCS</code></pre><h2 id="explanation">Explanation</h2>
<p>Go has three environment variables that control how modules are fetched:</p>
<table>
	<thead>
			<tr>
					<th>Variable</th>
					<th>Example Value</th>
					<th>Controls</th>
					<th>What It Means</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>GOPROXY</code></td>
					<td><code>https://internal-proxy.company.com</code></td>
					<td>Where modules are downloaded from</td>
					<td>Go only fetches from the internal proxy; if the proxy doesn&rsquo;t have it, the build fails (unless you append <code>,direct</code>)</td>
			</tr>
			<tr>
					<td><code>GONOSUMDB</code></td>
					<td><code>company.com/*</code></td>
					<td>Checksum verification source</td>
					<td>Matching modules skip the public <code>sum.golang.org</code> for verification</td>
			</tr>
			<tr>
					<td><code>GOPRIVATE</code></td>
					<td><code>company.com/*</code></td>
					<td>Shorthand for both above</td>
					<td>Simultaneously skips the public proxy and public checksum DB</td>
			</tr>
	</tbody>
</table>
<p><strong>GOPROXY fallback chain</strong></p>
<p><code>GOPROXY</code> supports comma-separated sources &ndash; Go tries them in order:</p>





<pre tabindex="0"><code>GOPROXY=https://internal-proxy.company.com,direct</code></pre><p><code>direct</code> is not another proxy &ndash; it means fetching directly from VCS (GitHub). If the internal proxy doesn&rsquo;t have a particular public module, it falls back to <code>direct</code>.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why can&rsquo;t private modules be sent to <code>sum.golang.org</code>?</strong>
<code>sum.golang.org</code> is a public service. By default, Go sends the module path there for checksum verification. If a private module&rsquo;s path gets sent, it leaks internal repo information. Use <code>GONOSUMDB</code> or <code>GOPRIVATE</code> to exclude them.</p>
<p><strong><code>GOPRIVATE</code> vs setting <code>GOPROXY</code> + <code>GONOSUMDB</code> separately</strong>
<code>GOPRIVATE=company.com/*</code> is equivalent to setting both <code>GONOSUMDB=company.com/*</code> and <code>GONOPROXY=company.com/*</code> (skip proxy, go straight to VCS). If you&rsquo;re using an internal Athens proxy, you usually still need to explicitly set <code>GOPROXY</code>, because <code>GOPRIVATE</code> tells Go &ldquo;don&rsquo;t use a proxy&rdquo; &ndash; which would bypass your Athens instance and hit GitHub directly.</p>
<p><strong>How to pass a GitHub token in Docker builds?</strong>
If your Dockerfile runs <code>go build</code> and needs to fetch private modules, don&rsquo;t use <code>ARG</code> for the token &ndash; <code>ARG</code> values end up in the build history and can be recovered from image layers even after the file is deleted.</p>
<p>The correct approach is Docker BuildKit&rsquo;s secret mount:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">RUN</span> --mount<span class="o">=</span><span class="nv">type</span><span class="o">=</span>secret,id<span class="o">=</span>github_token <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="k">$(</span>cat /run/secrets/github_token<span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl">    go build ./...</span></span></code></pre></div><p>The secret is only visible during this <code>RUN</code> step and is never written to any image layer.</p>
]]></content:encoded></item><item><title>OIDC Federation from GKE to AWS S3</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/oidc-gke-to-aws-s3/</link><pubDate>Tue, 24 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/oidc-gke-to-aws-s3/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;An application deployed on GKE can use OIDC federation to exchange a Kubernetes-issued JWT token for temporary AWS credentials, eliminating the need to hardcode AWS Access Keys.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What OIDC does in this scenario&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OIDC (OpenID Connect) is essentially a protocol for &amp;ldquo;let me prove this identity is real.&amp;rdquo; In this use case, GKE acts as the &lt;strong&gt;OIDC Identity Provider (IdP)&lt;/strong&gt;, and AWS is the party that &lt;strong&gt;trusts what GKE says&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>An application deployed on GKE can use OIDC federation to exchange a Kubernetes-issued JWT token for temporary AWS credentials, eliminating the need to hardcode AWS Access Keys.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>What OIDC does in this scenario</strong></p>
<p>OIDC (OpenID Connect) is essentially a protocol for &ldquo;let me prove this identity is real.&rdquo; In this use case, GKE acts as the <strong>OIDC Identity Provider (IdP)</strong>, and AWS is the party that <strong>trusts what GKE says</strong>.</p>
<p>The overall flow looks like this:</p>





<pre tabindex="0"><code>GKE Pod
  │
  │ 1. Kubernetes automatically mounts a Service Account JWT token on the Pod
  │    (this token contains the workload&#39;s identity info, signed by GKE)
  │
  ▼
AWS STS (AssumeRoleWithWebIdentity)
  │
  │ 2. Submit the JWT token to AWS STS
  │    AWS validates the token against GKE&#39;s OIDC endpoint
  │
  ▼
Temporary AWS credentials (Access Key + Secret + Session Token)
  │
  │ 3. Use temporary credentials to operate on S3
  │
  ▼
S3 Bucket ✓</code></pre><pre class="mermaid">sequenceDiagram
    participant Pod as GKE Pod
    participant STS as AWS STS
    participant OIDC as GKE OIDC
    participant S3

    Note over Pod: K8s mounts JWT token
    Pod->>STS: AssumeRoleWithWebIdentity(JWT)
    STS->>OIDC: Verify signature
    OIDC-->>STS: OK
    STS-->>Pod: Temporary credentials (with TTL)
    Pod->>S3: Upload CSV
    S3-->>Pod: 200 OK
</pre>

<p><strong>What needs to be configured in Terraform</strong></p>
<ol>
<li><strong>AWS IAM OIDC Provider</strong> &ndash; tells AWS &ldquo;I trust tokens issued by this GKE cluster&rdquo;; requires the GKE OIDC issuer URL and thumbprint</li>
<li><strong>AWS IAM Role + Trust Policy</strong> &ndash; defines which GKE service account can assume this role; the condition typically requires the <code>sub</code> claim to match <code>system:serviceaccount:&lt;namespace&gt;:&lt;ksa-name&gt;</code></li>
<li><strong>S3 Permission</strong> &ndash; attach the S3 read/write policy to this IAM Role</li>
</ol>
<p><strong>Go STS package upgrade</strong></p>
<p>In the Go application, use <code>AssumeRoleWithWebIdentity</code>, passing in the JWT token path read from the Pod (typically <code>/var/run/secrets/kubernetes.io/serviceaccount/token</code>). After exchanging for temporary credentials, initialize the S3 client. The package upgrade was mainly to ensure compatibility with SDK v2&rsquo;s credential provider interface.</p>
<h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Why not just use AWS Access Keys?</strong>
Hardcoding keys has rotation issues, leakage risks, and makes it hard to audit which workload is accessing what. OIDC federation&rsquo;s temporary credentials have a TTL and expire automatically, making them significantly more secure.</p>
<p><strong>What&rsquo;s inside the JWT token?</strong>
The token issued by GKE is a standard JWT. The payload contains:</p>
<ul>
<li><code>iss</code>: OIDC issuer (the GKE cluster&rsquo;s URL)</li>
<li><code>sub</code>: <code>system:serviceaccount:&lt;namespace&gt;:&lt;name&gt;</code></li>
<li><code>exp</code>: expiration time</li>
</ul>
<p>When AWS STS receives the token, it fetches the public key from the <code>iss</code> URL&rsquo;s <code>/.well-known/openid-configuration</code> endpoint to verify the signature.</p>
<p><strong>How is the JWT token mounted into the Pod?</strong>
This is a built-in Kubernetes mechanism &ndash; every Pod automatically gets a Service Account token mounted at creation, no extra config needed. However, the default token doesn&rsquo;t specify an <code>audience</code>, which AWS STS won&rsquo;t accept. So you need to mount a dedicated one using a projected volume in the Pod spec:</p>





<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="nt">volumes</span><span class="p">:</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">name</span><span class="p">:</span><span class="w"> </span><span class="l">aws-token</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">projected</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">sources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">        </span>- <span class="nt">serviceAccountToken</span><span class="p">:</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">audience</span><span class="p">:</span><span class="w"> </span><span class="l">sts.amazonaws.com</span><span class="w"> </span><span class="c"># here!</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">expirationSeconds</span><span class="p">:</span><span class="w"> </span><span class="m">3600</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">path</span><span class="p">:</span><span class="w"> </span><span class="l">token</span></span></span></code></pre></div><p>Terraform only handles the AWS side (OIDC Provider, IAM Role); the token mounting itself is done by Kubernetes.</p>
<p><strong>The official name for this pattern: Workload Identity Federation</strong>
GCP&rsquo;s own Workload Identity uses the same principle, just with GCP resources. For cross-cloud scenarios (GKE -&gt; AWS), OIDC serves as the universal standard that bridges them.</p>
]]></content:encoded></item><item><title>Go Context Cancel Propagation</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/go-context-cancel-propagation/</link><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/go-context-cancel-propagation/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;An optional external call should not share a cancellable context &amp;ndash; if that call errors and triggers cancel, the entire context is cancelled, affecting operations that shouldn&amp;rsquo;t be impacted.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;Scenario from a PR review:&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ❌ Problematic version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetCampaign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Campaign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// campaign components are optional&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FetchComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// defer cancel() fires here, ctx is cancelled&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all subsequent operations using the same ctx are affected&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doSomethingElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem: &lt;code&gt;FetchComponents&lt;/code&gt; fails -&amp;gt; returns error -&amp;gt; &lt;code&gt;defer cancel()&lt;/code&gt; executes -&amp;gt; this &lt;code&gt;ctx&lt;/code&gt; is cancelled. If this ctx is shared elsewhere, everything blows up together.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>An optional external call should not share a cancellable context &ndash; if that call errors and triggers cancel, the entire context is cancelled, affecting operations that shouldn&rsquo;t be impacted.</p>
<h2 id="explanation">Explanation</h2>
<p>Scenario from a PR review:</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="c1">// ❌ Problematic version</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Service</span><span class="p">)</span><span class="w"> </span><span class="nf">GetCampaign</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Campaign</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </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="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">ctx</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="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="c1">// campaign components are optional</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nx">components</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nf">FetchComponents</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </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="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">  </span><span class="c1">// defer cancel() fires here, ctx is cancelled</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="p">}</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="c1">// all subsequent operations using the same ctx are affected</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">doSomethingElse</span><span class="p">(</span><span class="nx">ctx</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="o">...</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>The problem: <code>FetchComponents</code> fails -&gt; returns error -&gt; <code>defer cancel()</code> executes -&gt; this <code>ctx</code> is cancelled. If this ctx is shared elsewhere, everything blows up together.</p>
<p>Since campaign components are <strong>optional</strong>, the correct approach is to absorb the error from this call and not propagate it upward:</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="c1">// ✅ Correct version: optional call errors don&#39;t affect the main flow</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Service</span><span class="p">)</span><span class="w"> </span><span class="nf">GetCampaign</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Campaign</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </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="c1">// don&#39;t create a cancel context here</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nx">components</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nf">FetchComponents</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">        </span><span class="c1">// optional -- log and continue, don&#39;t return error</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span><span class="nx">log</span><span class="p">.</span><span class="nf">Warn</span><span class="p">(</span><span class="s">&#34;failed to fetch optional components&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;err&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</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="nx">components</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="p">}</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="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nf">doSomethingElse</span><span class="p">(</span><span class="nx">ctx</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="o">...</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>Context cancellation propagation direction</strong></p>
<p>Cancellation only propagates from parent to child, never in reverse. However, <code>cancel()</code> directly cancels the ctx you created &ndash; if other code also holds a reference to this ctx (or child contexts derived from it), they all get cancelled together.</p>
<p><strong>If you need to fully isolate an optional call (unaffected by parent cancellation)</strong></p>
<p>Go 1.21+ has <code>context.WithoutCancel()</code>:</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="c1">// create a ctx that doesn&#39;t inherit the parent&#39;s cancel signal</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">detachedCtx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithoutCancel</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">components</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">s</span><span class="p">.</span><span class="nx">client</span><span class="p">.</span><span class="nf">FetchComponents</span><span class="p">(</span><span class="nx">detachedCtx</span><span class="p">)</span></span></span></code></pre></div><p>This way, even if the parent request is cancelled (e.g. client disconnects), this optional call still runs to completion.</p>
<p><strong>When to use which approach:</strong></p>
<table>
	<thead>
			<tr>
					<th>Scenario</th>
					<th>Approach</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Optional call error shouldn&rsquo;t affect main flow</td>
					<td>Absorb the error, don&rsquo;t return it</td>
			</tr>
			<tr>
					<td>Optional call shouldn&rsquo;t be affected by parent cancellation</td>
					<td><code>context.WithoutCancel()</code></td>
			</tr>
			<tr>
					<td>Optional call needs its own timeout</td>
					<td><code>context.WithTimeout(context.WithoutCancel(ctx), ...)</code></td>
			</tr>
	</tbody>
</table>
]]></content:encoded></item><item><title>About</title><link>https://kinoko-tech-blog-theta.vercel.app/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/about/</guid><description>&lt;p&gt;Hey there. This is Kinoko. I&amp;rsquo;m a backend engineer with experience in logistics systems, working primarily with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Languages:&lt;/strong&gt; Go, PHP, Python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure:&lt;/strong&gt; GCP, Terraform, Kubernetes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Domain:&lt;/strong&gt; Logistics / E-commerce&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m currently based in Japan and &lt;strong&gt;open to backend engineering opportunities in New Zealand&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This blog collects my TIL notes &amp;ndash; small discoveries worth remembering, and occasional longer technical posts.&lt;/p&gt;
&lt;p&gt;When I am AFK, you can find me either bouldering or skiing or observing (real) bugs in the mountains.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Hey there. This is Kinoko. I&rsquo;m a backend engineer with experience in logistics systems, working primarily with:</p>
<ul>
<li><strong>Languages:</strong> Go, PHP, Python</li>
<li><strong>Infrastructure:</strong> GCP, Terraform, Kubernetes</li>
<li><strong>Domain:</strong> Logistics / E-commerce</li>
</ul>
<p>I&rsquo;m currently based in Japan and <strong>open to backend engineering opportunities in New Zealand</strong>.</p>
<p>This blog collects my TIL notes &ndash; small discoveries worth remembering, and occasional longer technical posts.</p>
<p>When I am AFK, you can find me either bouldering or skiing or observing (real) bugs in the mountains.</p>
<h3 id="what-im-trying-to-do-more">What I’m trying to do more</h3>
<ul>
<li>keeping my dopamine at a stable level</li>
<li>being wary of structural inequality</li>
<li><a href="https://www.jstor.org/stable/j.ctvc77bcc">discovering mushrooms in the gap of the capitalist world</a>.</li>
</ul>
<h3 id="what-im-trying-to-avoid">What I’m trying to avoid</h3>
<ul>
<li>inaccurate definitions</li>
<li>an overdose of vocabulary and theory</li>
<li>being satisfied with being a permanent dilettante</li>
</ul>
]]></content:encoded></item></channel></rss>