<?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>Forem: Fabien Herfray</title>
    <description>The latest articles on Forem by Fabien Herfray (@fabienherfray).</description>
    <link>https://forem.com/fabienherfray</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%2F311312%2F020b7e43-7fd0-40c5-8b24-36f62d51699b.jpg</url>
      <title>Forem: Fabien Herfray</title>
      <link>https://forem.com/fabienherfray</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fabienherfray"/>
    <language>en</language>
    <item>
      <title>Upload BeatStars beats to YouTube in 5s</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Thu, 30 Jan 2025 15:05:15 +0000</pubDate>
      <link>https://forem.com/fabienherfray/upload-beatstars-beats-to-youtube-in-5s-13hb</link>
      <guid>https://forem.com/fabienherfray/upload-beatstars-beats-to-youtube-in-5s-13hb</guid>
      <description>&lt;p&gt;I thought that similar tools were often too expensive and not made for beatmakers. So I redid it myself :)&lt;/p&gt;

&lt;p&gt;May be useful to someone:&lt;br&gt;
&lt;a href="https://producerfury.com/resources/upload-beats-to-youtube" rel="noopener noreferrer"&gt;https://producerfury.com/resources/upload-beats-to-youtube&lt;/a&gt;&lt;/p&gt;

</description>
      <category>music</category>
    </item>
    <item>
      <title>Exclusive beat license generator for music producers</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Mon, 12 Aug 2024 17:58:47 +0000</pubDate>
      <link>https://forem.com/fabienherfray/exclusive-beat-licensegenerator-for-music-producers-6pl</link>
      <guid>https://forem.com/fabienherfray/exclusive-beat-licensegenerator-for-music-producers-6pl</guid>
      <description>&lt;p&gt;For the ones that know, Beatstars contracts are cool but they're pretty tied to the platform (and also they're impossible to understand) so I was thinking about making a standalone version.&lt;/p&gt;

&lt;p&gt;I read some books about music rights and made this one, I hope it can be helpful to the music producers out there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://producerfury.com/resources/exclusive-beat-license-agreement-generator" rel="noopener noreferrer"&gt;https://producerfury.com/resources/exclusive-beat-license-agreement-generator&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AI beat name generator for music producers</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Sun, 11 Aug 2024 09:51:10 +0000</pubDate>
      <link>https://forem.com/fabienherfray/ai-beat-name-generator-for-music-producers-2lf5</link>
      <guid>https://forem.com/fabienherfray/ai-beat-name-generator-for-music-producers-2lf5</guid>
      <description>&lt;p&gt;I made a cool AI beat name generator!&lt;br&gt;
If you're a music producer and you never know how to call your 237th type beat, you should go check it out 😛&lt;br&gt;
&lt;a href="https://producerfury.com/resources/beat-name-generator" rel="noopener noreferrer"&gt;https://producerfury.com/resources/beat-name-generator&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Mastering Postgres indexes in 10 minutes</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Tue, 15 Dec 2020 09:58:22 +0000</pubDate>
      <link>https://forem.com/fabienherfray/mastering-postgres-indexes-in-10-minutes-4nkn</link>
      <guid>https://forem.com/fabienherfray/mastering-postgres-indexes-in-10-minutes-4nkn</guid>
      <description>&lt;h2&gt;
  
  
  What's in this post?
&lt;/h2&gt;

&lt;p&gt;Enough about the insides of Postgres indexes to impress your coworkers at the coffee machine or recruiters at a job interview 🤓.&lt;/p&gt;

&lt;p&gt;We'll have a look at B-Tree, Hash, GIN, GiST, BRIN indexes and focus on demystifying them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the hell do I even need an index?
&lt;/h2&gt;

&lt;p&gt;Indexes are at the core of any querying in relational databases (the classic SQL databases like Postgres or MySQL). Therefore it is very rewarding and important to have an idea of how they work.&lt;/p&gt;

