<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[GGICCI]]></title><description><![CDATA[Sharing my tech knowledge, daily life and thoughts.]]></description><link>https://ggicci.me/</link><image><url>https://ggicci.me/favicon.png</url><title>GGICCI</title><link>https://ggicci.me/</link></image><generator>Ghost 5.85</generator><lastBuildDate>Sun, 15 Feb 2026 04:36:00 GMT</lastBuildDate><atom:link href="https://ggicci.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Timestamps in API Design]]></title><description><![CDATA[This article tackles two common API design questions: how to model time fields and how to format them. It also introduces an input → process → output pattern for handling timestamps safely in code.]]></description><link>https://ggicci.me/timestamps/</link><guid isPermaLink="false">6801b5f2b0e25f0001346b45</guid><category><![CDATA[best-practices]]></category><category><![CDATA[timestamps]]></category><category><![CDATA[api-design-principals]]></category><category><![CDATA[timestamps-in-api-design]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Fri, 18 Apr 2025 04:02:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1439754389055-9f0855aa82c2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDI3fHx0aW1lfGVufDB8fHx8MTc0NDk3NDQxMnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1439754389055-9f0855aa82c2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDI3fHx0aW1lfGVufDB8fHx8MTc0NDk3NDQxMnww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Timestamps in API Design"><p>I&#x2019;m writing this because what follows should be obvious to any seasoned developer:</p><ol><li>Should time fields be modeled as a dedicated <code>Timestamp</code> type or as a primitive <code>long</code>?</li><li>Should API time values be exposed as raw epoch integers or as RFC3339&#x2011;formatted strings?</li></ol><hr><h2 id="the-answers">The Answers</h2><h3 id="timestamps-should-be-modeled-as-a-dedicated-type-not-a-long">Timestamps should be modeled as a dedicated type, not a long.</h3><p>Using a primitive like <code>long</code> or <code>int64</code> to represent a timestamp might seem easy and performant&#x2014;but in most cases, it&#x2019;s a bad idea. It&#x2019;s <strong>error-prone, opaque</strong>, and worst of all, that performance &#x201C;gain&#x201D; is rarely something your application actually needs.</p><p>A raw <code>long</code> gives <strong>no context</strong>. Is it seconds? Milliseconds? Nanoseconds? Since epoch? From some custom epoch? Why should you&#x2013; or anyone else&#x2013;have to keep asking these tedious questions? It&#x2019;s a timestamp&#x2014;<strong>use a type that makes that explicit</strong>!</p><p>There&apos;s also <strong>no type safety</strong>. It&#x2019;s easy for developers to accidentally assign unrelated numeric values, or confuse durations with points in time.</p><p>And then there&#x2019;s functionality: you can&#x2019;t easily perform date/time operations on a <code>long</code>. A proper time type usually comes with everything you need&#x2014;comparison, formatting, arithmetic, etc.</p><p>However, some still might argue, &#x201C;I don&#x2019;t need all that&#x2014;I just want to get the difference in seconds between two time points.&#x201D; Fair enough. But doesn&#x2019;t a timestamp type give you that too, plus a lot more?</p><p><strong>Sure, there are exceptions.</strong> For instance, if you&apos;re building a database or a low-level storage engine, modeling timestamps with <code>long</code> might make sense for efficiency. But outside of those rare cases, reach for a real timestamp type. Your future self&#x2014;and your teammates&#x2014;will thank you.</p><h3 id="rfc3339-for-time-values-instead-of-epoch-in-apis">RFC3339 for time values instead of epoch in APIs.</h3><p>I&#x2019;m not talking about binary protocols or compact serialization formats. Don&#x2019;t argue on that. I&#x2019;m talking about <strong>APIs</strong>&#x2014;typically text-based formats like JSON, XML, etc.</p><p>Timestamps in APIs should be <strong>human-readable</strong> and <strong>unambiguous</strong>. That&#x2019;s <a href="https://www.rfc-editor.org/rfc/rfc3339.html?ref=ggicci.me#section-5.2">what RFC3339 gives us</a>.</p><p>Epochs are not readable&#x2014;<code>1681724411</code> means nothing at a glance. They&apos;re also ambiguous: is that seconds? Milliseconds? Something else?</p><p><strong>RFC3339 is a standard</strong>. It&apos;s a widely adopted format (2006-01-02T15:04:05Z), easily parsed by both humans and machines, and supported almost in every language and API tookit.</p><p>If you&apos;re concerned about performance or payload size, optimize elsewhere. <strong>Don&#x2019;t sacrifice clarity and interoperability.</strong></p><p>Some might argue, &#x201C;We don&#x2019;t need to look at API responses directly. Who cares about readability? Epoch is more performant. Why optimize for clarity?&#x201D;</p><p>Frankly, I don&#x2019;t even want to engage with that line of reasoning. It misses the point entirely. APIs are not just for machines&#x2014;they&#x2019;re for developers too. Readability, debuggability, and long-term maintainability <strong>do</strong> matter.</p><h2 id="best-practices-for-handling-timestamps-in-your-program-%E2%80%93-the-input-%E2%86%92-process-%E2%86%92-output-pattern">Best Practices for Handling Timestamps in Your Program &#x2013; The input &#x2192; process &#x2192; output Pattern</h2><p>When dealing with a timestamp, it should be divided into 3 phases.</p><ul><li><strong>Input phase</strong>: Parse raw timestamp values (e.g. from API requests or file inputs) into proper time objects&#x2014;like <code>java.time.Instant</code> in Java or <code>time.Time</code> in Go. <strong>Additionally, time objects should be timezone-aware. Time types without timezone information should be forbidden&#x2014;they&apos;re a breeding ground for bugs and confusion.</strong></li><li><strong>Processing phase</strong>: Use these time objects throughout your program logic. This gives you type safety, clarity, and access to useful date/time operations.</li><li><strong>Output phase</strong>: When producing output (e.g. an API response), format the timestamp as needed. For text-based APIs, RFC3339 is the go-to choice for readability and interoperability.</li></ul><p>Actually, this practice also applies to any other data types that involve conversions&#x2014;for instance, URLs, string encodings, and so on.</p><p>I&#x2019;ve lost count of how many times I&#x2019;ve seen developers mess up URLs by treating them like plain strings. Here&#x2019;s what I mean:</p><pre><code>base_url = &quot;https://example.com/search&quot;
query = &quot;apple&quot;
url = base_url + &quot;?q=&quot; + query + &quot;&amp;sort=asc&quot;</code></pre><p>I&#x2019;m not saying you should <em>never</em> build a URL by concatenating strings. In some simple, controlled cases, it&#x2019;s perfectly fine. But it should be done with caution&#x2014;and only when the structure is dead simple and the parameters are guaranteed to be safe. For anything dynamic or user-controlled, use proper URL utilities.</p><p>Following the pattern above, you should parse the raw URL string into a proper URL object as part of the input phase. From there on, treat it like what it is: a structured object. Use the provided methods to inspect or modify it&#x2014;don&#x2019;t fall back to string hacks.</p><h3 id="why-this-input-%E2%86%92-process-%E2%86%92-output-methodology-matters">Why this &quot;input &#x2192; process &#x2192; output&quot; methodology matters?</h3><p>This approach works for any kind of data that needs conversion, like timestamps (<code>Instant</code>), encoded strings, file paths, and more.</p><p>By <strong>centralizing conversions into the input and output phases</strong>, you get these advantages:</p><ul><li><strong>Cleaner code</strong>: your main logic deals with proper types.</li><li><strong>Fewer bugs</strong>: invalid data gets caught early, and you avoid bugs by using the proper functions (typically provided with the types).</li><li><strong>No duplicate work</strong>: you parse once, use many times in the entire program. </li><li><strong>Better separation</strong>: input/output is handled at the edge, business logic stays focused.</li></ul><h2 id="closing-thoughts"><strong>Closing Thoughts</strong></h2><p>These debates aren&apos;t about preference&#x2014;they&apos;re about <strong>clarity, safety, and long-term maintainability</strong>. We should aim for conventions that prevent bugs and help other developers (and future you) understand what&#x2019;s going on at a glance.</p><p>Let&#x2019;s stop reinventing the wheel and adopt what the industry has already learned through decades of hard lessons.</p>]]></content:encoded></item><item><title><![CDATA[goplay: Embed Go Playground on your Website]]></title><description><![CDATA[<p>This post is introducing a way to run Go snippets on websites. Which leveraged the official Go Playground service at <a href="https://go.dev/play/?ref=ggicci.me">https://go.dev/play/</a>.</p><h2 id="demo">Demo</h2><p>Let&#x2019;s take a quick look at the two demos below. You can click the <code>Run</code> button to see the output of the corresponding</p>]]></description><link>https://ggicci.me/goplay-embed-go-playground-on-your-website/</link><guid isPermaLink="false">6349263d12464b000110b095</guid><category><![CDATA[goplay]]></category><category><![CDATA[hugo]]></category><category><![CDATA[go-playground]]></category><category><![CDATA[go]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Tue, 10 May 2022 16:29:00 GMT</pubDate><media:content url="https://ggicci.me/content/images/2022/10/goplay-cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://ggicci.me/content/images/2022/10/goplay-cover.png" alt="goplay: Embed Go Playground on your Website"><p>This post is introducing a way to run Go snippets on websites. Which leveraged the official Go Playground service at <a href="https://go.dev/play/?ref=ggicci.me">https://go.dev/play/</a>.</p><h2 id="demo">Demo</h2><p>Let&#x2019;s take a quick look at the two demos below. You can click the <code>Run</code> button to see the output of the corresponding program. Click <code>Share</code> button to open the code in the official Go Playground to further edit/test the code on your hand.</p><h3 id="demo-1-hello-world">Demo 1. Hello World</h3><pre><code class="language-go">package main

