<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://gowtham.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gowtham.dev/" rel="alternate" type="text/html" /><updated>2026-03-28T06:18:16+00:00</updated><id>https://gowtham.dev/feed.xml</id><title type="html">Gowtham</title><subtitle>rants, learnings and documentations from a 1x engineer.</subtitle><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><entry><title type="html">Blogs I read and recommend</title><link href="https://gowtham.dev/blog/blogs-i-read-and-recommend.html" rel="alternate" type="text/html" title="Blogs I read and recommend" /><published>2026-03-28T05:24:12+00:00</published><updated>2026-03-28T05:24:12+00:00</updated><id>https://gowtham.dev/blog/blogs-i-read-and-recommend</id><content type="html" xml:base="https://gowtham.dev/blog/blogs-i-read-and-recommend.html"><![CDATA[<p>I have been meaning to list the best blogs I usually read via my self hosted <a href="https://miniflux.app/">miniflux</a>, so you can also explore and add it to your list if you find it useful.</p>

<p>Some special mentions:</p>
<ul>
  <li><em><a href="https://mrkaran.dev/">Karan Sharma</a></em> - I personally learnt and got into <a href="https://github.com/hashicorp/nomad">nomad</a> because of Karan and also contributed once upstream. He’s an avid open source enthusiast, experiments a lot and share it across in his blog.</li>
  <li><em><a href="https://theorangeone.net/posts/">Jake Howard</a></em> - Most of my self-hosted infra is inspired by <a href="https://git.theorangeone.net/systems/infrastructure">Jake’s infrastructure</a>. He writes a lot on <a href="https://theorangeone.net/posts/tags/self-hosting/">self hosting</a>.</li>
  <li><em><a href="https://www.seangoedecke.com/">Sean Goedecke</a></em> - Writes more about politics in the corporate world. Sometimes developers need the harsh pills and should know about how a company thinks, behaves and rewards it’s people.</li>
  <li><em><a href="https://lalitm.com/">Lalit Maganti</a></em> - Similar to Sean, but also have some technical entries as well. I personally love his <a href="https://lalitm.com/page/essays/">essays</a>.</li>
  <li><em><a href="https://simonwillison.net/entries/">Simon Willison</a></em> - Writes a lot on the recent developments in the field of LLMs.</li>
</ul>

<p>And the following blogs that cover a lot on linux, AI and computers in general:</p>
<ul>
  <li><em><a href="https://antonz.org/all/">Anton Zhiyanov</a></em></li>
  <li><em><a href="https://michael.stapelberg.ch/">Michael Stapelberg</a></em></li>
  <li><em><a href="https://iamvishnu.com/">Vishnu Haridas</a></em></li>
  <li><em><a href="https://anthonynsimon.com/blog/">Anthony Simon</a></em></li>
  <li><em><a href="https://herman.bearblog.dev/">Herman Martinus</a></em></li>
  <li><em><a href="https://cameronwestland.com/">Cameron Westland</a></em></li>
  <li><em><a href="https://jazco.dev/">Jaz</a></em></li>
</ul>

<p>and the usual - <a href="https://news.ycombinator.com/">news.ycombinator.com</a> and <a href="https://lobste.rs/">lobste.rs</a>.</p>

<hr />]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="reading" /><summary type="html"><![CDATA[Curation of the blogs I love to read and learn from.]]></summary></entry><entry><title type="html">Named Entity Recognition (NER)</title><link href="https://gowtham.dev/til/ner.html" rel="alternate" type="text/html" title="Named Entity Recognition (NER)" /><published>2026-01-24T13:14:20+00:00</published><updated>2026-01-24T13:14:20+00:00</updated><id>https://gowtham.dev/til/ner</id><content type="html" xml:base="https://gowtham.dev/til/ner.html"><![CDATA[<p>While researching on how to cluster some document contents together in an ML pipeline for a pet project, I came across <a href="https://en.wikipedia.org/wiki/Named-entity_recognition">NER (Named Entity Recognition)</a>. Apparently, it’s a technique in NLP which can be used to identify named entities like persons, places, date, organizations, etc.</p>

