<?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>Grpc on Kinoko's TIL Log</title><link>https://kinoko-tech-blog-theta.vercel.app/tags/grpc/</link><description>Recent content in Grpc 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>Sun, 12 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://kinoko-tech-blog-theta.vercel.app/tags/grpc/index.xml" rel="self" type="application/rss+xml"/><item><title>gRPC Metadata vs Proto Fields for HTTP Headers</title><link>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-metadata-vs-proto-fields-for-http-headers/</link><pubDate>Sun, 12 Apr 2026 00:00:00 +0000</pubDate><author>pippimotta@gmail.com (Kinoko)</author><guid>https://kinoko-tech-blog-theta.vercel.app/posts/grpc-metadata-vs-proto-fields-for-http-headers/</guid><description>&lt;h2 id="the-point"&gt;The Point&lt;/h2&gt;
&lt;p&gt;When converting a partner&amp;rsquo;s HTTP API to proto, standard HTTP headers (&lt;code&gt;Content-Length&lt;/code&gt;, etc.) should not go into the message &amp;ndash; the gRPC framework handles them automatically. Custom headers (like &lt;code&gt;amzn-request-id&lt;/code&gt;) go either in a response message field or in gRPC trailing metadata, depending on their purpose.&lt;/p&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Background: converting an Excel API spec to proto&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The partner&amp;rsquo;s API doc (Japanese-style Excel format) lists HTTP response headers and custom headers together. When converting to proto, you need to decide which ones belong in the message and which do not.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="the-point">The Point</h2>
<p>When converting a partner&rsquo;s HTTP API to proto, standard HTTP headers (<code>Content-Length</code>, etc.) should not go into the message &ndash; the gRPC framework handles them automatically. Custom headers (like <code>amzn-request-id</code>) go either in a response message field or in gRPC trailing metadata, depending on their purpose.</p>
<h2 id="explanation">Explanation</h2>
<p><strong>Background: converting an Excel API spec to proto</strong></p>
<p>The partner&rsquo;s API doc (Japanese-style Excel format) lists HTTP response headers and custom headers together. When converting to proto, you need to decide which ones belong in the message and which do not.</p>
<p><strong>Standard HTTP Headers &ndash; do not put in proto</strong></p>
<p>Transport-layer headers like <code>Content-Length</code>, <code>Content-Type</code>, and <code>Transfer-Encoding</code> are handled by the gRPC framework:</p>
<ul>
<li><code>Content-Type</code> is always <code>application/grpc</code></li>
<li><code>Content-Length</code> is determined by serialized message size, filled in automatically by the framework</li>
<li>Putting them in proto only causes semantic confusion (is this a business field or an HTTP header?)</li>
</ul>
<p><strong>Custom Headers (e.g. <code>amzn-request-id</code>) &ndash; two approaches</strong></p>
<table>
	<thead>
			<tr>
					<th>Approach</th>
					<th>Pros</th>
					<th>Cons</th>
					<th>Best for</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Response message field</td>
					<td>Simple, consumer reads directly</td>
					<td>Header semantics flattened into message</td>
					<td>IDs with business/tracing meaning</td>
			</tr>
			<tr>
					<td>gRPC Trailing Metadata</td>
					<td>Follows gRPC idiom, does not pollute message</td>
					<td>Consumer needs to use the metadata API</td>
					<td>Pure infra use (rate limit, etc.)</td>
			</tr>
	</tbody>
</table>
<p><strong>Go server-side trailing metadata example</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="kn">import</span><span class="w"> </span><span class="s">&#34;google.golang.org/grpc/metadata&#34;</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="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">Server</span><span class="p">)</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">req</span><span class="w"> </span><span class="o">*</span><span class="nx">pb</span><span class="p">.</span><span class="nx">GetOrderRequest</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">pb</span><span class="p">.</span><span class="nx">GetOrderResponse</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"> 4</span><span class="cl"><span class="w">    </span><span class="c1">// ... call partner API ...</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">// Put amzn-request-id in trailing metadata</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">trailer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">metadata</span><span class="p">.</span><span class="nf">Pairs</span><span class="p">(</span><span class="s">&#34;amzn-request-id&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">partnerResp</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;amzn-request-id&#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="nx">grpc</span><span class="p">.</span><span class="nf">SetTrailer</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">trailer</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></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="o">&amp;</span><span class="nx">pb</span><span class="p">.</span><span class="nx">GetOrderResponse</span><span class="p">{</span><span class="nx">Order</span><span class="p">:</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">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>Add proto comments to preserve traceability</strong></p>
<p>Even if you do not put all headers into the message, you can document the mapping in proto comments for future reference:</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">GetOrderResponse</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// Maps to amzn-request-id response header in partner API.
</span></span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="c1">// Passed as gRPC trailing metadata, not as a message field.
</span></span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="n">Order</span> <span class="n">order</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><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">// Note: Standard HTTP headers (Content-Length, Content-Type) are intentionally
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="o">//</span> <span class="n">omitted</span> <span class="err">—</span> <span class="n">handled</span> <span class="n">by</span> <span class="n">gRPC</span> <span class="n">framework</span> <span class="n">automatically.</span></span></span></code></pre></div><h2 id="knowledge-sugar">Knowledge Sugar</h2>
<p><strong>The &ldquo;shallow mapping first&rdquo; logic from a colleague</strong></p>
<p>Map all fields first for traceability, then prune later. This makes sense from a traceability perspective, but once a proto is published there is a risk of breaking changes. A better compromise is to document the mapping in comments rather than putting every header into the message.</p>
<p><strong>What is gRPC Metadata?</strong></p>
<p>gRPC metadata is a set of key-value pairs, equivalent to HTTP headers in the gRPC world. It comes in two flavors:</p>
<ul>
<li><strong>Header metadata</strong>: sent before the response starts (<code>grpc.SendHeader</code>)</li>
<li><strong>Trailing metadata</strong>: sent after the response ends (<code>grpc.SetTrailer</code>), more commonly used for carrying additional information</li>
</ul>
]]></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>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>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>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></channel></rss>