<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Georgios Kampitakis</title>
    <description>The latest articles on DEV Community by Georgios Kampitakis (@gkampitakis).</description>
    <link>https://dev.to/gkampitakis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F213852%2F2d4bc551-5b0c-40cd-bc4f-02e93d8ffcf7.png</url>
      <title>DEV Community: Georgios Kampitakis</title>
      <link>https://dev.to/gkampitakis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gkampitakis"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Tue, 04 Feb 2025 18:05:52 +0000</pubDate>
      <link>https://dev.to/gkampitakis/-4c5</link>
      <guid>https://dev.to/gkampitakis/-4c5</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gkampitakis" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F213852%2F2d4bc551-5b0c-40cd-bc4f-02e93d8ffcf7.png" alt="gkampitakis"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/gkampitakis/tracking-down-high-memory-usage-in-nodejs-2lbn" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Tracking down high memory usage in Node.js&lt;/h2&gt;
      &lt;h3&gt;Georgios Kampitakis ・ Dec 14 '24&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#performance&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#learning&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>node</category>
      <category>performance</category>
      <category>learning</category>
      <category>javascript</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sat, 14 Dec 2024 19:53:36 +0000</pubDate>
      <link>https://dev.to/gkampitakis/-2d0e</link>
      <guid>https://dev.to/gkampitakis/-2d0e</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/gkampitakis" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F213852%2F2d4bc551-5b0c-40cd-bc4f-02e93d8ffcf7.png" alt="gkampitakis"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/gkampitakis/tracking-down-high-memory-usage-in-nodejs-2lbn" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Tracking down high memory usage in Node.js&lt;/h2&gt;
      &lt;h3&gt;Georgios Kampitakis ・ Dec 14&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#performance&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#learning&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Tracking down high memory usage in Node.js</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sat, 14 Dec 2024 19:28:57 +0000</pubDate>
      <link>https://dev.to/gkampitakis/tracking-down-high-memory-usage-in-nodejs-2lbn</link>
      <guid>https://dev.to/gkampitakis/tracking-down-high-memory-usage-in-nodejs-2lbn</guid>
      <description>&lt;p&gt;In this article, I will share my approach of tracking down and fixing high memory usage in Node.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Context&lt;/li&gt;
&lt;li&gt;
Approach

&lt;ul&gt;
&lt;li&gt;Understand the code&lt;/li&gt;
&lt;li&gt;Replicate the issue in isolation&lt;/li&gt;
&lt;li&gt;Capture profiles from staging services&lt;/li&gt;
&lt;li&gt;Verify the fix&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Results&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Recently I got a ticket with the title "fix memory leak issue in library x". The description included a Datadog dashboard showing a dozen services suffering from high memory usage and eventually crashing with OOM (out of memory) errors, and they all had the x library in common.&lt;/p&gt;

&lt;p&gt;I was introduced quite recently to the codebase (&amp;lt;2 weeks) which was what made the task challenging and also worth sharing.&lt;/p&gt;

&lt;p&gt;I started working with two pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a library that is used by all the services that is causing high memory usage and it involves redis (redis was included in the name of the library).&lt;/li&gt;
&lt;li&gt;The list of the services that were affected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the dashboard that was linked to the ticket:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuu1o2z150mqtmmpzz4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuu1o2z150mqtmmpzz4w.png" alt="Initial state dashboard" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Services were running on Kubernetes and it was obvious  that services were accumulating memory over time until they reached the memory limit, crash (reclaim memory) and start again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;p&gt;In this section, I will share how I approached the task at hand,identifying the culprit of the high memory usage and later fixing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand the code
&lt;/h3&gt;

&lt;p&gt;As I was fairly new to the codebase, I first wanted to understand the code, what the library in question did and how it was supposed to be used, hoping with this process it would be easier to identify the problem. Unfortunately, there was no proper documentation but from reading the code and searching how services were utilising the library I was able to understand the gist of it. It was a library wrapping around redis streams and exposing convenient interfaces for event production and consumption. After spending a day and a half reading the code, I was not able to grasp all the details and how the data flowed due to code structure and complexity (a lot of class inheritance and &lt;a href="https://rxjs.dev/" rel="noopener noreferrer"&gt;rxjs&lt;/a&gt; which I am not familiar with).&lt;/p&gt;

&lt;p&gt;So I decided to put a pause on reading and try to spot the problem while observing the code in action and collect telemetry data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicate the issue in isolation
&lt;/h3&gt;

&lt;p&gt;As there was no profiling data available (e.g. continuous profiling) which would help me investigate further I decided to replicate the issue locally and try to capture memory profiles.&lt;/p&gt;

&lt;p&gt;I found a couple of ways for capturing memory profiles in Node.js:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/learn/diagnostics/memory/using-heap-snapshot" rel="noopener noreferrer"&gt;Using Heap Snapshot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/learn/diagnostics/memory/using-heap-profiler" rel="noopener noreferrer"&gt;Using Heap Profiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/nodejs/profiling" rel="noopener noreferrer"&gt;Performance Profiling JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://clinicjs.org/" rel="noopener noreferrer"&gt;Clinic.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With no leads on where to look, I decided to run what I thought was the most "data intensive" part of the library, the redis streams producer and consumer. I built two simple services that would produce and consume data from a redis stream and I proceeded with capturing memory profiles and comparing the results over time. Unfortunately, after a couple of hours of producing load to the services and comparing the profiles I was not able to spot any difference in the memory consumption in any of the two services, everything was looking normal. The library was exposing a bunch of different interfaces and ways of interacting with the redis streams. It became clear to me it would be more complicated than what I expected to replicate the issue, especially with my limited domain-specific knowledge of the actual services.&lt;/p&gt;

&lt;p&gt;So the question was, how can I find the right moment and conditions to capture the memory leak?&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture profiles from staging services
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier the easiest and most convenient way to capture memory profiles would be having continuous profiling on the actual services that were being affected, an option I did not have. I started to investigate how to at least leverage our staging services (they were facing the same high memory consumption) that would allow me to capture the data that I needed with no additional effort.&lt;/p&gt;

&lt;p&gt;I started searching for a way to connect Chrome DevTools to one of the running pods and capture heap snapshots over time. I knew the memory leak was happening in staging, so if I could capture that data I was hoping I would be able to spot at least some of the hotspots. To my surprise, there is a way to do just that.&lt;/p&gt;

&lt;p&gt;The process for doing this&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the Node.js debugger on the pod by sending a &lt;code&gt;SIGUSR1&lt;/code&gt; signal to the node process on your pod.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;nodejs-pod-name&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-SIGUSR1&lt;/span&gt; &amp;lt;node-process-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;More at about Node.js signals at &lt;a href="https://nodejs.org/api/process.html#signal-events" rel="noopener noreferrer"&gt;Signal Events&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If successful you should see a log from your service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Debugger listening on ws://127.0.0.1:9229/....
For &lt;span class="nb"&gt;help&lt;/span&gt;, see: https://nodejs.org/en/docs/inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Expose the port that the debugger is listening on, locally by running
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward &amp;lt;nodejs-pod-name&amp;gt; 9229
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Connect Chrome Devtools to the debugger you enabled on previous steps. Visit &lt;code&gt;chrome://inspect/&lt;/code&gt; and there you should see in the list of targets your Node.js process:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkijz1eywsrz50k28m1ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkijz1eywsrz50k28m1ul.png" alt="Chrome Devtool" width="666" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;if not, then make sure your target discovery settings are properly set up&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1i592ozpe9eqe1070nv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1i592ozpe9eqe1070nv.png" alt="Discovery settings" width="413" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can start capturing snapshots overtime (the period depends on the time required for the memory leak to happen) and compare them. Chrome DevTools provides a very convenient way to do this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can find more information about memory snapshots and Chrome Dev Tools at &lt;a href="https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots" rel="noopener noreferrer"&gt;Record heap snapshot&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When creating a snapshot, all other work in your main thread is stopped. Depending on the heap contents it could even take more than a minute. The snapshot is built in memory, so it can double the heap size, resulting in filling up entire memory and then crashing the app.&lt;/p&gt;

&lt;p&gt;If you're going to take a heap snapshot in production, make sure the process you're taking it from can crash without impacting your application's availability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://nodejs.org/en/learn/diagnostics/memory/using-heap-snapshot#warning" rel="noopener noreferrer"&gt;From Node.js docs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So back to my case, selecting two snapshots for comparing and sorting by delta I got what you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6riyp02relifjgku900c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6riyp02relifjgku900c.png" alt="Delta Comparison" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see the biggest positive delta was happening on the &lt;code&gt;string&lt;/code&gt; constructor which meant the service had created a lot of strings between the two snapshots but they were still in use. Now the question was where those were created and who was referencing them. Good thing that the snapshots captured contained this information as well called &lt;code&gt;Retainers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26amqgb24rocxuzd3s13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26amqgb24rocxuzd3s13.png" alt="Retainers" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While digging into the snapshots and the never shrinking list of strings I noticed a pattern of strings that were resembling an id. Clicking on them I could see the chain objects that were referencing them - aka &lt;code&gt;Retainers&lt;/code&gt;. It was an array called &lt;code&gt;sentEvents&lt;/code&gt; from a class name that I could recognise from the library code. Tadaaa we have our culprit, an only growing list of ids that by this point I assumed were are never released. I captured a bunch of snapshots overtime and this was the single place that kept reappearing as a hotspot with a big positive delta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify the fix
&lt;/h3&gt;

&lt;p&gt;With this information, instead of trying to understand the code to its entirety, I needed to focus on the purpose of the array, when it got populated and when cleared. There was a single place where the code was &lt;code&gt;pushing&lt;/code&gt; items to the array and another one where the code was &lt;code&gt;popping&lt;/code&gt; them out which narrowed the scope of the fix. &lt;/p&gt;