<p>In python you can use libraries like <a href="https://spacy.io/">spaCy</a>, <a href="https://github.com/google-research/bert">BERT</a>, etc to do this or you can also use LLM to extract this info as shown <a href="https://huggingface.co/datasets/polyglots/Tamil-NER-Transliterated/viewer">here</a>.</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="til" /><category term="machine-learning" /><category term="python" /><summary type="html"><![CDATA[Entry on NER which can be used for clustering of content.]]></summary></entry><entry><title type="html">Self Hosting Setup - 2025</title><link href="https://gowtham.dev/blog/self-hosting-setup-2025.html" rel="alternate" type="text/html" title="Self Hosting Setup - 2025" /><published>2025-12-18T17:24:12+00:00</published><updated>2025-12-18T17:24:12+00:00</updated><id>https://gowtham.dev/blog/self-hosting-setup-2025</id><content type="html" xml:base="https://gowtham.dev/blog/self-hosting-setup-2025.html"><![CDATA[<p>It’s been <a href="/blog/self-hosting-setup-2023.html">two years</a> since the last self-hosting setup post. The setup largely remained pretty much the same with the following changes:</p>

<ul>
  <li><a href="https://pi-hole.net/">piHole</a> and <a href="https://github.com/DNSCrypt/dnscrypt-proxy">dnscrypt-proxy</a> has been replaced with <a href="https://adguard.com/en/adguard-home/overview.html">Adguard Home</a> since it does both.</li>
  <li><a href="https://www.photoprism.app/">photoprism</a> - self-hosted alternative to google photos and iCloud photos. I have bought a new iPhone with iCloud plans for my family, but I’m still backing up all the photos here just in case. I am planning to explore immich in the next year when I get some time.</li>
  <li>loki, prometheus, grafana, blackbox-exporter and alertmanager - for observability.</li>
  <li><a href="https://jellyfin.org/">jellyfin</a> - currently testing as an alternative to plex.</li>
  <li><a href="https://uptime.kuma.pet/">uptime-kuma</a> - for uptime monitoring.</li>
  <li><a href="https://actualbudget.org/">Actual Budget</a> - helps me to keep track of my finances. Earlier, I was using MyExpenses in android which is open-source with some paid features and since I’m not carrying my android phone daily, I moved to actual and it’s been great so far.</li>
</ul>

<p>I added two servers in late 2023 in addition to the raspberry pi and they’ve been running for 2 years without any problems:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">blr</code> - server from DigitalOcean in Bangalore region. Takes care of latency sensitive and critical services.</li>
  <li><code class="language-plaintext highlighter-rouge">hydra</code> - server from Hetzner in Germany. The arm64 shared servers are good in terms of price-to-performance ratio. I use this server for all the resource heavy services.</li>
  <li><code class="language-plaintext highlighter-rouge">pi</code> - a raspberry pi 4B connected to my home network and runs a DNS server alone for now.</li>
</ol>

<p>All the services are deployed as docker containers via ansible and terraform. I talk to all these servers via tailscale SSH.</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="self-hosting" /><category term="servers" /><summary type="html"><![CDATA[Software that I self host as of 2025.]]></summary></entry><entry><title type="html">MySQL truncate in transaction</title><link href="https://gowtham.dev/til/mysql-truncate-transaction.html" rel="alternate" type="text/html" title="MySQL truncate in transaction" /><published>2025-06-30T16:26:00+00:00</published><updated>2025-06-30T16:26:00+00:00</updated><id>https://gowtham.dev/til/mysql-truncate-transaction</id><content type="html" xml:base="https://gowtham.dev/til/mysql-truncate-transaction.html"><![CDATA[<p>MySQL truncate does an implicit commit. So, if you are using a transaction, it will commit the transaction and for any reason if you want to rollback, you will not be able to do it. Use <code class="language-plaintext highlighter-rouge">DELETE FROM table_name</code> instead.</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="til" /><category term="database" /><category term="mysql" /><summary type="html"><![CDATA[Figured out we should not use truncate in MySQL transactions.]]></summary></entry><entry><title type="html">Side projects</title><link href="https://gowtham.dev/blog/side-projects.html" rel="alternate" type="text/html" title="Side projects" /><published>2025-05-26T14:43:49+00:00</published><updated>2025-05-26T14:43:49+00:00</updated><id>https://gowtham.dev/blog/side-projects</id><content type="html" xml:base="https://gowtham.dev/blog/side-projects.html"><![CDATA[<p>When I was around 17, my cousin and I wanted to earn money to buy some video games. We had a computer and we thought why not create a website and put up ads like every other website. The only thing is we don’t know how to create, host and run a website. After a quick google search, we stumbled upon <a href="https://www.blogger.com">Blogger</a>. We signed up, created a new site, created some posts, changed the default theme and also signed up for <a href="https://adsense.google.com/start/">Adsense</a>.</p>