&lt;p&gt;An illustration is worth a thousand words so let's take an example. Let's say we have a &lt;code&gt;user&lt;/code&gt; table containing 1M million entries (or rows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;   &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;age&lt;/span&gt;  &lt;span class="nb"&gt;INTEGER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to know how many of our users are above 30. We run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any extra index, the database would have to go over the 1M entries one by one, look at their age and increment a counter for all the users above 30:&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/scan.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/scan.png" alt="scan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it feels like an inefficient way of getting the result it's because it is 💁‍♀️. It would be a slow query (called a "full table scan", as every row needs to be scanned).&lt;/p&gt;

&lt;p&gt;That's where indexation comes into play. It will allow us to quickly locate the users we're interested in, without having to lookup all the rows.&lt;/p&gt;

&lt;p&gt;In Postgres, there are 5 types of indexes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;B-Trees&lt;/li&gt;
&lt;li&gt;Hashes&lt;/li&gt;
&lt;li&gt;GINs&lt;/li&gt;
&lt;li&gt;GiSTs&lt;/li&gt;
&lt;li&gt;BRINs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Naming
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row&lt;/strong&gt;: An entry in the database (e.g. a user). Also called tuple.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column&lt;/strong&gt;: An attribute of a row (e.g. the first name of a user).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table&lt;/strong&gt;: A collection of rows (e.g. a user table).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TID&lt;/strong&gt;: Tuple ID. It's an internal Postgres ID. It describes where to find the row on the disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operator&lt;/strong&gt;: Reserved keyword representing operations on data (e.g. &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statement&lt;/strong&gt;: Database operation (e.g. &lt;code&gt;CREATE TABLE user (name TEXT);&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query&lt;/strong&gt;: Statement that returns data (e.g. &lt;code&gt;SELECT * FROM user;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Core assumptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;In general, an index needs to be able to fit in RAM to be fully efficient. That's why having very large indexes can be problematic.&lt;/li&gt;
&lt;li&gt;Fetching the row behind a &lt;code&gt;tid&lt;/code&gt; is easy and relatively cheap to do. We consider it roughly equivalent to getting an element from an array by index (e.g. &lt;code&gt;var a = array[i]&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;We'll only talk about read operations (queries). How to propagate writes to indexes is a whole other story.&lt;/li&gt;
&lt;li&gt;Everything is more complex and complicated in real life, this post tries to explain things on a very high level and may be approximate on some parts. For more details, one should have a look at the &lt;code&gt;Further explanation&lt;/code&gt; links.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  B-Tree indexes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usecase
&lt;/h3&gt;

&lt;p&gt;So we want to index this query. We want to make it faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create a B-Tree index on the &lt;code&gt;age&lt;/code&gt; column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_age&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to specify anything, B-Tree is the default index for Postgres and many other databases including MySQL, Oracle or SQL Server.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does it work
&lt;/h3&gt;

&lt;p&gt;A binary search tree (&lt;strong&gt;different from a B-Tree&lt;/strong&gt;) is a well known data structure where every node of the tree has two children nodes. The values of the left and right children being respectively smaller and larger than their parent's value.&lt;/p&gt;

&lt;p&gt;A B-Tree is a generalised binary search tree where every node can have more than two children.&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/tree_comparison.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/tree_comparison.png" alt="tree comparison"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A very helpful property of these trees is that their values are sorted. It makes it easy to lookup values. Exactly what we need to speed up our query!&lt;/p&gt;

&lt;p&gt;Now a B-Tree index is slightly more than just a B-Tree. It is a linked list of tids, sorted by value and linked to a B-Tree.&lt;/p&gt;

&lt;p&gt;Let's run our query again and see how it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/btree.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/btree.png" alt="btree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was much easier! By using the sorting of the tree nodes, we can now quickly find the first user being above 30 and count from there. Remember? The linked list at the bottom is sorted by age!&lt;/p&gt;

&lt;p&gt;As a side note, you may be wondering about multicolumn indexes, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_age_and_height&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height_cm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplest way to visualize it is to imagine it as being roughly equivalent to a monocolumn index like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_age_and_height&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;height_cm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The B-Tree mechanism stays the same, but the values that are being sorted are now for example &lt;code&gt;28-182&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This explains why the index wouldn't help with trying to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;height_cm&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;160&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 tree, values like &lt;code&gt;28-182&lt;/code&gt; are not sorted by height. They are sorted by age, and then for every individual age they are sorted by height.&lt;/p&gt;

&lt;p&gt;On the other hand, the index would help us with this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;height_cm&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TLDR (Too Long Didn't Read)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;B-Tree is the default index in many databases.&lt;/li&gt;
&lt;li&gt;Is it so popular because it's fast, efficient and flexible.&lt;/li&gt;
&lt;li&gt;It makes operations like &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt; or even &lt;code&gt;LIKE&lt;/code&gt; faster.&lt;/li&gt;
&lt;li&gt;A rule of thumb would be: if you need to speed up the above operations, use a B-Tree index unless there is a specific reason not to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.qwertee.io/blog/postgresql-b-tree-index-explained-part-1/" rel="noopener noreferrer"&gt;https://www.qwertee.io/blog/postgresql-b-tree-index-explained-part-1/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://habr.com/en/company/postgrespro/blog/443284" rel="noopener noreferrer"&gt;https://habr.com/en/company/postgrespro/blog/443284&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hash indexes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usecase
&lt;/h3&gt;

&lt;p&gt;We're only querying using the &lt;code&gt;=&lt;/code&gt; operator and we're having very specific scaling/performance issues (e.g. our table is huge and a B-Tree would be too large to fit in memory). We want to index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Souleymane Laurence'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create a Hash index on the &lt;code&gt;name&lt;/code&gt; column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;HASH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, we need to specify it explicitely by adding &lt;code&gt;USING HASH&lt;/code&gt; to our statement.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does it work
&lt;/h3&gt;

&lt;p&gt;Let's think of our &lt;code&gt;user&lt;/code&gt; table as a Java array for one second. We consider the position in the array to be equivalent to a &lt;code&gt;tid&lt;/code&gt; in Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;userTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{...}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's how we'd naively lookup the users having the name &lt;code&gt;Souleymane Laurence&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;userTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{...}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foundTids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Iterating over the rows of our table to find our values in the array.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;userTable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&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;userTable&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Souleymane Laurence"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;foundTids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is slow because we have to go over all the entries of the table one by one (remember, they could be hundreds of millions in a database).&lt;/p&gt;

&lt;p&gt;Now couldn't we find a way to remove this big for loop and predict in advance where are our rows going to be located in the array? Yes! And it's called a &lt;strong&gt;hash function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A hash function is capable of taking any input and translating it into a smaller subset of values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Returns an int between 0 and 256. Always the same for the same input.&lt;/span&gt;
&lt;span class="n"&gt;hash_function_256&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Souleymane Laurence"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;147&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could use this as an array position! We just need to rearrange our table first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;userTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{...}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;hashIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// 256 hash values&lt;/span&gt;

&lt;span class="c1"&gt;// Iterating over the rows of our table to populate our index.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;userTable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash_function&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userTable&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hashIndex&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just created our own hash index. Let's now run our query again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Our new index.&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[]&lt;/span&gt; &lt;span class="n"&gt;hashIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{...}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash_function_256&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Souleymane Laurence"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;foundTids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashIndex&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, and that was quick as hell 🔥. Back to SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Souleymane Laurence'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/hash.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/hash.png" alt="hash"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TLDR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The hash index was never very popular for a few reasons:

&lt;ul&gt;
&lt;li&gt;Before Postgres 10, hash indexes were not properly supported. In particular they were not recorded in the write-ahead log so they could not be recovered after a failure/incident.&lt;/li&gt;
&lt;li&gt;They only index the &lt;code&gt;=&lt;/code&gt; operator and also don't help with sorting. They're not very flexible and for &lt;code&gt;=&lt;/code&gt;, the B-Tree does the job very well too.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;So why should you use a hash index? Probably you shouldn't. But they are smaller in size and can be faster than B-Trees so they can be useful under certain conditions. The read time being constant (&lt;code&gt;O(1)&lt;/code&gt; vs &lt;code&gt;O(log n)&lt;/code&gt; for B-Trees), they could for example benefit us for a high throughput of lookups by ID on a huge table.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://habr.com/en/company/postgrespro/blog/442776" rel="noopener noreferrer"&gt;https://habr.com/en/company/postgrespro/blog/442776&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@jorsol/postgresql-10-features-hash-indexes-484f319db281" rel="noopener noreferrer"&gt;https://medium.com/@jorsol/postgresql-10-features-hash-indexes-484f319db281&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GIN (Generalized Inverted Index)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usecase
&lt;/h3&gt;

&lt;p&gt;GIN indexes are mostly useful for indexing multi-valued columns (e.g. arrays, full-text search).&lt;/p&gt;

&lt;p&gt;Let's add a &lt;code&gt;favorite_colors&lt;/code&gt; column to our users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;favorite_colors&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to be able to index a query that looks up users that have &lt;code&gt;red&lt;/code&gt; and &lt;code&gt;blue&lt;/code&gt; in their favorite colors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;favorite_colors&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{red,blue}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now create a GIN index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_favorite_colors&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;favorite_colors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does it work
&lt;/h3&gt;

&lt;p&gt;Spoiler alert: GIN indexes are actually custom B-Trees where the multi-valued columns (arrays for example) are flattened 😱.&lt;br&gt;
The only difference is that we're adding a bitmap scan at the end of the operation for multi-value lookups.&lt;/p&gt;

&lt;p&gt;Let's run our query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;favorite_colors&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{red,blue}'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/gin.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/gin.png" alt="gin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TLDR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GIN indexes are useful to index multi-valued columns (e.g. arrays or for full-text search).&lt;/li&gt;
&lt;li&gt;It makes the array operations like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;&amp;lt;@&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;, or &lt;code&gt;@&amp;gt;&lt;/code&gt; faster.&lt;/li&gt;
&lt;li&gt;Unlike with B-Trees, multicolumn GIN search effectiveness is the same regardless of the columns used in the query conditions. It comes from the fact that, like for array values, GIN indexes the different columns in separate B-Trees and combines the results at querying time with a bitmap scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.louisemeta.com/blog/indexes-gin/" rel="noopener noreferrer"&gt;http://www.louisemeta.com/blog/indexes-gin/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://towardsdatascience.com/how-gin-indices-can-make-your-postgres-queries-15x-faster-af7a195a3fc5" rel="noopener noreferrer"&gt;https://towardsdatascience.com/how-gin-indices-can-make-your-postgres-queries-15x-faster-af7a195a3fc5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/gin-implementation.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/13/gin-implementation.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GiST (Generalized Inverted Seach Tree)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usecase
&lt;/h3&gt;

&lt;p&gt;Ok let's sum it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash indexes only support the &lt;code&gt;=&lt;/code&gt; operator.&lt;/li&gt;
&lt;li&gt;B-Trees support operators like &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;=&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;GIN support operators like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;@&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;@&lt;/code&gt; for multi-valued column queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GiST indexes go one step further and allow indexation of complex custom operators: geospacial queries for example.&lt;/p&gt;

&lt;p&gt;Let's add two &lt;code&gt;location_lat&lt;/code&gt; and &lt;code&gt;location_lon&lt;/code&gt; columns to our users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;location_lat&lt;/span&gt; &lt;span class="n"&gt;FLOAT8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;location_lon&lt;/span&gt; &lt;span class="n"&gt;FLOAT8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to be able to index a query that returns all the users in a 1km diameter circle around a point. Let's say (45.7640, 4.8357):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;earth_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8357&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location_lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location_lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;earth_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8357&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location_lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location_lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok this looks much more complicated than what we did before 😄. What are &lt;code&gt;ll_to_earth&lt;/code&gt;, &lt;code&gt;earth_box&lt;/code&gt; and &lt;code&gt;earth_distance&lt;/code&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ll_to_earth(FLOAT8, FLOAT8) -&amp;gt; EARTH&lt;/code&gt; is a function that converts coordinates to an &lt;code&gt;EARTH&lt;/code&gt; type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;EARTH&lt;/code&gt; type is a variation of the &lt;code&gt;CUBE&lt;/code&gt; type, forcing the &lt;code&gt;CUBE&lt;/code&gt; location to be close to the surface of the Earth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;CUBE&lt;/code&gt; type represents a shape in space. It can be a point, a line, a cube... It is efficiently indexable with GiST.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;earth_box(EARTH, FLOAT8) -&amp;gt; CUBE&lt;/code&gt; is a function that takes an &lt;code&gt;EARTH&lt;/code&gt; point, a radius, and returns a box containing &lt;strong&gt;at least&lt;/strong&gt; all the points at a &lt;code&gt;radius&lt;/code&gt; distance of the &lt;code&gt;EARTH&lt;/code&gt; point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This box contains too much so we need to double check the results. We refilter the results using &lt;code&gt;earth_distance(EARTH, EARTH) -&amp;gt; FLOAT8&lt;/code&gt; that returns the precise distance between two &lt;code&gt;EARTH&lt;/code&gt; points.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok let's index this. We now create a GiST index on the &lt;code&gt;ll_to_earth&lt;/code&gt; values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;user_by_location&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location_lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location_lon&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does it work
&lt;/h3&gt;

&lt;p&gt;What makes GiST special is that it is not a kind of index per say but more of an infrastructure with a flexible/extensible api allowing complex objects to be sorted and manipulated in a search tree: geometric shapes for example. GiST can implement different indexing strategies.&lt;/p&gt;

&lt;p&gt;In our example, we're indexing &lt;code&gt;CUBE&lt;/code&gt; objects, and particularly this clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;earth_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8357&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ll_to_earth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location_lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location_lon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how our index looks like. Looks familiar?&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/gist.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/gist.png" alt="gist"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a B-Tree, the values of the left and right children of a node are respectively smaller and larger than their parent's value.&lt;/p&gt;

&lt;p&gt;In this GiST index, every node is a box with children indicating if they contain or not the points we're looking for.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLDR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GiST indexes are useful in particular to index complex objects like geometric shapes.&lt;/li&gt;
&lt;li&gt;If we think about geometrical operations, it makes the operations like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;&amp;lt;@&lt;/code&gt; or &lt;code&gt;@&amp;gt;&lt;/code&gt; faster for these objects, but also all the other crazy operators that can be found &lt;a href="https://www.postgresql.org/docs/12/functions-geometry.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Like with B-Trees, multicolumn GiST search is only really efficient when the query conditions match the index columns order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/norman/1535879" rel="noopener noreferrer"&gt;https://gist.github.com/norman/1535879&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/cube.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/13/cube.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/earthdistance.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/13/earthdistance.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jlrobins.github.io/2019/05/simple-spatial-postgres-sans-postgis.html" rel="noopener noreferrer"&gt;https://jlrobins.github.io/2019/05/simple-spatial-postgres-sans-postgis.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/postgres-professional/indexes-in-postgresql-5-gist-86e19781b5db" rel="noopener noreferrer"&gt;https://medium.com/postgres-professional/indexes-in-postgresql-5-gist-86e19781b5db&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://patshaughnessy.net/2017/12/15/looking-inside-postgres-at-a-gist-index" rel="noopener noreferrer"&gt;http://patshaughnessy.net/2017/12/15/looking-inside-postgres-at-a-gist-index&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  BRIN (Block Range Indexes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Usecase
&lt;/h3&gt;

&lt;p&gt;Typically, the usecase would be for a log or audit table that is hundreds of millions of rows large:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;log_entry&lt;/span&gt;  &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would sometimes want to query our entries by timestamp to process it for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2020-11-04 22:17:35'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could definitely use a B-Tree for that. The problem is that on a table this big, the index would be multiple GB large. It would take a lot of unnecessary space in memory for such a specific background job usecase.&lt;/p&gt;

&lt;p&gt;BRIN would be the solution. We create the index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;log_by_created_at&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;BRIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does it work
&lt;/h3&gt;

&lt;p&gt;BRIN is a special one: it doesn't allow any new fancy complex operator indexation (only basics like &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;) but its structure is different.&lt;/p&gt;

&lt;p&gt;Remember when we said that a B-Tree index was a linked list of tids, sorted by value and linked to a B-tree?&lt;/p&gt;

&lt;p&gt;What if the data of our table was already sorted on the disk in the first place? Couldn't we use it directly instead of having to remap the entire table as a sorted linked list for the index?&lt;/p&gt;

&lt;p&gt;That's exactly what BRIN is about.&lt;/p&gt;

&lt;p&gt;Without going into details, as long as we only append to our log table (no updates, we only insert new log entries), we know that our rows will be sorted by &lt;code&gt;created_at&lt;/code&gt; timestamp on the disk.&lt;/p&gt;

&lt;p&gt;To index the data, BRIN will split the whole table into blocks, calculating for each the max/min values of the &lt;code&gt;created_at&lt;/code&gt; timestamp.&lt;/p&gt;

&lt;p&gt;The index allows us to scan the blocks instead of the rows. Let's run our query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&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;FROM&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2020-11-04 22:17:35'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/mastering-postgres-indexes-in-10-minutes/brin.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/mastering-postgres-indexes-in-10-minutes/brin.png" alt="brin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fact that we don't need to copy all the rows into the index makes it extremely lightweight: a few kilobytes only instead of megabytes or gigabytes for large B-Trees.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLDR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;BRIN indexes are useful in particular to index very large append-only tables where the order of insertion is the same as the order you want to use to query.&lt;/li&gt;
&lt;li&gt;If your table can fit these pretty strict requirements, BRIN works well for &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt; operations and is extremely lightweight.&lt;/li&gt;
&lt;li&gt;Something worth knowing is that the index only gets refreshed at vacuuming time (vacuuming is an internal Postgres garbage collection operation). To make sure that it's refreshed before running a query you can trigger it manually by running &lt;code&gt;SELECT brin_summarize_new_values ('log_by_created_at');&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further explanation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.percona.com/blog/2019/07/16/brin-index-for-postgresql-dont-forget-the-benefits" rel="noopener noreferrer"&gt;https://www.percona.com/blog/2019/07/16/brin-index-for-postgresql-dont-forget-the-benefits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/brin-intro.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/13/brin-intro.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;em&gt;That's all Folks!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Many people write SQL queries regularly but I noticed that many also don't know about indexes or only ever used them as black boxes.&lt;/p&gt;

&lt;p&gt;It is very understandable as articles and blog posts on indexes are often extremely thorough and technical (multiple pages per type of index) and it can be hard to extract general knowledge from them.&lt;/p&gt;

&lt;p&gt;I hope that I could help with that. It was my first blog post ever and I also hope that I'll be able to find the energy to write some more 😄.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🌴🍹 Chill networking for ranting hackers</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Sat, 07 Mar 2020 16:13:14 +0000</pubDate>
      <link>https://forem.com/fabienherfray/chill-networking-for-ranting-hackers-1lck</link>
      <guid>https://forem.com/fabienherfray/chill-networking-for-ranting-hackers-1lck</guid>
      <description>&lt;p&gt;...Because they say that complaining about the same things connects people.&lt;/p&gt;

&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;TLDR: Would you use my networking side project? &lt;a href="https://www.frenl.com" rel="noopener noreferrer"&gt;https://www.frenl.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a developer and person that doesn't like to go out that much, I don't have a lot of product/design/business people in my network. It has often been a problem for me when working on side projects and trying to learn things outside of my dev bubble.&lt;/p&gt;

&lt;p&gt;I've been working for a while on a tool to help with that and my biggest problem has been so far to get the users to talk to each other. It's actually hard to get two strangers to talk.&lt;/p&gt;

&lt;p&gt;I am now experimenting with two new concepts to help with that: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Adding some "hot takes" as a core of the user profiles to give them a more clickbaity flavor (they tend to be a bit bland right now).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding self-interviews to user profiles to create empathy and spark more interest.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one that I'm the least convinced about is actually the "hot takes" because I'm not sure that people will open up easily.&lt;br&gt;
That's what I wanted to know your opinion about today hehe.&lt;/p&gt;

&lt;p&gt;Thank you and have a great weekend all!&lt;/p&gt;

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

</description>
      <category>sideprojects</category>
      <category>go</category>
      <category>vue</category>
      <category>graphql</category>
    </item>
    <item>
      <title>A chill interview: Abolishing notification mess with a browser extension</title>
      <dc:creator>Fabien Herfray</dc:creator>
      <pubDate>Wed, 19 Feb 2020 22:15:47 +0000</pubDate>
      <link>https://forem.com/fabienherfray/a-chill-interview-abolishing-notification-mess-with-a-browser-extension-ecj</link>
      <guid>https://forem.com/fabienherfray/a-chill-interview-abolishing-notification-mess-with-a-browser-extension-ecj</guid>
      <description>&lt;p&gt;Hi all! I recently started to interview some developer/designer/indie hacker friends of mine to learn more about their stories, projects and side-projects.&lt;/p&gt;

&lt;p&gt;Next to more serious and ambitious interview websites, I'll mostly be trying to focus on chill and feel-good readings. A big goal is also to make the interviewees look approachable: don't hesitate to contact them :)&lt;/p&gt;

&lt;p&gt;The last one was with my ex-coworker at JustWatch, co-organizer of the Ionic Berlin Meetup and Vue.js expert. He built a Chrome extension to centralize all your web notifications. I hope you'll enjoy it!&lt;/p&gt;

&lt;p&gt;"We aren't okay anymore to grant permission to a Chrome extension to read all our cookies. Notyfy wants to pick up where Chime for Chrome left off."&lt;br&gt;
Simon Wicki&lt;br&gt;
&lt;a href="https://www.frenl.com/interview/abolishing-notification-mess-with-a-browser-extension" rel="noopener noreferrer"&gt;https://www.frenl.com/interview/abolishing-notification-mess-with-a-browser-extension&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you enjoyed reading it, see also:&lt;/p&gt;

&lt;p&gt;"I’d learned to use Zapier while running a previous business so I went for it. The demand surprised me and I found my first project the next day."&lt;br&gt;
Andrew Davison&lt;br&gt;
&lt;a href="https://www.frenl.com/interview/founding-a-zapier-agency-before-no-code-was-cool" rel="noopener noreferrer"&gt;https://www.frenl.com/interview/founding-a-zapier-agency-before-no-code-was-cool&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;p&gt;"One day I had to convert a XD file myself and I found out that there was no converter out there whatsoever. One full moon night, I decided to start building it. Many many nights later, it launched ;-)"&lt;br&gt;
Kevin Goedecke&lt;br&gt;
&lt;a href="https://www.frenl.com/interview/how-i-built-a-profitable-tool-with-only-one-landing-page" rel="noopener noreferrer"&gt;https://www.frenl.com/interview/how-i-built-a-profitable-tool-with-only-one-landing-page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>design</category>
    </item>
  </channel>
</rss>