&lt;p&gt;It's safe to assume that the array was not emptied when it should. Skipping the details of the code, what was basically happening is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The library was exposing interfaces for either consuming, producing events or producing and consuming events.&lt;/li&gt;
&lt;li&gt;When it was both consuming and producing events it needed to track the events that the process itself produced in order to skip them and not re-consume them. The &lt;code&gt;sentEvents&lt;/code&gt; was populated when producing and cleared when while trying to consume it would skip the messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Can you see where this is going? ? When services were using the library only for producing events the &lt;code&gt;sentEvents&lt;/code&gt; would still get populated with all the events but there was no code path (consumer) for clearing it. &lt;/p&gt;

&lt;p&gt;I patched the code to only track events on producer,consumer mode and deployed to staging. Even with the staging load it was clear that the patch helped with reducing the high memory usage and didn't introduce any regressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;When the patch was deployed on production, the memory usage was drastically reduced and reliability of the service was improved (no more OOMs).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39fmp5a60br1rnf9cl72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39fmp5a60br1rnf9cl72.png" alt="Memory usage per pod and OOMs" width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A nice side effect was the 50% reduction in the number of pods needed to handle the same traffic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjurchhgeo7n0zkfzd2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjurchhgeo7n0zkfzd2i.png" alt="Pod reduction" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a great learning opportunity for me regarding tracking memory issues in Node.js and further familiarising myself with the tools available.&lt;/p&gt;

&lt;p&gt;I thought it best not to dwell on the details of  each tool as that would deserve a separate post, but I hope this is  a good starting point for anyone that's interested in learning more about this topic or facing similar issues.&lt;/p&gt;

</description>
      <category>node</category>
      <category>performance</category>
      <category>learning</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Memory leaks in Go</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Mon, 18 Mar 2024 17:00:44 +0000</pubDate>
      <link>https://dev.to/gkampitakis/memory-leaks-in-go-3pcn</link>
      <guid>https://dev.to/gkampitakis/memory-leaks-in-go-3pcn</guid>
      <description>&lt;p&gt;In this post, we are going to have a look at&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is a memory leak&lt;/li&gt;
&lt;li&gt;Why are memory leaks bad&lt;/li&gt;
&lt;li&gt;Common causes for memory leaks in Go&lt;/li&gt;
&lt;li&gt;Methods for identifying memory leaks&lt;/li&gt;
&lt;li&gt;Investigate memory leaks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a memory leak
&lt;/h2&gt;

&lt;p&gt;Let's understand what memory leak is. &lt;em&gt;Reading from &lt;a href="https://en.wikipedia.org/wiki/Memory_leak" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Go has a garbage collector that does a very good job managing memory for us, tracking down memory that is no longer used and can be returned back to the system. Still there are some cases where we can end up with either memory leaks or our system needing excessive memory to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are memory leaks bad
&lt;/h2&gt;

&lt;p&gt;Before looking on cases that can make a Go program "waste" memory let's first discuss why it can be bad having memory leaks and why we need to be mindful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System reliability and stability is affected. 
The system might behave unpredictably and crash from OOM (out of memory) errors.&lt;/li&gt;
&lt;li&gt;Inefficient resource utilization resulting in increased costs.&lt;/li&gt;
&lt;li&gt;Performance degradation. As memory leaks accumulate the performance of the system may degrade, affecting responsiveness and efficiency. Memory leaks also create additional pressure on the garbage collector resulting also in increased CPU usage.&lt;/li&gt;
&lt;li&gt;Difficult to track down and debug, especially for bigger systems with complex interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common causes for memory leaks in Go
&lt;/h2&gt;

&lt;p&gt;In this section we will see some cases that can cause memory leaks. Some of those cases are specific to Go and some others more general.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unbounded resource creation
&lt;/h3&gt;

&lt;p&gt;Creating resources without a limit can be seens as a type of memory leak. &lt;/p&gt;

&lt;p&gt;For example if we have a cache that only ever grows our service eventually will crash with OOM (out of memory) error.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The solution would be to restrict how many items can a cache hold (e.g. TTL).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This applies to many resources, like Goroutines (more about this later), http connections or open files. We should always be mindful to have limits on creating resources.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Something you should keep in mind regarding maps in Go. They don't shrink after elements are deleted &lt;a href="https://github.com/golang/go/issues/20135" rel="noopener noreferrer"&gt;runtime: shrink map as elements are deleted #20135&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Long lived references
&lt;/h3&gt;

&lt;p&gt;Keeping references to objects that your service no longer needs can result to memory leaks as the garbage collector sees the references and cannot free the memory. Some cases where you can keep references unintentionally is with global variables, never ending goroutines, maps or not resetting pointers.&lt;/p&gt;

&lt;p&gt;There is a special case in Go for holding references unintenionally with reslicing a slice (this also applies to strings). In our example we have a function &lt;code&gt;readDetails&lt;/code&gt; that opens a big file and returns only a portion of it, so we slice the data &lt;code&gt;[]byte&lt;/code&gt; and return it. In Go slices share the same underlying memory block (&lt;a href="https://go.dev/blog/slices-intro" rel="noopener noreferrer"&gt;Go Slices: usage and internals&lt;/a&gt;). That means, that even if we are only interested in a very small subset of the data we are still keeping in memory, (referenced) the whole file.&lt;/p&gt;

&lt;p&gt;The correct way here, would be to call &lt;code&gt;bytes.Clone(data[5:10])&lt;/code&gt; so that the data will no longer be referenced and subsequently collected by the garbage collector.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You can also read more information for Go Slices at&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/slices-intro" rel="noopener noreferrer"&gt;Go Slices: usage and internals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/generic-slice-functions" rel="noopener noreferrer"&gt;Robust generic functions on slices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Goroutines
&lt;/h3&gt;

&lt;p&gt;Go runtime is doing a great job in spawning and managing goroutines, a lightweight thread, but as mentioned on the 'Unbounded resource creation' section, (realistically) there is a limit of how many goroutines you can have at any time, bounded to the underlying system your service is running on.&lt;/p&gt;

&lt;p&gt;More over, iniside Goroutines you can allocate or reference resources. So you need to make sure that your goroutines are properly terminated and memory is finally released.&lt;/p&gt;

&lt;p&gt;Let's see the example below. We have a function that creates a new goroutine every second to execute a task, allocates a big data slice does some processing and then hangs forever. This code, has two problems &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;creates an unbounder number of goroutines&lt;/li&gt;
&lt;li&gt;due to not termination of those goroutines resources allocated are never going to be released&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Deferring function calls
&lt;/h3&gt;

&lt;p&gt;Deferring a big number of functions can also cause a type memory of memory leak. The most common mistake is when you call defer inside a loop but the defer calls are pushed into a stack and only executed in lifo order at the end of the calling function.&lt;/p&gt;

&lt;p&gt;In the example below we are processing files in a for loop and calling &lt;code&gt;.Close&lt;/code&gt; on defer. The problem with below code is if we call &lt;code&gt;processManyFiles&lt;/code&gt; with a lot of files we are only going to close all the files after we are done processing.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The correct way to handle this case would be to break opening and processing the file to a separate function so when called in the for loop each file we be closed before moving to the next one.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Not stopping &lt;code&gt;time.Ticker&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Update(14-08-2024): This is no longer true after the release of Go 1.23. Read more on &lt;a href="https://go.dev/doc/go1.23#timer-changes" rel="noopener noreferrer"&gt;timer changes&lt;/a&gt; in the release notes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, another common case for memory leak is the &lt;code&gt;time.Ticker&lt;/code&gt;. As stated in the docs the resources are only released when stopping the ticker.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Correct way to handle a &lt;code&gt;time.Ticker&lt;/code&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Methods for identifying memory leaks
&lt;/h2&gt;

&lt;p&gt;The most straightforward method to notice if your service is having a memory leak is to observe memory consumption over time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In distributed systems, observability is the ability to collect data about programs' execution, modules' internal states, and the communication among components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://w.wiki/9Crn" rel="noopener noreferrer"&gt;From Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are some memory patterns, that if you notice them on your service, you should get at least suspicious that something is wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklya90fz0f2yrb9a70fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklya90fz0f2yrb9a70fr.png" alt="Memory increasing graph" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqh9kthanj6qruc4fx2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqh9kthanj6qruc4fx2s.png" alt="Out of Memory error graph" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the first image, we are seeing a memory graph of our service per pod and the memory is only growing. There are some big spikes in memory, the memory goes down but never back to the previous value. This is a good indication that something is kept into memory and never released. So we should investigate what exactly is using this memory and why.&lt;/p&gt;

&lt;p&gt;On the second image, this pattern with the abrupt (cliff like) reduction in memory appears when our service reaches the system's memory limit and crashes with an out of memory error.&lt;/p&gt;

&lt;p&gt;You need to keep in mind that in both cases just by noticing this pattern can't be enough to conclude that your service is having memory leak. On the 1st image your service can still be on the start phase where it needs to allocate memory for initialisation or "warming caches" and on the second there is no doubt this is an OOM error but it can be the case you have wrongly sized your service so there is not enough memory. In both cases you should look deeper to identify the causes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigate memory leaks
&lt;/h2&gt;

&lt;p&gt;So if you are suspecting a memory leak where you should look at? Where is the culprit? You could answer simply, take a look at your code, and that would be a valid answer. But in a complex system with external factors and interactions it's not that straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profiling
&lt;/h3&gt;