<p>All set <em>(we thought)</em>, but no-one visited the site. We constantly checked the stats page and there’ll be some random users from random countries who visited the homepage and will leave in a few seconds. We just don’t know what’s wrong with the site. Then we came to know about <a href="https://en.wikipedia.org/wiki/Search_engine_optimization">SEO (Search Engine Optimization)</a> which made us change almost all of our content as per the Google SEO recommendations. Then this blogger site was eventually moved to <a href="https://new.drupal.org/home">Drupal</a>, then to <a href="https://wordpress.org/">Wordpress</a> and then finally to a static site built on top of <a href="https://jekyllrb.com/">jekyll</a>.</p>

<p>This small side-project helped me learn a lot about managing servers, DNS, SEO, HTML, CSS, JS, PHP and ruby to some extent. After getting my first job at Cognizant, all these learning alongside very minor explorations with docker helped me land my second job.</p>

<p>After joining Freshworks as a Platforms Systems Engineer, we are supposed to manage the servers for our services and all my previous experiences came in handy. Somewhere in the middle around 2020, projects like <a href="https://kubernetes.io/">Kubernetes</a> and <a href="https://github.com/hashicorp/nomad">Nomad</a> were interesting and I learnt them both. I tried deploying a sample rails app using these orchestrators and even contributed a small enhancement to Nomad. Luckily the organisation was moving to kubernetes and my previous experience helped me to move faster and also assist other developers who are new to it. In the meanwhile, I moved to another team managing multiple golang services and it was way easier for me to onboard because of my previous open-source stint.</p>

<p>These are just the major instances which I can think of where my side-quests have helped me to connect the dots. At any point of time in my career, I have at-least one side-project that I actively work on. It may not see the light of day, but it helps to learn something new. So, if you’re a developer, <em>please have a side-project</em>.</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="career" /><summary type="html"><![CDATA[This entry talks about my side projects, how they helped me in my career and why you should have one.]]></summary></entry><entry><title type="html">Python Type Ordering</title><link href="https://gowtham.dev/til/python-type-ordering.html" rel="alternate" type="text/html" title="Python Type Ordering" /><published>2025-02-06T17:14:49+00:00</published><updated>2025-02-06T17:14:49+00:00</updated><id>https://gowtham.dev/til/python-type-ordering</id><content type="html" xml:base="https://gowtham.dev/til/python-type-ordering.html"><![CDATA[<p>Found out order of the types are important in the same python file for type hints. This will throw <code class="language-plaintext highlighter-rouge">Undefined name: B</code> error if the class is used before it’s defined. For example, consider the following code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">List</span>

<span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
    <span class="n">items</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">B</span><span class="p">]</span>

<span class="k">class</span> <span class="nc">B</span><span class="p">:</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</code></pre></div></div>

<p>To fix, we should reorder the classes:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">List</span>

<span class="k">class</span> <span class="nc">B</span><span class="p">:</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>

<span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
    <span class="n">items</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">B</span><span class="p">]</span>