func main() {
	println(&quot;hello world&quot;)
}</code></pre><!--kg-card-begin: html--><div class="goplay-container"></div><!--kg-card-end: html--><h3 id="demo-2-import-third-party-package-from-github">Demo 2. Import third-party package from GitHub</h3><pre><code class="language-go">package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;net/http/httptest&quot;

	&quot;github.com/ggicci/httpin&quot; // 3rd-party packages: will be auto-downloaded
	&quot;github.com/justinas/alice&quot;
)

type ListUsersInput struct {
	Page    int64  `in:&quot;query=page;default=1&quot;`
	PerPage int64  `in:&quot;query=per_page;default=20&quot;`
	Token   string `in:&quot;header=x-access-token;required&quot;`
}

func ListUsers(rw http.ResponseWriter, r *http.Request) {
	input := r.Context().Value(httpin.Input).(*ListUsersInput)
	fmt.Printf(&quot;input: %#v\n&quot;, input)
	rw.WriteHeader(204)
}

func main() {
	r, _ := http.NewRequest(&quot;GET&quot;, &quot;/?page=3&quot;, nil)
	r.Header.Set(&quot;X-Access-Token&quot;, &quot;secret...&quot;)
	handler := alice.New(
		httpin.NewInput(ListUsersInput{}),
	).ThenFunc(ListUsers)
	rw := httptest.NewRecorder()
	handler.ServeHTTP(rw, r)
}</code></pre><!--kg-card-begin: html--><div class="goplay-container"></div><!--kg-card-end: html--><h2 id="how-it-works">How it works?</h2><figure class="kg-card kg-image-card"><img src="https://ggicci.me/content/images/2022/10/goplay-arch.png" class="kg-image" alt="goplay: Embed Go Playground on your Website" loading="lazy" width="1008" height="642" srcset="https://ggicci.me/content/images/size/w600/2022/10/goplay-arch.png 600w, https://ggicci.me/content/images/size/w1000/2022/10/goplay-arch.png 1000w, https://ggicci.me/content/images/2022/10/goplay-arch.png 1008w" sizes="(min-width: 720px) 720px"></figure><p>Since we were not able to make AJAX requests directly to <a href="https://go.dev/play?ref=ggicci.me"><code>go.dev</code></a> because of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS?ref=ggicci.me">CORS</a> issue. I setted up a <strong>reverse proxy</strong> <code>goplay.ggicci.me</code>, which works as a downstream of <code>go.dev</code>. It is responsible to make requests on behalf of the clients to <code>go.dev</code> and return the response back to the clients.</p><p>I also implemented a simple JS SDK <a href="https://github.com/ggicci/goplay?ref=ggicci.me"><code>@ggicci/goplay</code></a> to easily interact with the go playground service. Use it on your page to connect to your own reverse proxy and run go snippets.</p><h3 id="set-up-a-reverse-proxy-to-godev">Set up a reverse proxy to go.dev</h3><h4 id="with-caddy">with <a href="https://caddyserver.com/?ref=ggicci.me"><strong>Caddy</strong></a>:</h4><pre><code class="language-text">goplay.ggicci.me {
    header {
      # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#cors
      access-control-allow-origin      &quot;https://ggicci.me&quot;
      access-control-allow-headers     &quot;content-type&quot;
      access-control-allow-methods     &quot;OPTIONS,HEAD,GET,POST&quot;
    }

    # only proxy the playground API of go.dev/play
    route /_/* {
      reverse_proxy https://go.dev {
        header_up Host go.dev
      }
    }

    respond 404
}</code></pre><h3 id="use-ggiccigoplay">Use @ggicci/goplay</h3><p>The dead simple SDK just implemented a class <code>GoPlayProxy</code> with 3 primary APIs:</p><pre><code class="language-js">class GoPlayProxy {
  // CTOR: specify a proxy url to connect
  constructor(public proxyUrl: string = &apos;/goplay&apos;) {}
  // compile source code and get JSON response
  public async compile(sourceCode: string, goVersion?: CompilerVersion): Promise&lt;CompilerResponse&gt;
  // same as `compile`, but renders the JSON response as an `HTMLElement` into the specified container
  public async renderCompile(container: HTMLElement, sourceCode: string, goVersion?: CompilerVersion): Promise&lt;void&gt;
  // get the share URL of the source code
  public async share(sourceCode: string, goVersion?: CompilerVersion): Promise&lt;string&gt;
}</code></pre><p>Visit <a href="https://github.com/ggicci/goplay?ref=ggicci.me">https://github.com/ggicci/goplay</a> for more details.</p><h2 id="hugo-shortcode-goplay">Hugo Shortcode <code>goplay</code></h2><p>If your are using <a href="https://gohugo.io/?ref=ggicci.me">hugo</a> to generate your website. This section should be helpful to you.</p><p>By using the <code>goplay</code> shortcode, you can easily turn your Go code block into a Go playground. Take the following example, in your markdown file, just wrap the code block between <code>{{% goplay %}}</code> and <code>{{% /goplay %}}</code>.</p><pre><code class="language-markdown">## Sample Code

{{% goplay %}}

```go
package main

func main() {
  println(&quot;hello world&quot;)
}
```

{{% /goplay %}}</code></pre><p>The latest code of <code>goplay - hugo shortcode</code> used by this site (former version) can be found <a href="https://github.com/ggicci/ggicci.me/blob/master/layouts/shortcodes/goplay.html?ref=ggicci.me">here</a>. Save it under your hugo directory correspondingly. Typically it is <code>${YourHugoSiteRoot}/layouts/shortcodes/goplay.html</code>.</p><h2 id="use-goplay-in-your-ghost-blog">Use goplay in your Ghost Blog</h2><p>My site is now using <a href="https://ghost.org/?ref=ggicci.me">Ghost</a>. Thanks to its <strong>Code Injection</strong> function (go to your blog settings and find it there), we can add our custom JavaScript code to drive goplay on our website.</p><p>The script I&apos;m using on this website can be found here:</p><p><a href="https://gist.github.com/ggicci/c85b4665e959a10fdbe0c97b33f44eb0?ref=ggicci.me">https://gist.github.com/ggicci/c85b4665e959a10fdbe0c97b33f44eb0</a></p><h2 id="who-is-using-ggiccigoplay">Who is using <code>@ggicci/goplay</code>?</h2><ul><li><a href="https://ggicci.github.io/httpin/?ref=ggicci.me">httpin Documentation</a></li><li>add yours here by commenting below :)</li></ul>]]></content:encoded></item><item><title><![CDATA[Postgres ID Generator with Safe JSON Numbers]]></title><description><![CDATA[<p>Before reading this post, I recommend you to read <a href="https://rob.conery.io/2014/05/29/a-better-id-generator-for-postgresql/?ref=ggicci.me" rel="nofollow">A Better ID Generator For PostgreSQL</a> written by Rob Conery. To recap briefly, their post had discussed:</p><ol><li>The problem you will face in the rapid grow of your systems if you used GUID as the primary key.</li><li>How Twitter generating auto-incrementing</li></ol>]]></description><link>https://ggicci.me/postgres-id-generator-with-safe-json-numbers/</link><guid isPermaLink="false">634921f812464b000110b05f</guid><category><![CDATA[database]]></category><category><![CDATA[postgres]]></category><category><![CDATA[snowflake]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Mon, 21 Jun 2021 02:33:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1581852017103-68ac65514cf7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fGVsZXBoYW50fGVufDB8fHx8MTY3Mzk2MjExMw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1581852017103-68ac65514cf7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fGVsZXBoYW50fGVufDB8fHx8MTY3Mzk2MjExMw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Postgres ID Generator with Safe JSON Numbers"><p>Before reading this post, I recommend you to read <a href="https://rob.conery.io/2014/05/29/a-better-id-generator-for-postgresql/?ref=ggicci.me" rel="nofollow">A Better ID Generator For PostgreSQL</a> written by Rob Conery. To recap briefly, their post had discussed:</p><ol><li>The problem you will face in the rapid grow of your systems if you used GUID as the primary key.</li><li>How Twitter generating auto-incrementing keys with <a href="https://github.com/twitter-archive/snowflake?ref=ggicci.me">Snowflake</a>.</li><li>Create a functional Snowflake equivalent for PostgresSQL.</li></ol><p>And the third point above will be the focus of discussion in this post.</p><h2 id="the-algorithm">The Algorithm</h2><pre><code class="language-sql">create schema shard_1;
create sequence shard_1.global_id_sequence;

CREATE OR REPLACE FUNCTION shard_1.id_generator(OUT result bigint) AS $$
DECLARE
    our_epoch bigint := 1314220021721;
    seq_id bigint;
    now_millis bigint;
    -- the id of this DB shard, must be set for each
    -- schema shard you have - you could pass this as a parameter too
    shard_id int := 1;
BEGIN
    SELECT nextval(&apos;shard_1.global_id_sequence&apos;) % 1024 INTO seq_id;

    SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
    result := (now_millis - our_epoch) &lt;&lt; 23;
    result := result | (shard_id &lt;&lt; 10);
    result := result | (seq_id);
END;
$$ LANGUAGE PLPGSQL;

select shard_1.id_generator();</code></pre><p>This algorithm will generate an integer in <code>64</code> bits consisting of:</p><pre><code class="language-text">   +----------------+---------------+-------------+
   | Timestamp (41) | Shard ID (13) | Seq ID (10) |
   +----------------+---------------+-------------+
   |&lt;-------------- Unique ID (64) --------------&gt;|</code></pre><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Part</th>
<th>Bits</th>
<th>Desc.</th>
</tr>
</thead>
<tbody>
<tr>
<td>Timestamp</td>
<td>41</td>
<td>Max valid date should be <code>timestamp_to_date((2^41 - 1 + 1314220021721)/1000)</code>, i.e. <code>2081-04-30</code></td>
</tr>
<tr>
<td>Shard ID</td>
<td>13</td>
<td>Support having <code>2^13 = 8192</code> shards</td>
</tr>
<tr>
<td>Seq ID</td>
<td>10</td>
<td>Max <code>2^10 = 1024 ops/ms</code></td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Here we can expand <code>Seq ID</code> part to gain the ablility of supporting more operations per ms. But as a trade off, the <code>Shard ID</code> will be shrunk.</p><p>For example, if we used <code>10</code> bits for <code>Shard ID</code> and <code>13</code> bits for <code>Seq ID</code>, we will allow <code>8192</code> write operations per ms. Which should be <code>8M ops/s</code>. However, <strong>as a reminder</strong>, this <code>8M ops/s</code> is <strong>evenly distributed</strong> over 1s. Which means when your system had a writing spike with over 8192 writes in the same millisecond, it can fail.</p><h2 id="safe-number-problem-in-json">Safe Number Problem in JSON</h2><p>The above algorithm generates a 64-bit integer. In practice, it&apos;s not &quot;JSON comaptible&quot;. Because in JSON world, <em>number</em> is a &quot;double-like&quot; type. And there&apos;s in fact a limitation at JavaScript/ECMAScript level of precision to <strong>53-bit</strong> for integers. The maximum safe integer in JavaScript is <code>2^53 - 1</code>. You can check it by running the following JS code in your browser:</p><pre><code class="language-js">Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.isSafeInteger(9007199254740992); // false</code></pre><p>So, <strong>How we work with 64-bit integers in our API and JavaScript?</strong></p><p>I have two solutions here:</p><ol><li>Encode a 64-bit integer to text by using binary-to-text encoding methods, e.g. <a href="https://en.wikipedia.org/wiki/Binary-to-text_encoding?ref=ggicci.me#Base58" rel="nofollow">Base58</a>, etc.</li><li>Generate &quot;safe&quot; integers, which only occupies the lower order 53-bits.</li></ol><p>Let&apos;s talk about the second solution by tweaking the algorithm above to generate &quot;safe&quot; unique IDs.</p><h2 id="generate-json-safe-53-bit-unique-ids-for-postgressql">Generate &quot;JSON-Safe&quot; 53-bit Unique IDs for PostgresSQL</h2><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Opt.</th>
<th>Bits (time,shard,seq)</th>
<th>Epoch Offset</th>
<th>Timestamp</th>
<th>Shard</th>
<th>Seq / TPS</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>(41, 3, 9)</td>
<td>946656000000</td>
<td>Max <code>2069-09-06</code></td>
<td>8</td>
<td>512 ops/ms</td>
</tr>
<tr>
<td>2</td>
<td>(41, 4, 8)</td>
<td>946656000000</td>
<td>Max <code>2069-09-06</code></td>
<td>16</td>
<td>256 ops/ms</td>
</tr>
<tr>
<td>3</td>
<td>(41, 5, 7)</td>
<td>946656000000</td>
<td>Max <code>2069-09-06</code></td>
<td>32</td>
<td>128 ops/ms</td>
</tr>
<tr>
<td>4</td>
<td>(31, 5, 17)</td>
<td>946656000</td>
<td>Max <code>2068-01-19</code></td>
<td>32</td>
<td>131072 ops/s</td>
</tr>
<tr>
<td>5</td>
<td>(31, 6, 16)</td>
<td>946656000</td>
<td>Max <code>2068-01-19</code></td>
<td>64</td>
<td>65536 ops/s</td>
</tr>
<tr>
<td>6</td>
<td>(31, 7, 15)</td>
<td>946656000</td>
<td>Max <code>2068-01-19</code></td>
<td>128</td>
<td>32768 ops/s</td>
</tr>
<tr>
<td>7</td>
<td>(32, 5, 16)</td>
<td>0</td>
<td>Max <code>2106-02-07</code></td>
<td>32</td>
<td>65536 ops/s</td>
</tr>
<tr>
<td>8</td>
<td>(32, 6, 15)</td>
<td>0</td>
<td>Max <code>2106-02-07</code></td>
<td>64</td>
<td>32768 ops/s</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Here are some points you need to consider:</p><!--kg-card-begin: markdown--><ol>
<li>Use <code>s</code> or <code>ms</code> for the timestamp?
<ul>
<li>Quick Answer: <code>ms</code> for tighter distributions of write operations, and <code>s</code> is more flexible to handle write spikes.</li>
</ul>
</li>
<li>Set epoch offset or not?
<ul>
<li>Quick Answer: It&apos;s a must if using <code>ms</code> for timestamp. Otherwise optional. Better not.</li>
</ul>
</li>
<li>How many shards should I have?
<ul>
<li>Quick Answer: 16 is sufficient for small and medium applications.</li>
</ul>
</li>
<li>TPS considerations?
<ul>
<li>Quick Answer: think of how many write operations your application needs, and the performance (TPS) of your database/shard.</li>
</ul>
</li>
</ol>
<!--kg-card-end: markdown--><p>Here&apos;s the code for the 7th option:</p><pre><code class="language-sql">CREATE SEQUENCE public.global_id_sequence;
CREATE OR REPLACE FUNCTION public.id_generator(OUT result bigint) AS $$
DECLARE
    now_seconds bigint;
    shard_id int := 1;
    seq_id bigint;
BEGIN
    SELECT nextval(&apos;public.global_id_sequence&apos;) % 65536 INTO seq_id;

    SELECT FLOOR(EXTRACT(EPOCH FROM CLOCK_TIMESTAMP())) INTO now_seconds;
    result := now_seconds &lt;&lt; 21; -- Shard(5) + Seq(16)
    result := result | (shard_id &lt;&lt; 16); -- Seq(16)
    result := result | (seq_id);
END;
$$ LANGUAGE PLPGSQL;

SELECT public.id_generator();</code></pre>]]></content:encoded></item><item><title><![CDATA[Decode HTTP Query Params into a Struct in Go]]></title><description><![CDATA[Use httpin package to decode HTTP request params into a struct in Go.]]></description><link>https://ggicci.me/decode-http-query-params-into-a-struct-in-go/</link><guid isPermaLink="false">63491a1012464b000110affc</guid><category><![CDATA[go]]></category><category><![CDATA[http]]></category><category><![CDATA[httpin]]></category><category><![CDATA[go-struct-tags]]></category><category><![CDATA[go-refelction]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Mon, 17 May 2021 03:34:00 GMT</pubDate><media:content url="https://ggicci.me/content/images/2022/10/httpin-cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://ggicci.me/content/images/2022/10/httpin-cover.png" alt="Decode HTTP Query Params into a Struct in Go"><p>Many people use <code>net/http</code> package directly in Go to deal with HTTP requests, including reading URL parameters, HTTP headers, and the request body. It&apos;s straightforward and efficient, though. We can still get bored writing so much tedious code for just reading and parsing the URL params. Especially when we were maintaining a service with hundres of APIs.</p><p>Let&apos;s see a piece of code for dealing with HTTP requests by using <code>net/http</code> package:</p><pre><code class="language-go">// GET /v1/users?page=1&amp;per_page=20&amp;is_member=true
func ListUsers(rw http.ResponseWriter, r *http.Request) {
	page, err := strconv.ParseInt(r.FormValue(&quot;page&quot;), 10, 64)
	if err != nil {
		// Invalid parameter: page.
		return
	}
	perPage, err := strconv.ParseInt(r.FormValue(&quot;per_page&quot;), 10, 64)
	if err != nil {
		// Invalid parameter: per_page.
		return
	}
	isMember, err := strconv.ParseBool(r.FormValue(&quot;is_member&quot;))
	if err != nil {
		// Invalid parameter: is_member.
		return
	}

	// Read database and return the user list.
}</code></pre><p>It&apos;s okay if there are only a few (usually less than 3) parameters to be parsed. But what if more? Both the number of lots of local variables and the writing of statements of parsing string URL params to target types can kill us. Someone even spreads these statements all over in an API handler. &#x1F912;</p><p>For such cases, what can we do to save a clean and tidy code base?</p><h2 id="using-ggiccihttpin">Using ggicci/httpin</h2><blockquote><a href="https://github.com/ggicci/httpin?ref=ggicci.me"><strong>httpin</strong></a> - &#x1F361;HTTP Input for Go - Decode an HTTP request into a custom struct</blockquote><p><strong>ggicci/httpin</strong> is an <a href="https://github.com/ggicci/awesome-go?ref=ggicci.me#forms">awesome</a> package which helps you easily decoding HTTP reqeusts from:</p><ul><li>Query string (URL parameters), e.g. <code>?name=john&amp;is_member=true</code></li><li>Headers, e.g. <code>Authorization: xxx</code></li><li>Form data, e.g. <code>login=john&amp;password=*****</code></li><li>JSON/XML body, e.g. <code>POST {&quot;name&quot;: &quot;john&quot;, &quot;is_member&quot;: true}</code></li><li>Path variables, e.g. <code>/users/{username}</code></li><li>File uploads</li></ul><p>You don&apos;t need to write parsing code for any parameter by yourself. <strong>With httpin, you only care about the definition of the input struct and in which handler it will be used.</strong></p><p>Let&apos;s see an example (working with <code>net/http</code>):</p><pre><code class="language-go">// 1. Define you input struct.
type ListUsersInput struct {
	Page     int  `in:&quot;form=page&quot;`
	PerPage  int  `in:&quot;form=per_page&quot;`
	IsMember bool `in:&quot;form=is_member&quot;`
}

// 2. Bind this input struct with an HTTP handler.
func init() {
	http.Handle(&quot;/users&quot;, alice.New(
		httpin.NewInput(ListUsersInput{}),
	).ThenFunc(ListUsers))
}

// 3. Get your input data in one line of code in your handler, all the parsing stuff are handled by httpin.
func ListUsers(rw http.ResponseWriter, r *http.Request) {
	input := r.Context().Value(httpin.Input).(*ListUsersInput)
}</code></pre><p><strong>httpin</strong> is:</p><ul><li><strong>well documentated</strong>: at <a href="https://ggicci.github.io/httpin/?ref=ggicci.me" rel="nofollow">https://ggicci.github.io/httpin/</a>.</li><li><strong>well tested</strong>: <a href="https://codecov.io/gh/ggicci/httpin?ref=ggicci.me" rel="nofollow">over 98% in coverage</a></li><li><strong>open integrated</strong>: with <a href="https://ggicci.github.io/httpin/integrations/http?ref=ggicci.me" rel="nofollow">net/http</a>, <a href="https://ggicci.github.io/httpin/integrations/gochi?ref=ggicci.me" rel="nofollow">go-chi/chi</a>, <a href="https://ggicci.github.io/httpin/integrations/gorilla?ref=ggicci.me" rel="nofollow">gorilla/mux</a>, <a href="https://ggicci.github.io/httpin/integrations/gin?ref=ggicci.me" rel="nofollow">gin-gonic/gin</a>, etc.</li><li><strong>extensible</strong> (advanced feature): by adding your custom directives. Read <a href="https://ggicci.github.io/httpin/directives/custom?ref=ggicci.me" rel="nofollow">httpin - custom directives</a> for more details.</li></ul><p>You will find that with the help of <strong>httpin</strong>, you can get the following benefits:</p><ul><li>&#x231B;&#xFE0F; Saving developer time</li><li>&#x267B;&#xFE0F; Lower code repetition rate</li><li>&#x1F4D6; Higher readability</li><li>&#x1F528; Higher maintainability</li></ul><p>&#x2764;&#xFE0F; Have a try with it :) </p><p>And please don&apos;t forget to give it a big &#x2B50;&#xFE0F;, thanks in advance!</p>]]></content:encoded></item><item><title><![CDATA[Deploying an AES-128 Encrypted HTTP Live Stream (HLS)]]></title><description><![CDATA[<p><strong>HTTP Live Streaming</strong> (HLS) is an HTTP-based adaptive bitrate streaming communications protocol developed by Apple Inc. and released in 2009. </p><p>Some key points of HLS: </p><ol><li>Created by Apple.</li><li>Consists of a playlist/manifest file (e.g. index.m3u8) and segment video files (e.g. index01.ts).</li><li>H264 codec of video</li></ol>]]></description><link>https://ggicci.me/deploying-an-aes-128-encrypted-http-live-stream-hls/</link><guid isPermaLink="false">6348f85912464b000110afa5</guid><category><![CDATA[streaming]]></category><category><![CDATA[vod]]></category><category><![CDATA[hls]]></category><category><![CDATA[ffmpeg]]></category><category><![CDATA[video.js]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Mon, 10 May 2021 15:07:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1607968565043-36af90dde238?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fGxpdmV8ZW58MHx8fHwxNjY1ODE1MTEz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1607968565043-36af90dde238?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fGxpdmV8ZW58MHx8fHwxNjY1ODE1MTEz&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Deploying an AES-128 Encrypted HTTP Live Stream (HLS)"><p><strong>HTTP Live Streaming</strong> (HLS) is an HTTP-based adaptive bitrate streaming communications protocol developed by Apple Inc. and released in 2009. </p><p>Some key points of HLS: </p><ol><li>Created by Apple.</li><li>Consists of a playlist/manifest file (e.g. index.m3u8) and segment video files (e.g. index01.ts).</li><li>H264 codec of video + AAC of audio.</li><li>Use HTTP, easily leveraging CDN to reach the widest audience without worring about the bandwidth and firewalls.</li><li>Adaptive streaming, enables changing the quality of the video mid-stream.</li><li>Widely supported across devicies and platforms. PC, mobile, Web, IOS, Andorid, etc.</li></ol><h2 id="preface">Preface</h2><p>Let&apos;s start from an sample video, which can be downloaded from <a href="https://vimeo.com/347119375?ref=ggicci.me" rel="nofollow">vimeo</a>. In this post, we are going to:</p><ol><li>Convert the sample video from MP4 to HLS (a <code>.m3u8</code> playlist file and <code>.ts</code> segment files) with AES-128 encryption method;</li><li>Serve the key files on a <strong>key server</strong>;</li><li>Serve the HLS files on a <strong>content server</strong>;</li><li>Test the deployment with <a href="https://www.videolan.org/vlc/?ref=ggicci.me" rel="nofollow">VLC player</a> and <a href="https://videojs.com/?ref=ggicci.me" rel="nofollow">video.js</a>.</li></ol><p></p><h2 id="%F0%9F%94%84-transcode-mp4hls">&#x1F504; Transcode (MP4 -&gt; HLS)</h2><p>The original sample video was in <code>.mp4</code> format. While we need HLS files to serve with. We can use <a href="https://www.ffmpeg.org/?ref=ggicci.me" rel="nofollow"><code>ffmpeg</code></a> to do the transcoding.</p><p><strong>Without AES-128</strong>, we can simply run the <a href="https://gist.github.com/lukebussey/4d27678c72580aeb660c19a6fb73e9ee?ref=ggicci.me">following command</a>:</p><pre><code class="language-bash">ffmpeg -i sample-video.mp4 -codec: copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls sample-noaes.m3u8</code></pre><p>Open <code>sample-noaes.m3u8</code> with VLC player. You should see it playing well.</p><p><strong>With AES-128</strong>, we need to firstly generate an encryption key and an optional IV (initialization vector) for the AES algorithm.</p><pre><code class="language-bash">openssl rand 16 &gt; enc.key  # Key to encrypt the video
openssl rand -hex 16       # IV# de0efc88a53c730aa764648e545e3874</code></pre><p>And then make a key info file having the following content:</p><pre><code class="language-text">KEY URI
Path to the key file
IV (optional)
</code></pre><p>Which is used by <code>ffmpeg</code>. e.g. <code>enc.keyinfo</code>:</p><pre><code class="language-text">https://ksm.ggicci.me/e9672408-b38b-4465-ab47-519c554ae402/enc.key
enc.key
de0efc88a53c730aa764648e545e3874
</code></pre><ul><li>The first line is the URI of the key. Which will be written to m3u8 file. And the player will consult this URI for the key to decrypt the segments files while playing.</li><li>The second line is the path to the file containg the encryption/decription key.</li><li>The third line is an optional initialization vector.</li></ul><p>Now then, we can feed it to <code>ffmpeg</code> to do the transcoding again. But this time, we should get an encrypted version of the output:</p><pre><code class="language-bash">ffmpeg \
  -i sample-video.mp4 \  # input file
  -hls_time 9 \ # 9s for each chunk
  -hls_key_info_file enc.keyinfo \ # encryption key
  -hls_playlist_type vod \ # video on demand mode
  -hls_segment_filename &quot;sample-%d.ts&quot; \ # name the segment files in pattern
  sample.m3u8 # HLS playlist (aka. HLS manifest)</code></pre><p>Open <code>sample.m3u8</code> with VLC. It <strong>won&apos;t</strong> play. Because VLC tried to retrieve the key file from this URI <a href="https://ksm.ggicci.me/e9672408-b38b-4465-ab47-519c554ae402/enc.key?ref=ggicci.me" rel="nofollow">https://ksm.ggicci.me/e9672408-b38b-4465-ab47-519c554ae402/enc.key</a> as noted in <code>sample.m3u8</code>. But it failed. Since we don&apos;t have this <strong>key server</strong> ready, yet.</p><h2 id="%F0%9F%94%91-start-a-key-server">&#x1F511; Start a Key Server</h2><p>Let&apos;s serve our key file <code>enc.key</code> on a web server and make it accessible from the URI above. Then go back to check if VLC can play this <code>sample.m3u8</code>.</p><p>I recommend using <a href="https://caddyserver.com/?ref=ggicci.me" rel="nofollow">Caddy</a> to start up a web server. Which should be easy to learn. Sample site configuration in the Caddyfile:</p><pre><code class="language-Caddyfile">ksm.ggicci.me {
  log {
    level INFO
  }

  tls {
    alpn http/1.1
  }

  file_server {
    root /var/www/ksm.ggicci.me
  }
}
</code></pre><p>Copy file <code>enc.key</code> to our web server in the path specified by the URI. And open <code>sample.m3u8</code> again. This time it should work, since the key URI became accessible.</p><figure class="kg-card kg-image-card"><a href="https://github.com/ggicci/ggicci.me/blob/master/content/posts/deploying-aes-128-encrypted-hls/vlc-open-sample-m3u8.png?ref=ggicci.me"><img src="https://github.com/ggicci/ggicci.me/raw/master/content/posts/deploying-aes-128-encrypted-hls/vlc-open-sample-m3u8.png" class="kg-image" alt="Deploying an AES-128 Encrypted HTTP Live Stream (HLS)" loading="lazy"></a></figure><h2 id="%F0%9F%8E%9E%EF%B8%8F-start-a-content-server">&#x1F39E;&#xFE0F; Start a Content Server</h2><p>So far, our encryption key file has been served well on our key server. As long as we move our video content to a web server, too. We can consume our videos online with assistance of video players. We call it a <strong>Content Server</strong>.</p><p>Again, we use Caddy to achieve our goal:</p><pre><code class="language-Caddyfile">v.ggicci.me {
  log {
    level INFO
  }

  tls {
    alpn http/1.1
  }

  file_server {
    root /var/www/v.ggicci.me
    index index.html
  }
}
</code></pre><p>Copy the playlist file and segment files to the web server. Under <code>/sample</code> folder in my case. And it&apos;s now accessible at <a href="https://v.ggicci.me/sample/index.m3u8?ref=ggicci.me" rel="nofollow">https://v.ggicci.me/sample/index.m3u8</a>. Try open it with VLC player (Media &gt; Open Network Stream...). It should work like a charm.</p><h2 id="%F0%9F%93%BA-use-of-videojs">&#x1F4FA; Use of video.js</h2><p>Now that we have set up servers to host our stream. Why not host a video player, too? Thus we can consume our videos through web browsers!</p><p>Here we will try <a href="https://videojs.com/?ref=ggicci.me" rel="nofollow">video.js</a> - the world&apos;s most popular open source HTML5 player framework.</p><p>With a little research on its official documentation. We can compose a test HTML like this:</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;head&gt;
  &lt;link href=&quot;https://vjs.zencdn.net/7.11.4/video-js.css&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;video
    id=&quot;my-video&quot;
    class=&quot;video-js&quot;
    controls
    preload=&quot;auto&quot;
    height=&quot;420&quot;
    poster=&quot;/sample/cover.png&quot;
    data-setup=&quot;{}&quot;
  &gt;
    &lt;source src=&quot;/sample/index.m3u8&quot; type=&quot;application/x-mpegURL&quot; /&gt;
    &lt;p class=&quot;vjs-no-js&quot;&gt;
      To view this video please enable JavaScript, and consider upgrading to a
      web browser that
      &lt;a href=&quot;https://videojs.com/html5-video-support/&quot; target=&quot;_blank&quot;
        &gt;supports HTML5 video&lt;/a
      &gt;
    &lt;/p&gt;
  &lt;/video&gt;

  &lt;script src=&quot;https://vjs.zencdn.net/7.11.4/video.min.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;</code></pre><p>Since we need to read encryption key files from <code>ksm.ggicci.me</code> on <code>v.ggicci.me</code>. We will meet <a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing?ref=ggicci.me" rel="nofollow">CORS</a> problem. Solving it by adding corresponding headers to <code>ksm.ggicci.me</code>&apos;s site configurations:</p><pre><code class="language-Caddyfile">ksm.ggicci.me {
  log {
    level INFO
  }

  tls {
    alpn http/1.1
  }

  file_server {
    root /var/www/ksm.ggicci.me
  }

  header {
    Vary &quot;Origin&quot;
    Access-Control-Allow-Origin &quot;https://v.ggicci.me&quot;
    Access-Control-Allow-Methods &quot;OPTIONS, HEAD, GET&quot;
  }
}
</code></pre><p>Visit <a href="https://v.ggicci.me/sample?ref=ggicci.me" rel="nofollow">https://v.ggicci.me/sample</a> to see the result. Also try it with your mobile devices :)</p><h2 id="architecture">Architecture</h2><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://github.com/ggicci/ggicci.me/blob/master/content/posts/deploying-aes-128-encrypted-hls/hls-arch.png?ref=ggicci.me"><img src="https://github.com/ggicci/ggicci.me/raw/master/content/posts/deploying-aes-128-encrypted-hls/hls-arch.png" class="kg-image" alt="Deploying an AES-128 Encrypted HTTP Live Stream (HLS)" loading="lazy"></a><figcaption>Architecture of an Encrypted HLS Service</figcaption></figure><h2 id="references">References</h2><ul><li><a href="https://developer.apple.com/streaming/?ref=ggicci.me" rel="nofollow">Apple Developer - HTTP Live Streaming</a></li><li><a href="https://www.dacast.com/blog/hls-encryption-for-video/?ref=ggicci.me" rel="nofollow">HLS Encryption: How to Encrypt Videos in AES-128 For HTTP Live Streaming [2021 Update]</a></li><li><a href="https://developer.apple.com/streaming/fps/?ref=ggicci.me" rel="nofollow">Apple FPS - FairPlay Streaming</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Open Source Contribution Workflow Best Practice]]></title><description><![CDATA[<p>Generally speaking, as we were using some open-source libraries we will inevitably find that there are some bugs or points that can be improved. And sometimes we even have to develop a brand new feature based on these libraries to satisfy our own needs. Out of respect for sprit of</p>]]></description><link>https://ggicci.me/open-source-contribution-workflow-best-practice/</link><guid isPermaLink="false">6348dca412464b000110af63</guid><category><![CDATA[git]]></category><category><![CDATA[github]]></category><category><![CDATA[open-source]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Mon, 07 Jan 2019 16:48:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1647166545674-ce28ce93bdca?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxnaXRodWJ8ZW58MHx8fHwxNjY1ODE1MzU2&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1647166545674-ce28ce93bdca?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDE1fHxnaXRodWJ8ZW58MHx8fHwxNjY1ODE1MzU2&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Open Source Contribution Workflow Best Practice"><p>Generally speaking, as we were using some open-source libraries we will inevitably find that there are some bugs or points that can be improved. And sometimes we even have to develop a brand new feature based on these libraries to satisfy our own needs. Out of respect for sprit of dedication, contributing our code to these opensource libraries is a good way to give back to the community.</p><h2 id="my-practice">My Practice</h2><p>When I decided to contribute to a repository, I will first try looking for if there is a contribution guide for it. Such guides usually lied as a section in the README or as a single document named CONTRIBUTING. Only when no guides were found, I will try to open a pull request to this repository on Github.</p><h2 id="my-best-workflow">My Best Workflow</h2><p>I&apos;m using <code>github.com/gorilla/mux</code> as a sample repository to which I&apos;m going to contribute. Here&apos;s my workflow:</p><ol><li>On GitHub</li></ol><p>Fork the repo to my account, e.g. <code>github.com/ggicci/mux</code></p><p>2. On local machine</p><pre><code class="language-bash"># clone the repo
git clone git@github.com:gorilla/mux.git

# rename origin to upstream
git remote rename origin upstream

# add my forked repo as the origin remote
git remote add origin git@github.com:ggicci/mux.git

# make some changes to the source code and commit to origin
git add ...
git commit ...
git push origin ...</code></pre><p>3. Back to GitHub</p><p>Open a pull request from <code>ggicci</code> to <code>gorilla</code>.</p>]]></content:encoded></item><item><title><![CDATA[Reduce Docker Image Size by Using Multi-stage Builds]]></title><description><![CDATA[If you don't take any optimization measures, docker images can easily get large. And in most cases we just wrapped too many inessential things into the images. So, we should take actions to get rid of it.]]></description><link>https://ggicci.me/reduce-docker-image-size-by-using-multi-stage-builds/</link><guid isPermaLink="false">6346dab7b8a55e00018f60f5</guid><category><![CDATA[docker]]></category><category><![CDATA[Dockerfile]]></category><dc:creator><![CDATA[Ggicci T'ang]]></dc:creator><pubDate>Sat, 13 Oct 2018 14:40:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1646627927863-19874c27316b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDd8fGRvY2tlcnxlbnwwfHx8fDE2NzM5NjI3NDE&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1646627927863-19874c27316b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDd8fGRvY2tlcnxlbnwwfHx8fDE2NzM5NjI3NDE&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Reduce Docker Image Size by Using Multi-stage Builds"><p>If you don&#x2019;t take any optimization measures, docker images can easily get large. And in most cases we just wrapped too many inessential things into the images. So, we should take actions to get rid of it.<br>Let&#x2019;s take a look at a common example Dockerfile for building an application written in <a href="https://golang.org/?ref=ggicci.me">Go</a>.</p><pre><code class="language-Dockerfile">FROM golang:1.10.2-stretch

WORKDIR /go/src/github.com/ggicci/docker-example-server
COPY . .

RUN go install

LABEL \
 me.ggicci.appdemo.image.created=&quot;2006-01-02T15:04:05+08:00&quot; \
 me.ggicci.appdemo.image.version=&quot;1.0.0&quot;

# (and more)

WORKDIR /app
ENTRYPOINT [ &quot;docker-example-server&quot; ]
EXPOSE 8080</code></pre><!--kg-card-begin: markdown--><p>After building the image by running command <code>docker build -t example:1.0.0 .</code>, we can see the size of the image we get is 800+ MB.</p>
<!--kg-card-end: markdown--><pre><code class="language-text">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
example             1.0.0               9daf905d097d        12 seconds ago      801MB</code></pre><p>However as we know, go programs are statically compiled and linked. We don&#x2019;t need many things in the image to run the final binary:</p><ol><li>Source code.</li><li>Go compiling tools (the <code>go</code> command).</li><li>Big linux image.</li></ol><p>Then, let&#x2019;s cut them off.</p><h2 id="use-multi-stage-builds">Use Multi-stage Builds</h2><pre><code class="language-Dockerfile"># Stage: builder
FROM golang:1.10.2-stretch as builder

WORKDIR /go/src/github.com/ggicci/docker-example-server
COPY . .

RUN go install

# Stage: runner
FROM alpine:3.7

WORKDIR /app
COPY --from=builder /go/bin/docker-example-server /app

LABEL \
  me.ggicci.appdemo.image.created=&quot;2006-01-02T15:04:05+08:00&quot; \
  me.ggicci.appdemo.image.version=&quot;1.0.0&quot;
  # (and more)

ENTRYPOINT [ &quot;docker-example-server&quot; ]
EXPOSE 8080</code></pre><p>There are two stages defined in the Dockerfile above:</p><ul><li><code>builder</code>: based on a linux image with go tools installed, we build our application;</li><li><code>runner</code>: based on a tiny linux image <a href="https://alpinelinux.org/?ref=ggicci.me">alpine</a>, we copy the application binary from the builder image and discard the builder image.</li></ul><p>Now, the image we get is small enough. Which is only 10+ MB.</p><pre><code class="language-text">REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
example-2           1.0.0               77488a7264a1        9 seconds ago       11MB</code></pre><p>Cheers &#x1F389;</p>]]></content:encoded></item></channel></rss>