&lt;p&gt;Profiling can help,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Profiling tools analyze the complexity and costs of a Go program such as its memory usage and frequently called functions to identify the expensive sections of a Go program.&lt;br&gt;
Profiling is useful for identifying expensive or frequently called sections of code. The Go runtime provides profiling data in the format expected by the pprof visualization tool. The profiling data can be collected during testing via go test or endpoints made available from the net/http/pprof package. Users need to collect the profiling data and use pprof tools to filter and visualize the top code paths.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://go.dev/doc/diagnostics" rel="noopener noreferrer"&gt;From Go Diagnostics&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The profile type we are interested in for investigating memory leaks is the &lt;code&gt;heap&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Heap profile reports memory allocations samples used to monitor current and historical memory usage, and to check for memory leaks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Go has two ways for capturing profiles, either via tests or enabling profiling over HTTP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-cpuprofile&lt;/span&gt; cpu.prof &lt;span class="nt"&gt;-memprofile&lt;/span&gt; mem.prof &lt;span class="nt"&gt;-bench&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"net/http/pprof"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":6060"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are not going to get into more details on how you can setup all this as it would be a whole new post you can read more at &lt;br&gt;
&lt;a href="https://pkg.go.dev/net/http/pprof" rel="noopener noreferrer"&gt;net/http/pprof&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's worth knowing that go tool pprof supports different types of visualisations for analysing the profiling data you have captured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-text.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-text.png" width="800" height="155"&gt;&lt;/a&gt;&lt;br&gt;Listing of the most expensive calls as text.
  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-dot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-dot.png" width="800" height="335"&gt;&lt;/a&gt;&lt;br&gt;Visualization of the most expensive calls as a graph.
  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-weblist.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgo.dev%2Fimages%2Fdiagnostics%2Fpprof-weblist.png" width="800" height="366"&gt;&lt;/a&gt;&lt;br&gt;Visualization of the most expensive calls as weblist.
  &lt;/p&gt;

&lt;p&gt;But there is a problem with this process, it's too manual and not always so straightforward. When do you capture the profiles? Do you know under what conditions the memory leak happens is it on a dev environment or in production under specific load?&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous profiling
&lt;/h3&gt;

&lt;p&gt;With Continuous Profiling we can collect metrics and data from our Services while serving real traffic.&lt;/p&gt;

&lt;p&gt;"Continuous profiling is the process of collecting application performance data in a production environment and making the data available to developers"&lt;/p&gt;

&lt;p&gt;There are monitoring platforms like &lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt; where with very few steps you can onboard your service and provide tools to slice and dice on the submitted data to investigate memory leaks or other various performance issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F925f6m7ou8xmqsdee49b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F925f6m7ou8xmqsdee49b.jpeg" alt="Datadog Flamegraph" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and personally one of the best features, comparing profile to a previous time period or even a previous version. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz27qkgq1xj4reamhzgu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz27qkgq1xj4reamhzgu6.png" alt="Profile comparison" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The graphs are from &lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The gopher art is from &lt;a href="https://github.com/MariaLetta" rel="noopener noreferrer"&gt;MariaLetta&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>learning</category>
      <category>performance</category>
      <category>observability</category>
    </item>
    <item>
      <title>HTTP Connection churn in GO</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sat, 21 Oct 2023 16:03:33 +0000</pubDate>
      <link>https://dev.to/gkampitakis/http-connection-churn-in-go-34pl</link>
      <guid>https://dev.to/gkampitakis/http-connection-churn-in-go-34pl</guid>
      <description>&lt;p&gt;In this post, we are going to have a look at&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is connection churn&lt;/li&gt;
&lt;li&gt;Common causes for connection churn in Go&lt;/li&gt;
&lt;li&gt;Benefits of utilizing persistent connections&lt;/li&gt;
&lt;li&gt;
Methods for identifying connection churn
and finally&lt;/li&gt;
&lt;li&gt;
Demo that will help us better understand how to fix it connection churn in Go.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is connection churn
&lt;/h2&gt;

&lt;p&gt;While searching for a definition of what connection churn is I came across with&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A system is said to have a high connection churn when its rate of newly opened connections is consistently high and its rate of closed connections is consistently high&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or in other words when a service frequently opens and closes network connections instead of efficiently utilizing persistent connections. Persistent connections enable multiple HTTP requests and responses to be sent and received over a single TCP connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common causes for connection churn in Go
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Not Reading and Closing HTTP body
&lt;/h3&gt;

&lt;p&gt;One of the most common causes and easy to miss, is not reading Body to completion and not closing after done with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii65wxjoiiu6yoh80w5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii65wxjoiiu6yoh80w5c.png" alt="read and close body in go" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may be counterintuitive to read the body especially when you don't care about the contents of it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a linter that can at least catch the &lt;a href="https://github.com/timakin/bodyclose" rel="noopener noreferrer"&gt;bodyclose&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Using the default HTTP client
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Go HTTP defaults are not great for heavy use&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internally Go HTTP client is using an LRU cache for persistent connection pooling. Instead of closing a socket connection after an HTTP request, if the connection is marked as reusable, it will be put back into the pool and later if you try to make another request before the idle connection timeout (90 seconds by default) it will re-use the existing connection from the pull rather than creating a new one. This will keep the number of total socket connections low as long as the pool doesn't fill up. If the pool is full the client will create a new connection and use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the size of the connection pool?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrqptscll4mmcz3jsno3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrqptscll4mmcz3jsno3.png" alt="Default http client configuration" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the DeafaultTransport in HTTP Client &lt;code&gt;MaxIdleConns&lt;/code&gt; sets the size of the pool to 100 connections, &lt;strong&gt;BUT&lt;/strong&gt; there is a caveat. &lt;code&gt;DefaultMaxIdleConnsPerHost&lt;/code&gt; is set to 2, what that means is even if the pool has a size of 100 connections, there is a cap of two open sockets per host. &lt;em&gt;We will see this later in action with an example.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of utilizing persistent connections
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;From Wikipedia for &lt;a href="https://en.wikipedia.org/wiki/HTTP_persistent_connection#Advantages" rel="noopener noreferrer"&gt;HTTP persistent connection#Advantages&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced latency in subsequent requests (no handshaking and no slow start). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example of a production service, after fixing an issue that allowed reusing connections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmspos25vgxw5msqy3hsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmspos25vgxw5msqy3hsb.png" width="800" height="262"&gt;&lt;/a&gt;&lt;br&gt;A drop in latency from ~1 sec to 400 ms
  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced CPU usage and round-trips because of fewer new connections and TLS handshakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjttc1ebo1t8v9uj1tv1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjttc1ebo1t8v9uj1tv1x.png" width="800" height="262"&gt;&lt;/a&gt;&lt;br&gt;same production service as before.
  &lt;/p&gt;

&lt;p&gt;TLS handshakes can be CPU-intensive by nature, so reducing the newly established connections and inherently spend less time in TLS handshakes reduces your service's CPU usage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enables HTTP pipelining of requests and responses.&lt;/li&gt;
&lt;li&gt;Reduced network congestion (fewer TCP connections).&lt;/li&gt;
&lt;li&gt;Errors can be reported without the penalty of closing the TCP connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Methods for identifying connection churn
&lt;/h2&gt;

&lt;p&gt;Let's see some methods that can help us identify that a service is having a high connection churn.&lt;/p&gt;

&lt;h3&gt;
  
  
  High CPU usage on TLS
&lt;/h3&gt;

&lt;p&gt;A good indicator that your service might be suffering from connection churn is when a high percentage of CPU is spent on TLS handshake.&lt;/p&gt;

&lt;p&gt;The below CPU profile is an example of this phenomenon. ~38% of CPU time is spent on establishing TLS connections,a size comparable to the time spent running our service code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ixssdqdepe394imx2hp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ixssdqdepe394imx2hp.png" width="800" height="461"&gt;&lt;/a&gt;&lt;br&gt;same production service as before.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;Monitoring platforms like &lt;a href="https://www.datadoghq.com" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt; provide tools for Network Monitoring.&lt;/p&gt;

&lt;p&gt;An example below is a view from &lt;a href="https://www.datadoghq.com/product/network-monitoring/network-performance-monitoring/" rel="noopener noreferrer"&gt;Network Performance Monitoring&lt;/a&gt; where a service is having high number of connections established and closed per second. This clearly shows a service suffering from connection churn.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36v31p3v3rw1qwz6y4yq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36v31p3v3rw1qwz6y4yq.png" width="800" height="183"&gt;&lt;/a&gt;&lt;br&gt;same production service as before.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Trace
&lt;/h3&gt;

&lt;p&gt;Another method is using &lt;a href="https://pkg.go.dev/net/http/httptrace" rel="noopener noreferrer"&gt;httptrace&lt;/a&gt; which provides mechanisms to trace events within HTTP client requests.&lt;/p&gt;

&lt;p&gt;An example usage where we print &lt;a href="https://pkg.go.dev/net/http/httptrace#GotConnInfo" rel="noopener noreferrer"&gt;connection info&lt;/a&gt;, which includes info on whether the connection was &lt;code&gt;Reused&lt;/code&gt; or not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6ykfq31zv8yck5ibxe8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6ykfq31zv8yck5ibxe8.png" alt="HTTP trace example usage" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Netstat
&lt;/h3&gt;

&lt;p&gt;Finally, we can use netstat which is a useful tool for inspecting connection statuses. In the command below we are running netstat every second and counting the number of all TCP connections at port &lt;code&gt;8080&lt;/code&gt; that are in status &lt;code&gt;time_wait&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The high number of &lt;code&gt;time_wait&lt;/code&gt; statuses indicates the creation of lots of short-lived TCP connections.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://linux.die.net/man/8/netstat" rel="noopener noreferrer"&gt;man netstat&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TIME_WAIT&lt;br&gt;
The socket is waiting after close to handle packets still in the network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7snlux1uqdakp4aajvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7snlux1uqdakp4aajvw.png" alt="Netstat cli example usage" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;It's time for the demo with 2 examples in Go that can cause connection churn.&lt;/p&gt;

&lt;p&gt;The setup is simple, we are going to run a server that will accept requests and a client sending requests from a specified number of goroutines (you can find the code below).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;To observe the state of our connections, in two different shells, we are going to run netstat&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s1"&gt;'netstat -n -p tcp | grep -i 8080 | grep -i time_wait | wc -l'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s1"&gt;'netstat -n -p tcp | grep -i 8080'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;to see the list of connections in more detail.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example-1
&lt;/h3&gt;