</code></pre></div></div>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="til" /><category term="python" /><summary type="html"><![CDATA[This entry is about the ordering of types in Python.]]></summary></entry><entry><title type="html">Python self-contained script execution with uv</title><link href="https://gowtham.dev/til/uv-self-contained-script.html" rel="alternate" type="text/html" title="Python self-contained script execution with uv" /><published>2025-02-06T17:14:49+00:00</published><updated>2025-02-06T17:14:49+00:00</updated><id>https://gowtham.dev/til/uv-self-contained-script</id><content type="html" xml:base="https://gowtham.dev/til/uv-self-contained-script.html"><![CDATA[<p>I’ve been playing with <code class="language-plaintext highlighter-rouge">uv</code> a lot lately for some python automation projects. TIL, <code class="language-plaintext highlighter-rouge">uv</code> supports running a single python file that has inline dependency declarations and runtime.</p>

<p>Official documentation - <a href="https://docs.astral.sh/uv/guides/scripts">https://docs.astral.sh/uv/guides/scripts</a>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># create a script</span>
uv init <span class="nt">--script</span> example.py <span class="nt">--python</span> 3.13

<span class="c"># add depedencies</span>
uv add <span class="nt">--script</span> example.py <span class="s1">'requests'</span> <span class="s1">'pandas'</span>

<span class="c"># run the script</span>
uv run example.py
</code></pre></div></div>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="til" /><category term="python" /><category term="uv" /><summary type="html"><![CDATA[This entry is about running a self-contained Python script with uv.]]></summary></entry><entry><title type="html">First PC Build</title><link href="https://gowtham.dev/blog/first-pc-build.html" rel="alternate" type="text/html" title="First PC Build" /><published>2024-12-14T17:14:49+00:00</published><updated>2024-12-14T17:14:49+00:00</updated><id>https://gowtham.dev/blog/first-pc-build</id><content type="html" xml:base="https://gowtham.dev/blog/first-pc-build.html"><![CDATA[<blockquote>
  <p>This entry was supposed to go live on December 2023 and it was only published until a year after in 2024.</p>
</blockquote>

<p>I was 10 years old when I got hooked with <a href="https://en.wikipedia.org/wiki/Road_Rash">Road Rash</a> on a PC at my neighbour’s house. My parents got me a Toshiba laptop when I was 17 years old and I played some casual games in it from time to time. This laptop served me great for 4 years and then one day during heavy thunderstorms, it got fried due to an electrical surge.</p>

<p>I bought a second laptop — this time a Lenovo with Nvidia 960M graphics inside with my side gig money. This one worked for 3 years and I extended 3 more years by changing its internal disk to SSD. By this time, I also had a 13-inch macbook pro from work and since it’s unix based, it became my goto for all development work.</p>

<p>I then bought two more mac devices (2018 Macbook Pro and 2023 M2 Mac Mini), but the urge to build myself a desktop PC was still there and I wanted to build a PC myself. Currently, Mac Mini works great for my development work and for some cracked sessions in <a href="https://en.wikipedia.org/wiki/Factorio">Factorio</a>, but some good games like <a href="https://en.wikipedia.org/wiki/Cities:_Skylines">Cities Skylines</a>, <a href="https://en.wikipedia.org/wiki/Stronghold:_Crusader">Stronghold Crusader</a> series, <a href="https://en.wikipedia.org/wiki/Anno_1800">Anno 1800</a> and other castle simulation RTS games were only available on PC.</p>

<p>I had some free time in December and thought I could build a PC for the holidays. I stumbled upon <a href="https://www.youtube.com/c/GamersNexus">GamersNexus</a> and lurked on <a href="https://discord.com/invite/uJggF6Z">IndianGaming discord</a> for research and clarified some of my questions with the community. Since I already had a 4k monitor (Dell Ultrasharp U2720Q), I wanted to build a system that can comfortably handle at least 1440p. So, after tons of research this is my parts list:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Component</th>
      <th style="text-align: left">Part</th>
      <th style="text-align: right">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Motherboard</td>
      <td style="text-align: left">Gigabyte B650M Gaming X AX Wifi</td>
      <td style="text-align: right">₹17,700</td>
    </tr>
    <tr>
      <td style="text-align: left">CPU</td>
      <td style="text-align: left">AMD Ryzen 7800X3D</td>
      <td style="text-align: right">₹34,600</td>
    </tr>
    <tr>
      <td style="text-align: left">PSU</td>
      <td style="text-align: left">Corsair RM850e</td>
      <td style="text-align: right">₹10,500</td>
    </tr>
    <tr>
      <td style="text-align: left">Memory</td>
      <td style="text-align: left">Corsair Vengeance DDR5 32GB 6000Mhz (16*2)</td>
      <td style="text-align: right">₹9,500</td>
    </tr>
    <tr>
      <td style="text-align: left">SSD</td>
      <td style="text-align: left">Samsung 980 Evo 1TB</td>
      <td style="text-align: right">₹8,750</td>
    </tr>
    <tr>
      <td style="text-align: left">GPU</td>
      <td style="text-align: left">Sapphire Pulse 7900XT 20GB</td>
      <td style="text-align: right">₹81,900</td>
    </tr>
    <tr>
      <td style="text-align: left">Cabinet</td>
      <td style="text-align: left">Corsair 4000D Airflow Black</td>
      <td style="text-align: right">₹6,550</td>
    </tr>
    <tr>
      <td style="text-align: left">CPU Cooler</td>
      <td style="text-align: left">Deepcool AK620 120MM</td>
      <td style="text-align: right">₹5,475</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><em>Total (excluding shipping)</em></td>
      <td style="text-align: right">₹1,74,975</td>
    </tr>
  </tbody>
</table>

<p>If you’re building a PC in India, you should definitely visit <a href="https://pcpricetracker.in">pcpricetracker</a> to compare prices between multiple PC parts online retailers. Amazon was costly even with a 5% discount from Amazon Pay card. You should also check with offline retail stores in your cities. I got most of the parts from an offline retail store in Coimbatore. The cabinet and the CPU cooler that I wanted were not available in that store, so I had to order online at <a href="https://www.pcstudio.in">PC Studio</a>.</p>

<p>After all the parts arrived, I unpacked all the parts. I knew it will take a lot of patience, so I prepared everything for the BIOS POST.</p>

<p><img src="/static/img/pc-build/parts-spread-min.jpg" alt="unpacked parts" title="unpacked parts on the floor" />
<em>unpacked parts on the floor. in hindsight, doing it on the floor was a mistake.</em></p>

<p>I started setting up the motherboard, RAM, PSU and the CPU. I saw the BIOS POST and then proceeded with the usual build. After mounting the CPU cooler, NVMe drive, GPU and some cable management, it was ready. All worked well after boot and sadly when I was closing the side glass panel of the cabinet, I dropped 5cms above a hard surface and it cracked.</p>

<p><img src="/static/img/pc-build/shattered-glass-min.jpg" alt="shattered glass panel of the cabinet" title="shattered glass panel of the cabinet" />
<em>shattered glass panel of the cabinet</em></p>

<p>When I looked this up on the internet, I realized it’s one of the newbie mistakes the new builders make. Fortunately, Corsair support was kind enough to send a replacement without any charges even after I offered to pay them. Thanks Corsair. Till the replacement came I used a cloth to seal the cabinet. Anyway, this is the final build after spending 4 hours on it.</p>

<p><img src="/static/img/pc-build/final-min.jpg" alt="final image of the cabinet" title="final image of the cabinet" />
<em>final image of the cabinet. sorry i didn’t take any pictures in-between.</em></p>

<p>It’s beautiful, isn’t it? Okay, there are no fancy RGBs, but it gets the job done. Now I’m playing all the games at 4k and the system handles it very well. I just wish I have more time to game :).</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="gaming" /><category term="devices" /><summary type="html"><![CDATA[This entry is about my first PC build and the things I learned from it.]]></summary></entry><entry><title type="html">Bitten by Golang Gotcha</title><link href="https://gowtham.dev/blog/bitten-by-golang-gotcha.html" rel="alternate" type="text/html" title="Bitten by Golang Gotcha" /><published>2023-09-16T18:41:18+00:00</published><updated>2023-09-16T18:41:18+00:00</updated><id>https://gowtham.dev/blog/bitten-by-golang-gotcha</id><content type="html" xml:base="https://gowtham.dev/blog/bitten-by-golang-gotcha.html"><![CDATA[<blockquote>
  <p>Update 19, Sep 2023: Go team <a href="https://go.dev/blog/loopvar-preview">confirmed</a> the default behaviour will be changed from version 1.22.</p>