&lt;p&gt;In the first example, we run the code as is, without reading or closing the body and with the default Go HTTP client configuration.&lt;/p&gt;

&lt;p&gt;After around ~16k requests we are getting errors&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Get &lt;span class="s2"&gt;"http://localhost:8080/message"&lt;/span&gt;: dial tcp &lt;span class="o"&gt;[&lt;/span&gt;::1]:8080: connect: connection refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;we can see the shell running netstat also having a high number of sockets at &lt;code&gt;time_wait&lt;/code&gt; state.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every 1.0s: netstat -n -p tcp | grep -i 8080

tcp4       0      0  127.0.0.1.49478        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49477        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49481        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49482        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49486        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49485        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49489        127.0.0.1.8080         TIME_WAIT
tcp4       0      0  127.0.0.1.49490        127.0.0.1.8080         TIME_WAIT
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What's happening is, because we are not reading and closing any of the requests, eventually we run out of available ephemeral ports and we are not able to establish new connections.&lt;/p&gt;

&lt;p&gt;We can fix this by closing and reading the request. After rerunning the code with the fix we can see on the netstat list with TPC connections only two connections as &lt;code&gt;ESTABLISHED&lt;/code&gt; and the sockets with status &lt;code&gt;time_wait&lt;/code&gt; remaining 0.&lt;/p&gt;

&lt;p&gt;All our requests are handled by those two persistent connections successfully.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Every 1.0s: netstat &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tcp | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; 8080

tcp4       0      0  127.0.0.1.8080         127.0.0.1.61273        ESTABLISHED
tcp4       0      0  127.0.0.1.8080         127.0.0.1.61272        ESTABLISHED
tcp4       0      0  127.0.0.1.61273        127.0.0.1.8080         ESTABLISHED
tcp4       0      0  127.0.0.1.61272        127.0.0.1.8080         ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Example-2
&lt;/h3&gt;

&lt;p&gt;For the second example, we are increasing the number of goroutines from 2 to 3, with this change essentially doing 3 concurrent requests.&lt;/p&gt;

&lt;p&gt;Running the code now with the new update, we are seeing a steady increase of sockets in &lt;code&gt;time_wait&lt;/code&gt; status and two established connections as before.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Every 1.0s: netstat &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tcp | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; 8080

tcp4       0      0  127.0.0.1.8080         127.0.0.1.49213        ESTABLISHED
tcp4       0    102  127.0.0.1.49213        127.0.0.1.8080         ESTABLISHED
tcp4       0    128  127.0.0.1.8080         127.0.0.1.49198        ESTABLISHED
tcp4       0    102  127.0.0.1.49198        127.0.0.1.8080         ESTABLISHED
tcp4       0      0  127.0.0.1.8080         127.0.0.1.61787        TIME_WAIT
tcp4       0      0  127.0.0.1.8080         127.0.0.1.61751        TIME_WAIT
tcp4       0      0  127.0.0.1.61787        127.0.0.1.8080         
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We are successfully reusing two of the connections but still creating new connections to close the short after. &lt;/p&gt;

&lt;p&gt;The question of why is this happening can be answered by &lt;code&gt;DefaultMaxIdleConnsPerHost&lt;/code&gt;. By increasing the concurrency to 3 requests, on every iteration we are able to reuse two of the connections (&lt;code&gt;DefaultMaxIdleConnsPerHost&lt;/code&gt; is 2) but the 3rd connection can not be put into the idle pool due to &lt;code&gt;http: putIdleConn: too many idle connections for host&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the fix for this example would be adjusting &lt;code&gt;MaxIdleConnsPerHost&lt;/code&gt; to something &amp;gt; 2. Rerunning the code after the fix we can see now 3 established connections and zero sockets at &lt;code&gt;time_wait&lt;/code&gt; state.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Every 1.0s: netstat &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; tcp | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; 8080

tcp4       0    128  127.0.0.1.8080         127.0.0.1.52440        ESTABLISHED
tcp4       0      0  127.0.0.1.8080         127.0.0.1.52439        ESTABLISHED
tcp4       0      0  127.0.0.1.8080         127.0.0.1.52438        ESTABLISHED
tcp4       0    102  127.0.0.1.52440        127.0.0.1.8080         ESTABLISHED
tcp4       0    102  127.0.0.1.52439        127.0.0.1.8080         ESTABLISHED
tcp4       0    102  127.0.0.1.52438        127.0.0.1.8080         ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;


&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fstackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
          &lt;a href="https://stackoverflow.com/questions/72850372/in-golang-http-client-what-does-host-mean-in-maxconnsperhost-and-maxidleconnspe" rel="noopener noreferrer"&gt;
            In Golang http client, what does Host mean in MaxConnsPerHost and MaxIdleConnsPerHost?
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Jul  3 '22&lt;/span&gt;
            &lt;span&gt;Comments: &lt;/span&gt;
            &lt;span&gt;Answers: 3&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/72850372/in-golang-http-client-what-does-host-mean-in-maxconnsperhost-and-maxidleconnspe" rel="noopener noreferrer"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fstackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          3
        &lt;/div&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fstackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;In MaxConnsPerHost and MaxIdleConnsPerHost in GO HTTP package, is "Host" a domain(i.e., yahoo.com) or an IP address? The actual meaning of "Host" will affect my settings for the connection pool.&lt;/p&gt;

    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://stackoverflow.com/questions/72850372/in-golang-http-client-what-does-host-mean-in-maxconnsperhost-and-maxidleconnspe" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Question&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;






&lt;p&gt;The graphs are from &lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The demo was inspired by &lt;a href="http://tleyden.github.io/blog/2016/11/21/tuning-the-go-http-client-library-for-load-testing/" rel="noopener noreferrer"&gt;Tuning the Go HTTP Client Settings for Load Testing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The gopher art is from &lt;a href="https://github.com/MariaLetta" rel="noopener noreferrer"&gt;MariaLetta&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>learning</category>
      <category>performance</category>
      <category>http</category>
    </item>
    <item>
      <title>Advent of Code: Investigating performance improvements in Go</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sat, 26 Aug 2023 21:16:55 +0000</pubDate>
      <link>https://dev.to/gkampitakis/advent-of-code-investigating-performance-improvements-in-go-2l07</link>
      <guid>https://dev.to/gkampitakis/advent-of-code-investigating-performance-improvements-in-go-2l07</guid>
      <description>&lt;p&gt;I often enjoy trying to solve previous years' Advent of Code puzzles. If you don't know what &lt;a href="https://adventofcode.com/about" rel="noopener noreferrer"&gt;Advent of Code&lt;/a&gt; is make sure you check it out.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TLDR: Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post is about puzzle &lt;a href="https://adventofcode.com/2021/day/6" rel="noopener noreferrer"&gt;2021/day-6&lt;/a&gt; I was trying to solve that's not very interesting as a puzzle but I found it to be a really good example for exploring how to deep dive into your code's performance, find potential bottlenecks and improve it with tools that Go provides.&lt;/p&gt;

&lt;p&gt;Each puzzle on Advent of Code contains two parts. You need to solve the 1st to move to the 2nd one and usually the 2nd is a more complicated or demanding variation of the 1st. This is the approach we are going to follow on this post as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;The tools we are going to use are &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/testing#hdr-Benchmarks" rel="noopener noreferrer"&gt;Benchmarks&lt;/a&gt; for measuring and analyzing the performance of go code with help from &lt;a href="https://golang.google.cn/pkg/cmd/pprof/" rel="noopener noreferrer"&gt;go tool pprof&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/golang.org/x/perf/cmd/benchstat" rel="noopener noreferrer"&gt;Benchstat&lt;/a&gt; computes statistical summaries and compares benchmarks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1
&lt;/h2&gt;

&lt;p&gt;I am not going to repeat the problem here for brevity, but if you read the puzzle carefully you can probably come up with a trivial solution as I did the 1st time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;","&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MustAtoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;MustAtoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Atoi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tempFish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
                &lt;span class="n"&gt;tempFish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempFish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tempFish&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The approach for solving it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parsing the input and turning it into a list of numbers and then iterating for the number of &lt;code&gt;days&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;for each day I decrease each fish's timer, if the timer reaches zero resets to &lt;code&gt;6&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;and on a temporary list add a new fish with timer &lt;code&gt;8&lt;/code&gt; that at the end of the day is going to be appended on the main list. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's pretty much it, successfully solves the Part 1 and it's fast enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ok advent-of-code/2021/go/day-6 0.226s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of this post though is not to just solve the puzzle.&lt;/p&gt;

&lt;p&gt;Before updating the code we need a benchmark of the current implementation as a base &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Always measure before optimizing.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkSolution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResetTimer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReportAllocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benchmarks
&lt;/h3&gt;

&lt;p&gt;We can run the benchmark with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./... &lt;span class="nt"&gt;-benchmem&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;^&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt; ^Bench &lt;span class="nt"&gt;-memprofile&lt;/span&gt; naive.out &lt;span class="nt"&gt;-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 | &lt;span class="nb"&gt;tee &lt;/span&gt;naive.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will produce output similar to the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkg: advent-of-code/2021/go/day-6
BenchmarkSolution
BenchmarkSolution-10                 235           5210983 ns/op        31653556 B/op       1025 allocs/op
BenchmarkSolution-10                 231           5086091 ns/op        31653547 B/op       1025 allocs/op
BenchmarkSolution-10                 225           5146135 ns/op        31653549 B/op       1025 allocs/op
BenchmarkSolution-10                 231           5191141 ns/op        31653571 B/op       1025 allocs/op
BenchmarkSolution-10                 229           5104518 ns/op        31653558 B/op       1025 allocs/op
PASS
ok      advent-of-code/2021/go/day-6    8.847s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: the flag &lt;code&gt;-memprofile&lt;/code&gt; memory profiling is enabled which can help to identify memory-related bottlenecks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can use the interactive CLI interface for navigating the profiling data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go tool pprof naive.out
&lt;span class="c"&gt;# and then list the data for Solve function with &lt;/span&gt;
list Solve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff72vj9gyzzqjkok6ehfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff72vj9gyzzqjkok6ehfw.png" alt="go tool pprof output screenshot highlighted" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this view we can dig into profiling data collected from the benchmark and it's easy to spot the 3 big offenders that allocate memory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reducing unnecessary memory allocations leads to improved performance (e.g memory allocation overhead, garbage collection cache friendly behaviour).&lt;/p&gt;