</blockquote>

<p><img src="/static/img/gopher.jpg" alt="AI generated image of a gopher biting a man" title="AI generated image of a gopher biting a man" /></p>

<p>At work, both my previous project and the current project I’m working on relies on SQS for failure retries with exponential backoff. The current project is a <em>webhook service</em> that delivers webhooks to our customers from our products and also among internal services. We use SQS for failure retries and the current queue setup looks like this:</p>

<table>
  <thead>
    <tr>
      <th>queue-name</th>
      <th>delay</th>
      <th>offset-delay (hh:mm:ss)</th>
      <th>visibility-timeout</th>
      <th>max-receive-count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>failed-0</td>
      <td>30s</td>
      <td>00:00:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
    <tr>
      <td>failed-1</td>
      <td>1m</td>
      <td>00:01:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
    <tr>
      <td>failed-2</td>
      <td>2m</td>
      <td>00:03:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
    <tr>
      <td>failed-4</td>
      <td>4m</td>
      <td>00:07:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
    <tr>
      <td>failed-8</td>
      <td>8m</td>
      <td>00:15:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
    <tr>
      <td>failed-15</td>
      <td>15m</td>
      <td>00:30:30</td>
      <td>10m</td>
      <td>10</td>
    </tr>
  </tbody>
</table>

<p>“<em>visibility timeout</em>” above refers to the maximum time SQS will wait before sending the same message to another consumer API call <strong>if the message is not deleted</strong> by your consumer. This will be helpful in cases where your consumer pod/instance crashed and the messages should be re-delivered to other consumers. “<em>maximum receive count</em>” refers to the maximum number of consumer deliveries SQS will do for a same request before moving it to the <a href="https://en.wikipedia.org/wiki/Dead_letter_queue">dead-letter</a> queue.</p>

<p>When a request fails on the first try due to various reasons (connection timeout, non-2xx response, etc), the service will push the request to the first failed queue (failed-0) and mark it as failed. The failed consumers should delete the message from the current failed queue (failed-0) irrespective of the result of the operation, else they’ll become visible to subsequent <code class="language-plaintext highlighter-rouge">ReceiveMessage</code> API calls.</p>

<p>We were in the middle of migrating internal test endpoints and found that one endpoint was failing a lot because of connection timeouts and our golang consumers were unable to keep up with the traffic (~20k rps). All these requests were pushed to the failed queue (failed-0) and our consumers were accepting the messages, but only a very few of these failed messages were deleted and pushed to the second failed queue (failed-1). Also, I can see our consumer processing the <strong>same message 10 times</strong> (what?).</p>

<p>Meanwhile the <code class="language-plaintext highlighter-rouge">NumberOfMessagesNotVisible</code> metric (the number of messages that are currently processed by the consumers) for the first queue spiked to the maximum limit of 120K allowed by SQS. Post this limit SQS won’t return any messages in <code class="language-plaintext highlighter-rouge">ReceiveMessage</code> api calls causing a delayed delivery for all the failed messages.</p>

<p>So, there are two issues at hand - the consumer is processing duplicate messages and the <code class="language-plaintext highlighter-rouge">NumberOfMessagesVisible</code> reached the 120k limit. Just to be sure that the producer is not pushing any duplicate messages to the SQS queue, I checked the logs (we’re using ELK stack internally) and can see we’re producing the message only once. I also polled for messages multiple times in the AWS SQS console and there I don’t see any duplicates (though it’s very random and we can’t be sure). So, the bug might be in the consumer code?!</p>

<p>I was also able to reproduce this in my local setup and started a debugging session to check whether SQS <code class="language-plaintext highlighter-rouge">ReceiveMessage</code> API call returned any duplicate messages. As per design, after receiving the messages via <code class="language-plaintext highlighter-rouge">ReceiveMessage</code>, each message will be pushed to a buffered channel which is consumed by a goroutine worker pool. The code looked like this:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">{</span>
	<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">SqsClient</span><span class="o">.</span><span class="n">ReceiveMessage</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sqs</span><span class="o">.</span><span class="n">ReceiveMessageInput</span><span class="p">{</span>
		<span class="n">QueueUrl</span><span class="o">:</span>              <span class="o">&amp;</span><span class="n">queueUrl</span><span class="p">,</span>
		<span class="n">MessageAttributeNames</span><span class="o">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"ApproximateReceiveCount"</span><span class="p">},</span>
		<span class="n">MaxNumberOfMessages</span><span class="o">:</span>   <span class="m">10</span><span class="p">,</span>
		<span class="n">WaitTimeSeconds</span><span class="o">:</span>       <span class="m">20</span><span class="p">,</span>
	<span class="p">})</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">ctx</span><span class="o">.</span><span class="n">Err</span><span class="p">()</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">log</span><span class="o">.</span><span class="n">Ctx</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span><span class="o">.</span><span class="n">Debug</span><span class="p">()</span><span class="o">.</span><span class="n">Msg</span><span class="p">(</span><span class="s">"context cancelled. stopping sqs reader"</span><span class="p">)</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Ctx</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span><span class="o">.</span><span class="n">Error</span><span class="p">()</span><span class="o">.</span><span class="n">Err</span><span class="p">(</span><span class="n">err</span><span class="p">)</span><span class="o">.</span><span class="n">Msg</span><span class="p">(</span><span class="s">"error while calling ReceiveMessage"</span><span class="p">)</span>
		<span class="k">continue</span>
	<span class="p">}</span>

	<span class="c">// if resp is nil, log and continue</span>
	<span class="k">if</span> <span class="n">resp</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Ctx</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span><span class="o">.</span><span class="n">Info</span><span class="p">()</span><span class="o">.</span><span class="n">Msg</span><span class="p">(</span><span class="s">"sqs receiveMessage response is nil"</span><span class="p">)</span>
		<span class="k">continue</span>
	<span class="p">}</span>

	<span class="c">// iterate and push each message to the buffered worker channel</span>
	<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">msg</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">resp</span><span class="o">.</span><span class="n">Messages</span> <span class="p">{</span>
		<span class="n">workerChan</span> <span class="o">&lt;-</span> <span class="o">&amp;</span><span class="n">msg</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Do you find anything weird in the snippet above? Look carefully again inside the <code class="language-plaintext highlighter-rouge">for</code> loop. Still nothing? I also didn’t notice anything and started the debugger. Turns out, I’m bitten by the golang’s <a href="https://github.com/golang/go/issues/20733">loop variable</a> gotcha. Go by design handles <code class="language-plaintext highlighter-rouge">for</code> loop variables differently than other languages. If you pass the loop variables by reference in golang, it will always point to the last element. So when <code class="language-plaintext highlighter-rouge">&amp;msg</code> was passed to the <code class="language-plaintext highlighter-rouge">workerChan</code> we sent the last element of <code class="language-plaintext highlighter-rouge">resp.Messages</code> 10 times and ignored the first 9 elements.</p>

<p>This explains why the 120k limit was also hit. When the worker goroutine consumes a message, it’ll process it and delete it from the queue as required by SQS. Since we’re deleting 1 in 10 messages and ignoring the other 9, after the configured visibility-timeout interval these messages will be returned again in <code class="language-plaintext highlighter-rouge">ReceiveMessage</code> API call. The below code fixes this issue:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">msg</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">resp</span><span class="o">.</span><span class="n">Messages</span> <span class="p">{</span>
	<span class="n">m</span> <span class="o">:=</span> <span class="n">msg</span> <span class="c">// assign it to a temp variable</span>
	<span class="n">workerChan</span> <span class="o">&lt;-</span> <span class="o">&amp;</span><span class="n">m</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This fix won’t be needed in newer versions of go as per the <a href="https://github.com/golang/go/issues/60078">latest discussions</a>, but you never know when you need a rock solid observability for your entire infrastructure. In my case, having dead letter queues and alerts for <code class="language-plaintext highlighter-rouge">NumberOfMessagesNotVisible</code> helped narrowing down this issue faster. Always have dead-letter queues configured kids!</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="golang" /><summary type="html"><![CDATA[This entry discusses how I debugged duplicate message deliveries from SQS and why having dead letter queues are important.]]></summary></entry><entry><title type="html">Self Hosting Setup - 2023</title><link href="https://gowtham.dev/blog/self-hosting-setup-2023.html" rel="alternate" type="text/html" title="Self Hosting Setup - 2023" /><published>2023-02-19T18:24:12+00:00</published><updated>2023-02-19T18:24:12+00:00</updated><id>https://gowtham.dev/blog/self-hosting-setup-2023</id><content type="html" xml:base="https://gowtham.dev/blog/self-hosting-setup-2023.html"><![CDATA[<p>You don’t know when an algorithm from a big corporation will end up blacklisting you and removes access to your online accounts. I have read <a href="https://news.ycombinator.com/item?id=13657282">a</a> <a href="https://news.ycombinator.com/item?id=24952202">lot</a> <a href="https://news.ycombinator.com/item?id=31815289">of</a> hacker news threads where big corporations end up disabling access to accounts without any way to contact support.</p>

<p>Though, I’m not currently self-hosting email (<em>and I strongly recommend you not to</em>) for <a href="https://cfenollosa.com/blog/after-self-hosting-my-email-for-twenty-three-years-i-have-thrown-in-the-towel-the-oligopoly-has-won.html">good reasons</a>, it’s a good start to consider hosting the easy things. I own a <a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/">raspberry pi model</a> 4B which I thankfully got before the pandemic.</p>

<p><strong>Here’s the list of items I self-host:</strong></p>

<ul>
  <li><a href="https://pi-hole.net">piHole</a> - a rock solid DNS server. It blocks trackers across the network for all of our devices. <a href="https://adguard.com/en/adguard-home/overview.html">Adguard Home</a> is also a good alternative.</li>
  <li><a href="https://github.com/DNSCrypt/dnscrypt-proxy">dnscrypt-proxy</a> - An upstream for piHole since it doesn’t support DNS-over-HTTPS. I’m currenty using <a href="https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/">cloudflare’s DoH</a> as upstream.</li>
  <li><a href="https://miniflux.app">miniflux</a> - simple, clean and minimalistic RSS reader. I follow and read about other bloggers and news publications via miniflux.</li>
  <li><a href="https://syncthing.net">syncthing</a> - synchronises and backs up my data across all devices and servers. Mobile clients are also available.</li>
  <li><a href="https://www.plex.tv">plex</a> - local media server. I mostly consume new content from online streaming platforms but there are some older movies I have in mp4/mkv format.</li>
  <li><a href="https://transmissionbt.com">tranmission-daemon</a> - a lightweight torrent client with a web UI. Since I have a higher FUP plan from ACT (<em>~4TB per month</em>), I seed linux distros and <a href="https://academictorrents.com">scientific papers</a>.</li>
</ul>

<p><strong>Things I wish to self-host in future:</strong></p>

<ul>
  <li><a href="https://github.com/juanfont/headscale">headscale</a> - open-source co-ordination server for <a href="https://tailscale.com">tailscale</a> network. I use tailscale for all peer-to-peer communications between my devices and I want to move away to headscale in future.</li>
  <li><a href="https://maddy.email">maddy</a> - all in one email server. I currently use <a href="https://fastmail.com">Fastmail</a>, but I want to go with a self-hosting solution.</li>
</ul>

<p>If you’ve found any interesting software that I’m missing out on, please let me know.</p>]]></content><author><name>Gowtham Gopalakrishnan</name><email>your-email@domain.com</email></author><category term="blog" /><category term="self-hosting" /><category term="servers" /><summary type="html"><![CDATA[Software that I self host as of 2023.]]></summary></entry></feed>