&lt;p&gt;Normally while investigating performance issues, you run the benchmarks against every code change to verify your changes and that you actually improving your code, but for brevity we are going to discuss all the improvements and then run the benchmark to see the final result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Improvements
&lt;/h3&gt;

&lt;p&gt;The first offender is &lt;code&gt;parseInput&lt;/code&gt; which is allocating an N sized integer slice, where N is the input of the puzzle. Instead of slice of integers &lt;code&gt;[]int&lt;/code&gt; we can use a slice of bytes &lt;code&gt;[]byte&lt;/code&gt; so we can store the same information with less memory &lt;br&gt;
 (and as a bonus we skip parsing from string to integer). We can do this due to the puzzle's constraints, input numbers are from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second offender can be seen if we do &lt;code&gt;list parseInput&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk772l7651v46pjeibz1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk772l7651v46pjeibz1m.png" alt="screenshot of profiling data" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data := strings.Split(strings.Split(s, "\n")[0], ",")&lt;/code&gt; is as well allocating space that we throw right after. Instead, it's better parsing char by char. &lt;/p&gt;

&lt;p&gt;Here is the updated &lt;code&gt;parseInput&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;','&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third offender is the &lt;code&gt;tempFish&lt;/code&gt; slice. On every iteration, we are populating the slice only to throw it on the next step and create a new one and we are keep reallocating memory. We can reuse the same slice across iterations by just clearing the slice instead of creating a new one.&lt;/p&gt;

&lt;p&gt;The updated code for the &lt;code&gt;Solve&lt;/code&gt; function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tempFish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'0'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'6'&lt;/span&gt;
                &lt;span class="n"&gt;tempFish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tempFish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tempFish&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;tempFish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempFish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;Rerunning the benchmarks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt; &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;benchmem&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;=^&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bench&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;Bench&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;memprofile&lt;/span&gt; &lt;span class="n"&gt;improved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tee&lt;/span&gt; &lt;span class="n"&gt;improved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;generates the following, much improved, output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkg: advent-of-code/2021/go/day-6
BenchmarkSolution
BenchmarkSolution-10                 388           2945361 ns/op         2193282 B/op         45 allocs/op
BenchmarkSolution-10                 410           2950991 ns/op         2193280 B/op         45 allocs/op
BenchmarkSolution-10                 409           2917432 ns/op         2193278 B/op         45 allocs/op
BenchmarkSolution-10                 404           2968872 ns/op         2193277 B/op         45 allocs/op
BenchmarkSolution-10                 409           2917535 ns/op         2193281 B/op         45 allocs/op
PASS
ok      advent-of-code/2021/go/day-6    7.725s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the benchmark is already looking better but &lt;code&gt;benchstat&lt;/code&gt; can help to answer "by how much"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;benchstat naive.txt improved.txt 
name         old &lt;span class="nb"&gt;time&lt;/span&gt;/op    new &lt;span class="nb"&gt;time&lt;/span&gt;/op    delta
Solution-10    5.20ms ± 7%    2.94ms ± 1%  &lt;span class="nt"&gt;-43&lt;/span&gt;.51%  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.008 &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5+5&lt;span class="o"&gt;)&lt;/span&gt;

name         old alloc/op   new alloc/op   delta
Solution-10    31.7MB ± 0%     2.2MB ± 0%  &lt;span class="nt"&gt;-93&lt;/span&gt;.07%  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.008 &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5+5&lt;span class="o"&gt;)&lt;/span&gt;

name         old allocs/op  new allocs/op  delta
Solution-10     1.02k ± 0%     0.04k ± 0%  &lt;span class="nt"&gt;-95&lt;/span&gt;.61%  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.008 &lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5+5&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the new solution is 43.51% faster than the old one and requires 93.07% less memory to do the same job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2
&lt;/h2&gt;

&lt;p&gt;For the second part, the puzzle is running for 256 days. Even with the previous improvements though, at least on my machine, I can't get a result (after waiting for 5 minutes I gave up).&lt;/p&gt;

&lt;p&gt;It's time to think the solution a bit more carefully and use a more efficient algorithm, as the solution is &lt;code&gt;O(days*fish)&lt;/code&gt; and the number of fish increases on every iteration. We need to find a way to keep the number of fish constant. &lt;/p&gt;

&lt;p&gt;An example input of the puzzle&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3,4,3,1,2,2,1,1 ....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We know that a fish can have a timer from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;8&lt;/code&gt; and notice all fish that have the same timer are "synchronized".This means we can group all the fish that have the same timer and change the &lt;code&gt;O(days*fish)&lt;/code&gt; to &lt;code&gt;O(days*8)&lt;/code&gt; which is equal to &lt;code&gt;O(days)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can achieve this by using a map, where the keys are the timer values, hence &lt;code&gt;0&lt;/code&gt;-&lt;code&gt;8&lt;/code&gt;, and the values are the number of fish that have this as their timer. That would turn the above input to the following structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  0: 0,
  1: 3,
  2: 2,
  3: 2,
  4: 1,
  5: 0,
  6: 0,
  7: 0,
  8: 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the size of the map will be constant regardless of the number of fish. Every day we are increasing the timers per group instead of one fish at a time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9j0dpf1y8hixmjo2qhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9j0dpf1y8hixmjo2qhs.png" alt="Visual representation of map structure" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are keeping the findings from Part 1 and the new adjusted code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tmpFish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;resetCount&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;resetCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tmpFish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;tmpFish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;resetCount&lt;/span&gt;
        &lt;span class="n"&gt;tmpFish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;resetCount&lt;/span&gt;

        &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tmpFish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmpFish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;
        &lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpFish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;parseInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fish&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'\n'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;','&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="sc"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fish&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;This solution returns a result - if you recall 5 minutes were not enough to get a result previously - running the benchmark against it produces the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pkg: advent-of-code/2021/go/day-6
BenchmarkSolution
BenchmarkSolution-10               34027             33479 ns/op            2373 B/op         85 allocs/op
BenchmarkSolution-10               35720             33435 ns/op            2374 B/op         85 allocs/op
BenchmarkSolution-10               35521             33486 ns/op            2374 B/op         85 allocs/op
BenchmarkSolution-10               35900             33392 ns/op            2373 B/op         85 allocs/op
BenchmarkSolution-10               35878             33553 ns/op            2373 B/op         85 allocs/op
PASS
ok      advent-of-code/2021/go/day-6    7.960s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the &lt;code&gt;benchstat&lt;/code&gt; with the previous solution reveals even more impressive results than before improving the performance of the code up to 98.86% and using 99.89% less memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;benchstat improved.txt new_algo.txt
name         old time/op    new time/op    delta
Solution-10    2.94ms ± 1%    0.03ms ± 0%  -98.86%  (p=0.008 n=5+5)

name         old alloc/op   new alloc/op   delta
Solution-10    2.19MB ± 0%    0.00MB ± 0%  -99.89%  (p=0.008 n=5+5)

name         old allocs/op  new allocs/op  delta
Solution-10      45.0 ± 0%      85.0 ± 0%  +88.89%  (p=0.008 n=5+5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus point
&lt;/h2&gt;

&lt;p&gt;What if I told you there is one more &lt;strong&gt;one-line&lt;/strong&gt; improvement that could give an increase in the code's performance by another 95.90% and reach zero memory allocations?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;benchstat  new_algo.txt new_algo_array.txt 
name         old time/op    new time/op    delta
Solution-10    33.5µs ± 0%     1.4µs ± 2%   -95.90%  (p=0.008 n=5+5)

name         old alloc/op   new alloc/op   delta
Solution-10    2.37kB ± 0%    0.00kB       -100.00%  (p=0.008 n=5+5)

name         old allocs/op  new allocs/op  delta
Solution-10      85.0 ± 0%       0.0       -100.00%  (p=0.008 n=5+5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can try changing the data structure from &lt;code&gt;map[uint8]int&lt;/code&gt; to &lt;code&gt;[]int&lt;/code&gt;, the rest of the code will still work. Why is that you might be wondering or you might have already guessed but I leave it as an exercise, use the profiling data and the answer is there.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;hint: You can run benchmarks with &lt;code&gt;-cpuprofile&lt;/code&gt; instead of &lt;code&gt;-memprofile&lt;/code&gt; and check the top entries with &lt;code&gt;go tool pprof&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Feel free to post the answer in the comments 📝&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The code for this puzzle was trivial, but the approach of measuring, finding bottlenecks, improving code and repeating until being satisfied with the results will be the same even in production setups. Learn to leverage the tools that the language and the ecosystem provide and you won't be disappointed.&lt;/p&gt;




&lt;p&gt;The gopher art on the image is from &lt;a href="https://github.com/MariaLetta" rel="noopener noreferrer"&gt;MariaLetta&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>performance</category>
      <category>adventofcode</category>
      <category>learning</category>
    </item>
    <item>
      <title>Kubernetes: Dynamic Admission Control</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sat, 13 May 2023 18:33:44 +0000</pubDate>
      <link>https://dev.to/gkampitakis/kubernetes-dynamic-admission-control-1f9p</link>
      <guid>https://dev.to/gkampitakis/kubernetes-dynamic-admission-control-1f9p</guid>
      <description>&lt;p&gt;This is a demo for &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/" rel="noopener noreferrer"&gt;Kuberentes Dynamic Admission Control&lt;/a&gt;, exploring various concepts, capabilities and constraints, it's following &lt;a href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/" rel="noopener noreferrer"&gt;A Guide to Kubernetes Admission Controllers&lt;/a&gt; which contains a lot of information around Admission Controllers and Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Dynamic Admission Control
&lt;/h2&gt;

&lt;p&gt;Borrowing the definion from the &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/" rel="noopener noreferrer"&gt;Kubernetes page&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In addition to &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/" rel="noopener noreferrer"&gt;compiled-in admission plugins&lt;/a&gt;, admission plugins can be developed as extensions and run as webhooks configured at runtime.&lt;/p&gt;

&lt;p&gt;Admission webhooks are HTTP callbacks that receive admission requests and do something with them. You can define two types of admission webhooks, &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook" rel="noopener noreferrer"&gt;validating admission webhook&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook" rel="noopener noreferrer"&gt;mutating admission webhook&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can &lt;a href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/#why-do-i-need-admission-controllers" rel="noopener noreferrer"&gt;read some of the use cases&lt;/a&gt; for admission controllers.&lt;/p&gt;

&lt;p&gt;In the demo we will focus on validating admission webhooks, but most of the principles apply on mutating as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;We are going to run the demo locally on &lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;minikube&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You will need some tools installed for following along&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone code from &lt;a href="https://github.com/gkampitakis/k8s-dac-demo" rel="noopener noreferrer"&gt;Github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;minikube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/#kubectl" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/" rel="noopener noreferrer"&gt;docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;go&lt;/a&gt; (&lt;em&gt;optional if you want to play with webhook server code&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up minikube
&lt;/h3&gt;

&lt;p&gt;We are going to spin up a minikube cluster by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# start kubernetes cluster locally&lt;/span&gt;
minikube start

&lt;span class="c"&gt;# enable needed addons&lt;/span&gt;
minikube addons &lt;span class="nb"&gt;enable &lt;/span&gt;metrics-server
minikube addons &lt;span class="nb"&gt;enable &lt;/span&gt;registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are setting up a local image registry so we don't rely on pushing/pulling the webhook server container image on a remote registry e.g. &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;docker hub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run local registry&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host alpine ash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"apk add socat &amp;amp;&amp;amp; socat TCP-LISTEN:5000,reuseaddr,fork TCP:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;minikube ip&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:5000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the components for running and testing the webhook now should be ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building and Deploying
&lt;/h3&gt;

&lt;p&gt;The next step is to build the Webhook Docker image and deploy it alongside with the Admission Webhook configuration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You will need to build and push the container to your local registry by running:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make docker-image-local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;then you are ready to deploy the Validating Admision Webhook in your local minikube cluster with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;make deploy&lt;/code&gt; command does a couple of actions&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates TLS Certificates, since a webhook must be served via HTTPS, we need proper certificates for the server. [ &lt;a href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/#tls-certificates" rel="noopener noreferrer"&gt;More&lt;/a&gt; ] &lt;/li&gt;
&lt;li&gt;Creates a dedicated namespace for putting the Webhook Server &lt;code&gt;Deployment&lt;/code&gt; and the &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt;. This useful if you want to skip your Webhook from "validating itself".&lt;/li&gt;
&lt;li&gt;Creates a Kubernetes TLS secret with the generated key and certificate so the Webhook server can access. &lt;/li&gt;
&lt;li&gt;Creates a &lt;code&gt;Service&lt;/code&gt; for reaching the Webhook Server &lt;code&gt;Pods&lt;/code&gt; at port &lt;code&gt;443&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Creates the Webhook Server &lt;code&gt;Deployment&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally after everything is in place creates the &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Webhook Server
&lt;/h3&gt;

&lt;p&gt;The Webhook Server we are deploying is a simple Go HTTP server that accepts &lt;code&gt;AdmissionReview&lt;/code&gt; POST requests at &lt;code&gt;/validate&lt;/code&gt;, verifys the payload is correct.&lt;/p&gt;

&lt;p&gt;Then checks the resource passed for review, in the demo case we are only care about &lt;code&gt;POD&lt;/code&gt; creation, if it contains a &lt;code&gt;team&lt;/code&gt; label set up. You can configure rules for what review requests are directed to your Webhook in &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; object. More details on &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-rules" rel="noopener noreferrer"&gt;Matching requests: rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If this label &lt;code&gt;team: &amp;lt;value&amp;gt;&lt;/code&gt; is missing the Webhook Server will return either a warning that the lable is missing or a failure that prevents the &lt;code&gt;POD&lt;/code&gt; from scheduling depending on the &lt;code&gt;ALLOW_SCHEDULING&lt;/code&gt; env var.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"team"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;admissionReviewResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allowScheduling&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;allowScheduling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;admissionReviewResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warnings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"Team label not set on pod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;admissionReviewResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Failure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Team label not set on pod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Team label not set on pod: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the warning case you will see this message&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Warning: Team label not &lt;span class="nb"&gt;set &lt;/span&gt;on pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also it's a good practise to skip validating namespaces that are used by Kubernetes, e.g. &lt;code&gt;kube-public&lt;/code&gt; and &lt;code&gt;kube-system&lt;/code&gt;, as you might cause a disruption of you cluster if you deny scheduling of resources there.&lt;/p&gt;

&lt;p&gt;You can experiment with the Webhook Server &lt;a href="https://github.com/gkampitakis/k8s-dac-demo/blob/main/webhook-server/main.go" rel="noopener noreferrer"&gt;code&lt;/a&gt; do changes and redeploy it. You can build a new docker image and push it to the local registry we set up earlier with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make docker-image-local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then restart the deployment for creating new &lt;code&gt;PODS&lt;/code&gt; with the new image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout restart deploy/webhook-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying everything Works
&lt;/h3&gt;

&lt;p&gt;Okay let's check that everything works and see the Validating Webhook in practise.&lt;/p&gt;

&lt;p&gt;First we can check that the Webhook Server is scheduled and healty. You can do it with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; webhook-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the output should be similar to this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NAME                              READY   STATUS    RESTARTS   AGE
webhook-server-79c79b5877-d624n   1/1     Running   0          11m
webhook-server-79c79b5877-dpf4b   1/1     Running   0          11m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then if we try to schedule &lt;a href="https://github.com/gkampitakis/k8s-dac-demo/blob/main/deployment/busy-box.yaml" rel="noopener noreferrer"&gt;a simple&lt;/a&gt; &lt;code&gt;POD&lt;/code&gt; it should fail to schedule with the error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment/busy-box.yaml

Error from server: error when creating &lt;span class="s2"&gt;"deployment/busy-box.yaml"&lt;/span&gt;: admission webhook &lt;span class="s2"&gt;"webhook-server.webhook-demo.svc"&lt;/span&gt; denied the request: Team label not &lt;span class="nb"&gt;set &lt;/span&gt;on pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add a team label in the pod spec and try again successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Namespace Selector
&lt;/h3&gt;

&lt;p&gt;You might have noticed the &lt;a href="https://github.com/gkampitakis/k8s-dac-demo/blob/1b5648cc00b33257cd42efd5343a1c3fd2cc9a4e/webhook-server/main.go#L164-L172" rel="noopener noreferrer"&gt;code&lt;/a&gt; in the Webhook Server contains logic for skipping validation for a list of namespaces.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; supports natively limiting requests that reach to your Webhook Server depending on &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector" rel="noopener noreferrer"&gt;namespaceSelector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I couldn't find a way to whitelist namespaces by their name. For using namespaceSelector you can add this to &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webhook-skip&lt;/span&gt;
      &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DoesNotExist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then mark all namespaces you want to skip with this label&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl label ns webhook-demo webhook-skip=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reliability and availability
&lt;/h3&gt;

&lt;p&gt;Dynamic Admission Controller can be on the critical path, depending how you your Webhook configured e.g. can block various actions on Kubernetes resources.&lt;/p&gt;

&lt;p&gt;This is a non exhaustive list of things you can do to increase the reliability and the availability of your admission controller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; The Dynamic Admission Controller is backed by an HTTP Server and a &lt;code&gt;Deployment&lt;/code&gt;. This means all concepts for making a stateless deployment more reliable in Kubernetes ( &lt;em&gt;if it is hosted in Kubernetes&lt;/em&gt; )

&lt;ul&gt;
&lt;li&gt;Use load balancer in front of your Webhook server that provides &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#availability" rel="noopener noreferrer"&gt;High Availability&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/#:~:text=with%20rolling%20updates.-,Rolling%20updates%20allow%20Deployments%27%20update%20to%20take%20place%20with%20zero%20downtime%20by%20incrementally%20updating%20Pods%20instances%20with%20new%20ones.,-The%20new%20Pods" rel="noopener noreferrer"&gt;Rolling Updates&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/" rel="noopener noreferrer"&gt;Pod Disruption Budget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rollback to an earlier Deployment if the current state is not stable&lt;/li&gt;
&lt;li&gt;Autoscalling for matching increasing demand [ &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/" rel="noopener noreferrer"&gt;HPA&lt;/a&gt; ]&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#avoiding-deadlocks-in-self-hosted-webhooks" rel="noopener noreferrer"&gt;Avoiding deadlock in self hosted webhooks&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#avoiding-operating-on-the-kube-system-namespace" rel="noopener noreferrer"&gt;Avoiding operating on the kube-system namespace&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;It is recommended that admission webhooks should evaluate as quickly as possible (typically in milliseconds), since they add to API request latency. It is encouraged to use a small &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts" rel="noopener noreferrer"&gt;timeout for webhooks&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;Setting &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy" rel="noopener noreferrer"&gt;Failure Policy&lt;/a&gt; depending on your needs.&lt;/li&gt;

&lt;li&gt;And last but not least always monitoring and observing your Webhooh Server status and the Kube API server status. The API Server exposes Prometheus metrics from the &lt;code&gt;/metrics&lt;/code&gt; endpoint 
e.g. &lt;code&gt;apiserver_admission_webhook_admission_duration_seconds_sum&lt;/code&gt;,&lt;code&gt;apiserver_admission_webhook_rejection_count&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Dynamic Admission Controllers provide great benefits for security and governance across your Kubernetes Cluster. This demo can help you get started building and deploying a Validating Admission Webhook.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers" rel="noopener noreferrer"&gt;Dynamic Admission Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/" rel="noopener noreferrer"&gt;A Guide to Kubernetes Admission Controllers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gkampitakis/k8s-dac-demo" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
      <category>minikube</category>
      <category>docker</category>
    </item>
    <item>
      <title>Go Snaps: The Latest Updates and Improvements</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Mon, 27 Feb 2023 21:33:16 +0000</pubDate>
      <link>https://dev.to/gkampitakis/snapshot-testing-in-golang-updated--4h89</link>
      <guid>https://dev.to/gkampitakis/snapshot-testing-in-golang-updated--4h89</guid>
      <description>&lt;p&gt;More than a year after the previous post about Go Snaps and there have been some improvements and a lot bug fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt; is a Go library that makes snapshot testing in Go easier. &lt;/p&gt;

&lt;p&gt;Snapshot testing is an important part of the unit testing process. It simplifies writing assertions for complex or dynamic output which can be time consuming and can catch regressions or unintended changes that otherwise might went unnoticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updates to go-snaps
&lt;/h2&gt;

&lt;p&gt;The improvements and updates in the library can be put into 3 categories, visual changes, new features and bug fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Changes
&lt;/h3&gt;

&lt;p&gt;Visual changes are always the one that make the more difference in the experience of using the library. So I focused on making the diff output better more easy to distinguish where the regression is happening in a long output.&lt;/p&gt;

&lt;p&gt;An example diff&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u7sbw0lca7if95runad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5u7sbw0lca7if95runad.png" alt="small diff before" width="800" height="331"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pyytfvhe22pgojh1lef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pyytfvhe22pgojh1lef.png" alt="small diff after" width="718" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or with long diffs&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nv47tr25loh9zo8k8h3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nv47tr25loh9zo8k8h3.png" alt="big diff before" width="800" height="244"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk41meo25cwga1tkos3cm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk41meo25cwga1tkos3cm.png" alt="big diff after" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second important visual change was on the the reporting of the library and the summary of the tests.&lt;/p&gt;

&lt;p&gt;An example when Go Snaps identifies snapshots that are no longer used there is a message prompting cleaning them and showing which tests are marked for deletion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq04sh0pwixs6i4kx43ct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq04sh0pwixs6i4kx43ct.png" alt="summary-obsolete" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally now the library supports &lt;a href="https://no-color.org" rel="noopener noreferrer"&gt;NO_COLOR&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  New Features
&lt;/h3&gt;

&lt;p&gt;Two new big features landed on Go Snaps on is complimentary to the other.&lt;/p&gt;

&lt;p&gt;It's &lt;code&gt;snaps.MatchJSON&lt;/code&gt; and property Matchers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MatchJSON&lt;/code&gt; method as the name indicates is for capturing snapshots for JSON structured data. It can accept a valid json in form of &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;[]byte&lt;/code&gt; or whatever value can be passed successfully on &lt;code&gt;json.Marshal&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;`{"user":"mock-user","age":10,"email":"mock@email.com"}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"user":"mock-user","age":10,"email":"mock@email.com"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mock-email"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;JSON will be saved in snapshot in pretty format for better readability.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Having a method that accepts structured data also enabled building another feature that can be useful in snapshot testing, property matchers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MatchJSON&lt;/code&gt; can accept a list of arguments called &lt;code&gt;Matchers&lt;/code&gt;. &lt;code&gt;Matchers&lt;/code&gt; are functions that can act as property matchers and test values on a specific path of the JSON.&lt;/p&gt;

&lt;p&gt;Currently Go Snaps has two builtin matchers&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;match.Any&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match.Custom&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;match.Any&lt;/code&gt; can be convenient when some fields are dynamic and you have different snapshots on every run, e.g. containing a date or time field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"should ignore fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;`{"user":"mock-user","age":10,"nested":{"now":["%s"]}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nested.now.0"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the resulting snapshot will be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "age": 10,
 "nested": {
  "now": [
   "&amp;lt;Any value&amp;gt;"
  ]
 },
 "user": "mock-user"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so you can see we are ignoring the dynamic value there.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;match.Custom&lt;/code&gt; allows you to build your own matcher that either validates or mutates a specific path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"struct marshalling"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"email"`&lt;/span&gt;
        &lt;span class="n"&gt;Keys&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="s"&gt;`json:"keys"`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"mock-user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mock-user@email.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected []interface{} but got %T"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected less than 5 keys"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom matcher provides a lot of expressiveness and can support very complex use cases. In the future I am thinking of adding support for common used &lt;code&gt;Matchers&lt;/code&gt; like &lt;code&gt;type&lt;/code&gt;, or &lt;code&gt;Date&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you are using a &lt;code&gt;Matcher&lt;/code&gt; and you think it should added in the library feel free to open an &lt;a href="https://github.com/gkampitakis/go-snaps/issues" rel="noopener noreferrer"&gt;issue&lt;/a&gt; or contribute.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For more information around &lt;a href="https://github.com/gkampitakis/go-snaps#matchers" rel="noopener noreferrer"&gt;Matchers&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Fixes
&lt;/h3&gt;

&lt;p&gt;As more people start using the library noticed some edge cases ( or not that edge 😅 ) that the library was either not handling gracefully or was misbehaving. &lt;/p&gt;

&lt;p&gt;I am not going into much detail but I will share the list of pull requests if you are interested.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fix: update isSkippedFile, replace file absolute match w/ regex match by @gparasyris in &lt;a href="https://github.com/gkampitakis/go-snaps/pull/48" rel="noopener noreferrer"&gt;#48&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fix: permission denied when running from test suite by &lt;a class="mentioned-user" href="https://dev.to/gkampitakis"&gt;@gkampitakis&lt;/a&gt; in &lt;a href="https://github.com/gkampitakis/go-snaps/pull/44" rel="noopener noreferrer"&gt;#44&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fix: issue with updating snapshots with expand chars, fixes &lt;a href="https://github.com/gkampitakis/go-snaps/issues/30" rel="noopener noreferrer"&gt;#30&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fix: issue with marking non child tests as skipped &lt;a href="https://github.com/gkampitakis/go-snaps/compare/v0.3.0...v0.3.1" rel="noopener noreferrer"&gt;v0.3.0...v0.3.1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fix: issue with newlines when removing obsolete snaps &lt;a href="https://github.com/gkampitakis/go-snaps/compare/v0.3.0...v0.3.1" rel="noopener noreferrer"&gt;v0.3.0...v0.3.1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;fix: ignore end chars --- inside snapshot in &lt;a href="https://github.com/gkampitakis/go-snaps/pull/26" rel="noopener noreferrer"&gt;#26&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The latest changes and updates to &lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt; improved and simplified using snapshot testing into unit testing workflow. Alongside the bug fixes it makes the library more reliable for people to use it and rely on catching issues and potential regressions.&lt;/p&gt;




&lt;p&gt;If you liked or found &lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt; useful you can leave a ⭐️.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Go Unit Test Coverage</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Fri, 20 Jan 2023 18:21:05 +0000</pubDate>
      <link>https://dev.to/gkampitakis/golang-coverage-29cb</link>
      <guid>https://dev.to/gkampitakis/golang-coverage-29cb</guid>
      <description>&lt;p&gt;Go has really good support for unit testing out of the box. With the &lt;code&gt;go test&lt;/code&gt; CLI and the &lt;code&gt;testing&lt;/code&gt; std library you can do almost *everything.&lt;/p&gt;

&lt;p&gt;One of the niceties it provides is code coverage. You can run your tests with a flag &lt;code&gt;-cover&lt;/code&gt; or you can even have to collect the coverprofile and spin up an interactive UI and check your code and what branches are covered with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run tests and create a coverprofile&lt;/span&gt;
go &lt;span class="nb"&gt;test&lt;/span&gt; ./... &lt;span class="nt"&gt;-coverprofile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cover.out
&lt;span class="c"&gt;# open the interactive UI to check the Coverage Repor&lt;/span&gt;
go tool cover &lt;span class="nt"&gt;-html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cover.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something similar to this for your code&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8262u1j1ky8k401203r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8262u1j1ky8k401203r.png" alt="Coverage Report UI" width="800" height="708"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The red colour indicates that those branches are not covered at all by your unit tests and then with shades of green, you can see how much your code is covered.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is this post about then
&lt;/h2&gt;

&lt;p&gt;All this is good, what Go provides right away is at least amazing. But what I have found missing, is coverage threshold.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is coverage threshold?
&lt;/h3&gt;

&lt;p&gt;If you are coming from js ecosystem you might have encountered this in jest. With a coverage threshold, if your tests are less than a specified percentage, even if they are successful they will fail.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a long debate about whether high code coverage or what percentage of code coverage is the best. This post is not covering this debate. If you are interested I could do it in a separate post and share my thoughts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Code coverage "tracking" can be useful, in many different scenarios. Adding a new feature, you write your unit tests but you miss covering some branches. If your coverage goes below the acceptable threshold you will be notified right away when running your tests. If someone is contributing to your team's project, it can show that you are expecting at least some basic unit tests for the code he wrote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coverage
&lt;/h3&gt;

&lt;p&gt;So I took the opportunity and wrote a small util library, that does exactly that.&lt;/p&gt;

&lt;p&gt;Given a threshold number if your package is below that, then it will fail your tests&lt;/p&gt;

&lt;p&gt;You can find the code and the library in &lt;a href="https://github.com/gkampitakis/coverage" rel="noopener noreferrer"&gt;coverage&lt;/a&gt;. Have a look and tell me what you think.&lt;/p&gt;

&lt;p&gt;* &lt;em&gt;Of course some useful libraries can help you with boilerplate code like &lt;a href="https://github.com/stretchr/testify" rel="noopener noreferrer"&gt;testify&lt;/a&gt; or &lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt; (shameless plug 😅)&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Thanks for reading ❤️&lt;/p&gt;

</description>
      <category>career</category>
      <category>mentorship</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Check file's git history even if renamed/moved</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sun, 15 Jan 2023 19:19:25 +0000</pubDate>
      <link>https://dev.to/gkampitakis/check-files-git-history-even-if-renamedmoved-13m0</link>
      <guid>https://dev.to/gkampitakis/check-files-git-history-even-if-renamedmoved-13m0</guid>
      <description>&lt;p&gt;From time to time, I find myself that I want to rename or move a file to a different folder, but I am reluctant. The reason is the git history of a file is more important than you might think, so moving it or renaming it, will mean you are going to lose the file history.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check a file history even if renamed/moved
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git&lt;/code&gt; has a really useful flag that can help us.&lt;/p&gt;

&lt;p&gt;You can check a file's history with &lt;code&gt;git log&lt;/code&gt; and pass the &lt;a href="https://git-scm.com/docs/git-log#Documentation/git-log.txt---follow" rel="noopener noreferrer"&gt;--follow&lt;/a&gt; flag.&lt;/p&gt;

&lt;p&gt;In practise for a project &lt;a href="http://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt;, if you run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# passing --pretty=oneline for brevity 
git log --pretty=oneline internal/test/test.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will show&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;sha-id&amp;gt; chore: add examples for MatchJSON
&amp;lt;sha-id&amp;gt; feat: implement MatchJSON snapshot function (#49)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but running with the &lt;code&gt;--follow&lt;/code&gt; flag&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git log --pretty=oneline --follow internal/test/test.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will result in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;sha-id&amp;gt;  chore: add examples for MatchJSON
&amp;lt;sha-id&amp;gt;  feat: implement MatchJSON snapshot function (#49)
&amp;lt;sha-id&amp;gt;  chore: change file structure
&amp;lt;sha-id&amp;gt;  fix: update deps and add more tests (#39)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There can be more things before moving a file as you can see in this example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why file history can be important you ask
&lt;/h2&gt;

&lt;p&gt;In a commit ( or a series of commits ) there can be a lot of information that can explain decisions that were taken and why the code has evolved as it is right now. This information can be as valuable as the code itself so you can understand why I find &lt;code&gt;--follow&lt;/code&gt; useful.&lt;/p&gt;




&lt;p&gt;That was my TIL 😃&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>git</category>
    </item>
    <item>
      <title>Go Test supports shuffling tests</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Sun, 08 Jan 2023 22:42:36 +0000</pubDate>
      <link>https://dev.to/gkampitakis/go-test-supports-shuffling-tests-38e</link>
      <guid>https://dev.to/gkampitakis/go-test-supports-shuffling-tests-38e</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Randomize the execution order of tests and benchmarks with &lt;code&gt;-shuffle off,on,N&lt;/code&gt; flag.&lt;/p&gt;




&lt;p&gt;As a general guideline, it's bad for unit tests to depend on each other and the order of them messing with the results.&lt;/p&gt;

&lt;p&gt;A "trick" I have seen some codebases adopt to mitigate and catch issues with unit test results relying on order is instead of using arrays in table-driven tests using maps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
TestMyFunc(t *testing.T) {
    for name, _ := range map[string]struct{}{} {
        t.Run(name, func(t *testing.T) {
                ...
        })
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason behind this is, the order of map iteration is not guaranteed, so on different runs the tests might run in different order making it more difficult to rely on the order.&lt;/p&gt;

&lt;p&gt;But as of Go 1.17, you can "Randomize the execution order of tests and benchmarks."&lt;/p&gt;

&lt;p&gt;As &lt;code&gt;go help testflag&lt;/code&gt; states&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-shuffle off,on,N
        Randomize the execution order of tests and benchmarks.
        It is off by default. If -shuffle is set to on, then it will seed
        the randomizer using the system clock. If -shuffle is set to an
        integer N, then N will be used as the seed value. In both cases,
        the seed will be reported for reproducibility.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/issues/10655" rel="noopener noreferrer"&gt;testing: add shuffle flag #10655&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>todayilearned</category>
      <category>testing</category>
    </item>
    <item>
      <title>Snapshot testing in Go</title>
      <dc:creator>Georgios Kampitakis</dc:creator>
      <pubDate>Mon, 03 Jan 2022 22:08:21 +0000</pubDate>
      <link>https://dev.to/gkampitakis/snapshot-testing-in-golang-fpk</link>
      <guid>https://dev.to/gkampitakis/snapshot-testing-in-golang-fpk</guid>
      <description>&lt;p&gt;On my journey into learning Go, after some time spent learning the basic concepts I came across the testing. Go has a built-in testing command with &lt;code&gt;go test&lt;/code&gt; and a testing &lt;code&gt;package&lt;/code&gt; which combined can be used for unit testing.&lt;/p&gt;

&lt;p&gt;It is really useful having native testing capabilities but Go only provides the minimum for writing unit tests. There are no assertions like Mocha or Jest for javascript ( &lt;code&gt;Equal&lt;/code&gt;, &lt;code&gt;toBe&lt;/code&gt;), repeated steps (e.g. &lt;code&gt;beforeEach&lt;/code&gt;, &lt;code&gt;afterEach&lt;/code&gt;) or mocking/spying and as you would expect by now there is no snapshot testing.&lt;/p&gt;

&lt;p&gt;The community has built libraries on top of the standard &lt;code&gt;testing&lt;/code&gt; package to provide some of the functionality like &lt;a href="https://github.com/stretchr/testify" rel="noopener noreferrer"&gt;testify&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Snapshot testing
&lt;/h2&gt;

&lt;p&gt;Snapshot testing is mostly used when you want to test UIs, as stated in &lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;Jest Docs&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But snapshot testing can be useful in applications other than UI as well. Some examples could be &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database clients, where you want to diff multiple rows of data&lt;/li&gt;
&lt;li&gt;Backend services, where you can diff API responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and any application that can have a "state" and you would like to create a representation of it and compare it at a later time.&lt;/p&gt;

&lt;p&gt;For more information around snapshot testing, there is a wide range of resources you could read for more specific applications and where it fits in unit testing.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Go snaps
&lt;/h2&gt;

&lt;p&gt;So I decided to build a library that supports snapshot testing &lt;code&gt;go-snaps&lt;/code&gt; and practice my Go skills.&lt;/p&gt;

&lt;p&gt;It's simple to use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// example_test.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"testing"&lt;/span&gt;

  &lt;span class="s"&gt;"github.com/gkampitakis/go-snaps/snaps"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestExample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the 1st run of your tests this will create a file &lt;code&gt;__snapshots__/example_test.snap&lt;/code&gt; containing your snapshot&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotpktd94n5fls46eojni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotpktd94n5fls46eojni.png" alt="New snapshot written" width="800" height="92"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
[TestExample - 1]
Hello World
---

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On every other run, it will compare this snapshot with the parameters provided.&lt;/p&gt;

&lt;p&gt;So if we update the call with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"another value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;our test will fail and return a message containing a diff view of the snapshot and params.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm1uws2ar0vo9uozajll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm1uws2ar0vo9uozajll.png" alt="Diff error" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we are sure this is not a regression in our code and we want to update the snapshots we can call again the &lt;code&gt;go test&lt;/code&gt; with either flag &lt;code&gt;-snaps.update=true&lt;/code&gt; or the env variable &lt;code&gt;UPDATE_SNAPS=true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting obsolete snapshots
&lt;/h3&gt;

&lt;p&gt;Apart from the standard snapshot functionality that &lt;code&gt;go-snaps&lt;/code&gt; provides, it also can detect obsolete snapshots (snapshots that are no longer used). In order to enable this functionality you have to use &lt;a href="https://pkg.go.dev/testing#hdr-Main" rel="noopener noreferrer"&gt;TestMain&lt;/a&gt; and call the &lt;code&gt;snaps.Clean()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c"&gt;// After all tests have run `go-snaps` can check for not used snapshots&lt;/span&gt;
  &lt;span class="n"&gt;snaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for using &lt;code&gt;TestMain&lt;/code&gt;, is because &lt;code&gt;go-snaps&lt;/code&gt; needs to be sure that all tests have run so it can keep track of which snapshots were not called and mark them as obsolete. &lt;/p&gt;

&lt;p&gt;Again you can remove obsolete tests and files by calling the &lt;code&gt;-snaps.update=true&lt;/code&gt; or &lt;code&gt;UPDATE_SNAPS=true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb12vvhngu0nbp6tiwn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb12vvhngu0nbp6tiwn4.png" alt="Obsolete snapshots" width="800" height="264"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfingcdq31xvceanzmiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfingcdq31xvceanzmiu.png" alt="Remove obsolete snapshots" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In the repository, you can find &lt;a href="https://github.com/gkampitakis/go-snaps/tree/master/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt; and more information in &lt;a href="https://github.com/gkampitakis/go-snaps#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hope you find &lt;code&gt;go-snaps&lt;/code&gt; useful, any feedback is welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgments
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;Go Snaps&lt;/a&gt; used &lt;a href="https://jestjs.io/docs/snapshot-testing" rel="noopener noreferrer"&gt;Jest Snapshoting&lt;/a&gt; and &lt;a href="https://github.com/bradleyjkemp/cupaloy" rel="noopener noreferrer"&gt;Cupaloy&lt;/a&gt; as inspiration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jest is a full-fledged Javascript testing framework and has robust snapshoting features.&lt;/li&gt;
&lt;li&gt;Cupaloy is a great and simple Go snapshoting solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;




&lt;p&gt;If you liked or found &lt;a href="https://github.com/gkampitakis/go-snaps" rel="noopener noreferrer"&gt;go-snaps&lt;/a&gt; useful you can leave a ⭐️.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>unittesting</category>
      <category>jest</category>
    </item>
  </channel>
</rss>
