<?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: Michiel Hendriks</title>
    <description>The latest articles on Forem by Michiel Hendriks (@elmuerte).</description>
    <link>https://forem.com/elmuerte</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%2F14430%2F62d2dca3-0053-4127-9bae-03d29e4c73ab.jpeg</url>
      <title>Forem: Michiel Hendriks</title>
      <link>https://forem.com/elmuerte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/elmuerte"/>
    <language>en</language>
    <item>
      <title>Hands of the privates</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Tue, 12 May 2020 10:01:24 +0000</pubDate>
      <link>https://forem.com/elmuerte/hands-of-the-privates-3p39</link>
      <guid>https://forem.com/elmuerte/hands-of-the-privates-3p39</guid>
      <description>&lt;p&gt;I am not a fan of private visibility. It prevents extensibility, but more importantly, it prevents unit testing. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;You shouldn't unit test private methods.&lt;/em&gt; I hear you say. But why not?&lt;/p&gt;

&lt;p&gt;It is time for a bad analogy. Lets say I have I have this &lt;code&gt;FoodFactory&lt;/code&gt; and I have a great hamburger recipe. So I would have the method&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Hamburger&lt;/span&gt; &lt;span class="nf"&gt;createHambuger&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&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 probably all agree that this method should not contain every single step in creating a hamburger, but you would probably do something like this.&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Hamburger&lt;/span&gt; &lt;span class="nf"&gt;createHambuger&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="n"&gt;parts&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;Stack&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;parts&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;createBottomBun&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;parts&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;getLettuce&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;parts&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;getTomatoSlice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;parts&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;createPatty&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;parts&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;createTopBun&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;assembleHamburger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&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;At some point in time we start getting complaints that the top bun does not have sesame seeds on top. Which is weird, because it &lt;em&gt;is&lt;/em&gt; part of the top bun. After some extensive debugging and eating a lot of hamburgers we find out that sometimes &lt;code&gt;createTopBun()&lt;/code&gt; returns the bun upside down.&lt;/p&gt;

&lt;p&gt;This would be an easy fix. We just create a unit test to assert &lt;code&gt;createTopBun()&lt;/code&gt; returns the bun right side up, and ... wait! Our awesome hamburger is a top-secret recipe, so all our steps should be private methods. The only way to test if the top bun is right side up I need to create a full hamburger and then dissect the result. Which would also mean my test setup needs to also take care of having the inventory to create the patty, etc. All I wanted was to verify the top bun.&lt;/p&gt;

&lt;p&gt;With the exception for security related parts, why should I make things private instead of protected? &lt;br&gt;
And why should I not test private methods?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>New Programming Jargon</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Fri, 10 Apr 2020 06:43:57 +0000</pubDate>
      <link>https://forem.com/elmuerte/new-programming-jargon-1h89</link>
      <guid>https://forem.com/elmuerte/new-programming-jargon-1h89</guid>
      <description>&lt;p&gt;Besides coming up with new solutions to old and new problems, we programmers sometimes also come up with new terms.&lt;/p&gt;

&lt;p&gt;About ten years ago a question was posted on StackOverflow asking people about new terms they coined. The result was quite a good list of terms I am still using to date. Terms like &lt;em&gt;Yoda Conditions&lt;/em&gt;, or &lt;em&gt;Stringly Typed&lt;/em&gt;. Jeff Atwood &lt;a href="https://blog.codinghorror.com/new-programming-jargon/" rel="noopener noreferrer"&gt;wrote an article&lt;/a&gt; about most of the good terms. Sadly the original StackOverflow question has been deleted.&lt;/p&gt;

&lt;p&gt;Today while reading Ben Halpern's post &lt;a href="https://dev.to/ben/longtime-devs-have-you-rekindled-your-love-of-coding-after-losing-the-spark-at-any-point-in-your-career-4jn0"&gt;Have you rekindled your love of coding after losing the spark at any point in your career?&lt;/a&gt; I saw &lt;a href="https://dev.to/codemouse92/comment/nhhm"&gt;Jason C. McDonald's comment&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I only start losing my love of coding when I start getting burned out and &lt;strong&gt;codeblind&lt;/strong&gt; on one project. The cure is usually to take a break and pick up something completely different!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where de defined &lt;strong&gt;codeblind&lt;/strong&gt; as &lt;em&gt;a term for that phenomenon when you've been working on the same code for so long, you can no longer see the obvious.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As it has been a while since the StackOverflow quest, I this it's due time to ask it again.&lt;/p&gt;

&lt;h1&gt;
  
  
  What new programming jargon have you coined, or picked up somewhere?
&lt;/h1&gt;

&lt;p&gt;Post your term and definition in the comments below. 👇👇👇&lt;/p&gt;

</description>
      <category>jargon</category>
      <category>discuss</category>
    </item>
    <item>
      <title>hCaptcha instead of Google's reCaptcha</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Thu, 09 Apr 2020 13:49:23 +0000</pubDate>
      <link>https://forem.com/elmuerte/hcaptcha-instead-of-google-s-recaptcha-2c92</link>
      <guid>https://forem.com/elmuerte/hcaptcha-instead-of-google-s-recaptcha-2c92</guid>
      <description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/CAPTCHA" rel="noopener noreferrer"&gt;CAPTCHA&lt;/a&gt; means &lt;em&gt;Completely Automated Public Turing-test to tell Computers and Humans Apart&lt;/em&gt;. It is a vital feature to keep spam bots away from your site.&lt;/p&gt;

&lt;p&gt;Years ago everybody wrote their own system, usual a picture containing an obscured word or number. But technology of the spammers also improved and these methods would no longer stop them. Some smart people invented reCAPTCHA, which was based on solving problems computers were not really good at. As with most useful and popular technology this was bought by a large tech company, in this case Google.&lt;/p&gt;

&lt;p&gt;We all know Google and their primary business: selling advertisements. One of their ad-platform's major selling point is targeted advertisements based on information they harvested about the website visitors. &lt;/p&gt;

&lt;p&gt;If you want to run a privacy minded website you obvious want as little technology on which invades on your visitors privacy. There are many &lt;a href="https://restoreprivacy.com/google-alternatives/" rel="noopener noreferrer"&gt;alternatives for various Google products&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;On one of my websites I removed Google Ads, because I did not earn much from it anyway. I removed Google Analytics, because I was not looking at it anyway. But, I was still using reCAPTCHA to fend of spammers. &lt;/p&gt;

&lt;p&gt;Yesterday I saw a &lt;a href="https://blog.cloudflare.com/moving-from-recaptcha-to-hcaptcha/" rel="noopener noreferrer"&gt;blogpost from Cloudflare&lt;/a&gt; on how they moved from reCAPTCHA to hCaptcha. So there is also an alternative for the last Google product I had on my site. Today I started all the work to move to hCaptcha.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hcaptcha.com/?r=bb3dd94fa5e7" rel="noopener noreferrer"&gt;hCaptcha&lt;/a&gt; is basically the same as reCAPTCHA, with some major differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They respect the privacy of your visitors.&lt;/li&gt;
&lt;li&gt;The problems visitors solve are not for the benefit of a single company.&lt;/li&gt;
&lt;li&gt;You can earn money, or donate it, by visitors solving these problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now the difficult part: moving away from reCAPTCHA to hCaptcha. All the steps you need to perform to convert are &lt;a href="https://docs.hcaptcha.com/switch" rel="noopener noreferrer"&gt;detailed in the documentation&lt;/a&gt;. It basically required me to perform &lt;em&gt;three&lt;/em&gt; replace-all actions, and updating two settings to covert my existing reCAPTCHA implementation to hCaptcha. I think it cost me almost 30 minutes to execute and verify! Yes, it took me more time to write this article.&lt;/p&gt;

</description>
      <category>captcha</category>
      <category>privacy</category>
      <category>spam</category>
    </item>
    <item>
      <title>Dear Technology, ...</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Wed, 18 Sep 2019 17:46:26 +0000</pubDate>
      <link>https://forem.com/elmuerte/dear-technology-mi6</link>
      <guid>https://forem.com/elmuerte/dear-technology-mi6</guid>
      <description>&lt;p&gt;I saw this tweet during the recent #DevDicuss on Twitter. &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1174135015485186053-749" src="https://platform.twitter.com/embed/Tweet.html?id=1174135015485186053"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1174135015485186053-749');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1174135015485186053&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I like the format of writing open letters to technology. Obviously technology cannot respond or even defend itself. But it's good to write away your annoyances. &lt;/p&gt;

&lt;p&gt;What is your &lt;em&gt;Dear Technology, ...&lt;/em&gt; letter?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>This space available for rent</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Sun, 25 Aug 2019 20:39:23 +0000</pubDate>
      <link>https://forem.com/elmuerte/this-space-available-for-rent-3ch2</link>
      <guid>https://forem.com/elmuerte/this-space-available-for-rent-3ch2</guid>
      <description>&lt;p&gt;Last week the maintainer of a JavaScript package &lt;a href="https://github.com/standard/standard/issues/1381" rel="noopener noreferrer"&gt;decided to monetize installation&lt;/a&gt; of its package through NPM by showing ads after the install.&lt;/p&gt;

&lt;p&gt;The response to this was surprisingly quite mixed. I did not expect so many people supporting this idea. Most of the supporters justified this as a valid means to earn money of maintaining the open source project.&lt;/p&gt;

&lt;p&gt;The above incident was not the first case. A few months ago an other developer decided to &lt;a href="https://github.com/zloirock/core-js/issues/548" rel="noopener noreferrer"&gt;beg for a job in a similar way&lt;/a&gt;. And &lt;a href="https://github.com/cssinjs/jss/issues/881" rel="noopener noreferrer"&gt;there&lt;/a&gt; &lt;a href="https://github.com/remy/nodemon/issues/1189" rel="noopener noreferrer"&gt;are&lt;/a&gt; &lt;a href="https://github.com/styled-components/styled-components/issues/1590" rel="noopener noreferrer"&gt;some&lt;/a&gt; &lt;a href="https://github.com/pouchdb/pouchdb/issues/7392" rel="noopener noreferrer"&gt;more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was rather supersized that NPM allows arbitrary code execution during package installation. Seems like a major security issue, which can clearly also be abused in other ways.&lt;/p&gt;

&lt;p&gt;So what is your take on this? Is your console output available for others to rent out?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>healthydebate</category>
    </item>
    <item>
      <title>Healthy Breakfast Coding</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Fri, 19 Jul 2019 06:18:12 +0000</pubDate>
      <link>https://forem.com/elmuerte/healthy-breakfast-coding-5dig</link>
      <guid>https://forem.com/elmuerte/healthy-breakfast-coding-5dig</guid>
      <description>&lt;p&gt;It's the fruit loop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kiwi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;Inspired by Ben's post about loops.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/ben" 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%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png" alt="ben"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/ben/which-types-of-loops-are-most-popular-in-the-programming-languages-you-use-1mma" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Which types of loops are most popular in the programming languages you use?&lt;/h2&gt;
      &lt;h3&gt;Ben Halpern ・ Jul 18 '19&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>jokes</category>
    </item>
    <item>
      <title>Actual 10x Engineers</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Wed, 17 Jul 2019 17:00:40 +0000</pubDate>
      <link>https://forem.com/elmuerte/actual-10x-engineers-n22</link>
      <guid>https://forem.com/elmuerte/actual-10x-engineers-n22</guid>
      <description>&lt;p&gt;Earlier this month &lt;a href="https://bellard.org" rel="noopener noreferrer"&gt;Fabrice Bellard&lt;/a&gt; released &lt;a href="https://bellard.org/quickjs/" rel="noopener noreferrer"&gt;QuickJS&lt;/a&gt;. Which is a Javascript runtime, which is almost ES2019 complete, that you can embed in your application. This is no small feat. &lt;/p&gt;

&lt;p&gt;In the past week or so there has been a lot of discussion about &lt;a href="https://dev.to/ben/what-are-your-thoughts-on-the-whole-10x-engineer-viral-discussion-50ce"&gt;10x engineers&lt;/a&gt;. While the concept of 10x engineers is flawed in numerous ways, there are software developers who have a truly amazing track record of work.&lt;/p&gt;

&lt;p&gt;Bellard's QuickJS release started a &lt;a href="https://news.ycombinator.com/item?id=20411154" rel="noopener noreferrer"&gt;discussion on hackernews&lt;/a&gt; about great developers who have done amazing work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fabrice Bellard
&lt;/h1&gt;

&lt;p&gt;So let's start with Fabrice. His &lt;a href="https://bellard.org/" rel="noopener noreferrer"&gt;website&lt;/a&gt; lists the great things he created/started. His work is not just in a single domain. &lt;/p&gt;

&lt;p&gt;He created &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt;, which is the basis for a lot of audio video software out there. There is now obviously a whole project team which keeps improving this.&lt;/p&gt;

&lt;p&gt;Bellard also created the &lt;a href="https://www.qemu.org/" rel="noopener noreferrer"&gt;QEMU&lt;/a&gt;, a generic machine emulator and virtualizer. Which has been a major player in virtual machine hosts. &lt;/p&gt;

&lt;p&gt;And then there are a whole bunch of other projects like a LTE/NR Base Station, a PC emulator in JavaScript, ASN1 compiler, etc.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dan J. Bernstein
&lt;/h1&gt;

&lt;p&gt;Also known as &lt;a href="https://cr.yp.to/djb.html" rel="noopener noreferrer"&gt;djb&lt;/a&gt;. Not only did he sue the US government about the &lt;a href="https://en.wikipedia.org/wiki/Bernstein_v._United_States" rel="noopener noreferrer"&gt;export laws around cryptography&lt;/a&gt;, and won, he also create a lot of great software. &lt;/p&gt;

&lt;p&gt;His main field is cryptography. Creating algorithms/ciphers like Sals20, ChaCha20, Curve25519. And a whole bunch of research.&lt;/p&gt;

&lt;p&gt;On his software resume is a lot of really secure software, which is often still used despite not having had any updates for years. It includes: &lt;a href="https://en.wikipedia.org/wiki/Qmail" rel="noopener noreferrer"&gt;qmail&lt;/a&gt;, a SMTP server; djbdns, a DNS server; ezmlm, a mailing list server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Qmail" rel="noopener noreferrer"&gt;qmail&lt;/a&gt; is probably the most notable. It has not received significant updates for a really long time. Only a few bugs have been found, none of them deemed as a security bug.&lt;/p&gt;

&lt;h1&gt;
  
  
  Linus Torvalds
&lt;/h1&gt;

&lt;p&gt;Probably the most well known person of this list. He started the Linux kernel, and is still actively working on it and managing the project.&lt;/p&gt;

&lt;p&gt;But he also created &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; because he needed a decentralized version control system that suited his needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Richard Stallman
&lt;/h1&gt;

&lt;p&gt;Probably mostly known for his activism. But when he original started the &lt;a href="https://www.gnu.org/" rel="noopener noreferrer"&gt;GNU project&lt;/a&gt; there was not a lot of software he could use within his idea of &lt;em&gt;Free Software&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;He had to write this own compiler (which became GCC), his own debugger (GDB), a standard C library (glibc), and a whole bunch of other parts in the GNU userland. He also created GNU Emacs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ludvig Strigeus
&lt;/h1&gt;

&lt;p&gt;One of the younger guys on this list. &lt;/p&gt;

&lt;p&gt;He is one of the original developers of &lt;a href="https://www.scummvm.org/" rel="noopener noreferrer"&gt;ScummVM&lt;/a&gt;, a re-implementation of the LucasArts Scumm game engine. These days ScummVM has grown to a generic VM for various older graphical adventure games, not just Scumm based games.&lt;/p&gt;

&lt;p&gt;Ludvig also started the &lt;a href="https://www.openttd.org/" rel="noopener noreferrer"&gt;OpenTTD&lt;/a&gt; project, which is a reverse engineered implementation of the Chris Sawyer video game Transport Tycoon Deluxe. The community continued his work, and OpenTTD is now available as a fully standalone video game. &lt;/p&gt;

&lt;p&gt;One of the most popular BitTorrent clients on MS Windows is probably uTorrent. This client was also written by Ludvig.&lt;/p&gt;

&lt;p&gt;And then there is this thing called Spotify. Ludvig was there from the start to build the technology.&lt;/p&gt;

&lt;h1&gt;
  
  
  Others?
&lt;/h1&gt;

&lt;p&gt;Who else has a track record of creating amazing pieces of software which baffles the most? Leave your suggestions in the comments.&lt;/p&gt;




&lt;p&gt;PS. This article is not intended to imply that 10x engineers are a normal or even real thing.&lt;/p&gt;

</description>
      <category>career</category>
      <category>history</category>
    </item>
    <item>
      <title>Happy birthday to Stack</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Mon, 15 Jul 2019 16:32:43 +0000</pubDate>
      <link>https://forem.com/elmuerte/happy-birthday-to-stack-127b</link>
      <guid>https://forem.com/elmuerte/happy-birthday-to-stack-127b</guid>
      <description>&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%2Fkmn456atyz2ijn18qwv2.jpg" 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%2Fkmn456atyz2ijn18qwv2.jpg" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jokes</category>
    </item>
    <item>
      <title>The best joke about recursion</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Sun, 07 Jul 2019 15:00:43 +0000</pubDate>
      <link>https://forem.com/elmuerte/the-best-joke-about-recursion-423n</link>
      <guid>https://forem.com/elmuerte/the-best-joke-about-recursion-423n</guid>
      <description>&lt;p&gt;... can be found here:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/elmuerte" 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%2F14430%2F62d2dca3-0053-4127-9bae-03d29e4c73ab.jpeg" alt="elmuerte"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/elmuerte/the-best-joke-about-recursion-423n" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The best joke about recursion&lt;/h2&gt;
      &lt;h3&gt;Michiel Hendriks ・ Jul 7 '19&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#jokes&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>jokes</category>
    </item>
    <item>
      <title>Is High Quality Software Worth the Cost?</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Tue, 04 Jun 2019 19:23:30 +0000</pubDate>
      <link>https://forem.com/elmuerte/is-high-quality-software-worth-the-cost-2li0</link>
      <guid>https://forem.com/elmuerte/is-high-quality-software-worth-the-cost-2li0</guid>
      <description>&lt;p&gt;Most developers agree that quality of software is important, and not an afterthought. However, quality is usually one of the first things to get axed when budgets (time, money, ...) start to run out. &lt;/p&gt;

&lt;p&gt;Many developers probably have tried to explain to management why you should not skim on investing is quality, but have failed to convince them.&lt;/p&gt;

&lt;p&gt;Instead of cutting features from the next release, quality investments are reduced. Investments like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit tests&lt;/li&gt;
&lt;li&gt;refactoring code&lt;/li&gt;
&lt;li&gt;design documentation&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I could also try to explain why you should invest in creating high quality software. But I will probably not do a good job either. At least, not better than Martin Fowler already did.&lt;/p&gt;

&lt;p&gt;In his recent article &lt;strong&gt;&lt;a href="https://martinfowler.com/articles/is-quality-worth-cost.html"&gt;Is High Quality Software Worth the Cost?&lt;/a&gt;&lt;/strong&gt; Martin Fowler properly explains why you should invest in creating high quality software. He also discusses that the cost of creating high quality software is actually &lt;em&gt;negative&lt;/em&gt;. It saves you time and money in the end.&lt;/p&gt;

&lt;p&gt;It is an article management should also be able to understand. Next time you need to explain to management why you need to spend time on quality improvements. Hand them this article, or in fact, do it right now. If they are not convinced, you should consider that a &lt;a href="https://dev.to/david_j_eddy/what-is-your-biggest-red-flag-when-interviewing-4ec8"&gt;red flag&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>technicaldebt</category>
    </item>
    <item>
      <title>Improving PostgreSQL queries</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Tue, 30 Apr 2019 22:23:34 +0000</pubDate>
      <link>https://forem.com/elmuerte/improving-postgresql-queries-4pc1</link>
      <guid>https://forem.com/elmuerte/improving-postgresql-queries-4pc1</guid>
      <description>&lt;p&gt;A while ago &lt;a href="https://dev.to/elmuerte/comment/9mad"&gt;I commented that my big win&lt;/a&gt; was improving a query which took over an hour to execute to just 30 seconds. In fact, for the client this query went from close to 2 hours to about 2 minutes, which included transferring the millions of rows. I optimized quite a few other queries in the past months. &lt;/p&gt;

&lt;p&gt;This article will be mostly about reporting or business intelligence (BI) queries. These queries are often large/complex, reason about a lot of data, and generally take a &lt;em&gt;long&lt;/em&gt; time to execute. With &lt;em&gt;long&lt;/em&gt; I mean tens of seconds if not minutes or hours. &lt;/p&gt;

&lt;p&gt;Some of the things I will explain in this article could also apply to more standard application queries, like search queries. But I cannot be sure about it. This brings me to the most important step.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explain the query plan
&lt;/h1&gt;

&lt;p&gt;Any serious DBMS has an option to get information about how the DBMS is going to execute the query. This is the &lt;em&gt;query plan&lt;/em&gt;. Every time you change anything you should request a query plan to see how it was affected. Changes include changes to the PostgreSQL server settings, and also upgrades of the server. It could very well happen that your optimized query will now perform worse.&lt;/p&gt;

&lt;p&gt;In PostgreSQL the query plan can be retrieved using the &lt;a href="https://www.postgresql.org/docs/current/sql-explain.html" rel="noopener noreferrer"&gt;EXPLAIN&lt;/a&gt; command. You simply but this in front of your query and it will produce a whole bunch of output.&lt;/p&gt;

&lt;p&gt;There are various options to the &lt;code&gt;EXPLAIN&lt;/code&gt; command. I generally use &lt;code&gt;EXPLAIN (VERBOSE)&lt;/code&gt; to get the most important information. When you also include the &lt;code&gt;ANALYZE&lt;/code&gt; option PostgreSQL will also execute the query and return actual execution information, and not just the plan. However, this is not really helpful if you query takes half an hour to execute. With &lt;code&gt;ANALYZE&lt;/code&gt; you might also want to include the &lt;code&gt;BUFFERS&lt;/code&gt; option to get information about shared buffer usage.&lt;/p&gt;

&lt;p&gt;An other option which you might need is the &lt;code&gt;FORMAT&lt;/code&gt; option. Which format to select depends on the tool you use for visualization/analysis of the query plan. The default output is quite human readable. But when the query is really complex it will become difficult to keep track of the nesting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXPLAIN SELECT sum(i) FROM foo WHERE i &amp;lt; 10;

                             QUERY PLAN
---------------------------------------------------------------------
 Aggregate  (cost=23.93..23.93 rows=1 width=4)
   -&amp;gt;  Index Scan using fi on foo  (cost=0.00..23.92 rows=6 width=4)
         Index Cond: (i &amp;lt; 10)
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output formats include &lt;code&gt;JSON&lt;/code&gt;, &lt;code&gt;XML&lt;/code&gt;, &lt;code&gt;YAML&lt;/code&gt;, and the default &lt;code&gt;TEXT&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query plan visualization
&lt;/h2&gt;

&lt;p&gt;There are quite some tools out there which can render the query plan into something easier to use.&lt;/p&gt;

&lt;p&gt;Note: despite that these visualization are really helpful, this article will contain the plain text output as most details are hidden behind interactive usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  pgAdmin
&lt;/h3&gt;

&lt;p&gt;The standard administration tool of PostgreSQL &lt;a href="https://www.pgadmin.org/" rel="noopener noreferrer"&gt;pgAdmin&lt;/a&gt; has visualization build in. Simple execute the query using the explain action. And you will get a visual representation.&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%2F5v3piyv0imxdtbhvic6n.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%2F5v3piyv0imxdtbhvic6n.png" alt="pgAdmin4 query plan visualization" width="750" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lines between the various query parts become thicker depending on the cost of that step. (Note: set the analyze options to include costs.) So it will be easy to find where the biggest cost comes from. But note that the thickness of the line is not relative to the total cost of the query. So a part with a cost of 1,000,000 would have the same thickness as the part with a cost of 10,000. &lt;/p&gt;

&lt;p&gt;Hovering over the various nodes will show some additional details.&lt;/p&gt;

&lt;h3&gt;
  
  
  depesz' explain tool
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://explain.depesz.com/" rel="noopener noreferrer"&gt;Depesz&lt;/a&gt; has an online tool where you can paste the &lt;code&gt;TEXT&lt;/code&gt; output of explain and it renders it in a more easy to navigate HTML table.&lt;/p&gt;

&lt;p&gt;It will highlight the most expensive parts of the query. Once you start optimizing the query you can keep adding new query plans to compare them.&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%2Fsno5bpnobaho9ucovrjg.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%2Fsno5bpnobaho9ucovrjg.png" alt="depesz' explain tool" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgres EXPLAIN Visualizer (pev)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tatiyants.com/pev/" rel="noopener noreferrer"&gt;Pev&lt;/a&gt; is another online tool to visualize the query plan. This one uses the JSON output format, and prefers it with the &lt;code&gt;ANALYZE&lt;/code&gt; option. &lt;/p&gt;

&lt;p&gt;It produces quite a nice looking output with hints on which parts of the query are most problematic.&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%2Fuwle2rq4rw07uri5k5jf.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%2Fuwle2rq4rw07uri5k5jf.png" alt="Postgres EXPLAIN Visualizer (pev)" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have really complex queries the visualization might become more troublesome to navigate.&lt;/p&gt;

&lt;p&gt;Be sure to read the &lt;a href="http://tatiyants.com/postgres-query-plan-visualization/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; for some more explanation about the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  I love it when a plan comes together
&lt;/h2&gt;

&lt;p&gt;Once you have the query plan it is time to inspect it for possible improvement points.&lt;/p&gt;

&lt;p&gt;Your first point of attention should go to the lowest level node with the highest cost. This is the place where you can probably gain the most. Each entry in the plan contains a cost range: &lt;code&gt;cost=0.00..23.92&lt;/code&gt;. The first value can be considered the optimistic cost and the second one the pessimistic cost, or minimum and maximum cost. These numbers have no sensible unit, just regard them as low number is good, high number is not. When optimizing a query, get the initial cost, and then try to get both values down.&lt;/p&gt;

&lt;p&gt;Generally the most costly parts are sequential table scans of large tables (&lt;code&gt;Seq Scan&lt;/code&gt; in the query plan). The goal is to try to get rid of these. But do note that PostgreSQL will happily choose to perform sequential table scans for small tables instead of using an index. The cost of these is usually really low, so do not simply try to get rid of all sequential table scans.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, every time you make a change you must generate a new query plan. The cost might shift to a different part in the plan, making effectively no difference. Or the new plan might perform even worse.&lt;/p&gt;

&lt;p&gt;A way more extensive explanation on the output can be found in the &lt;a href="https://www.postgresql.org/docs/current/using-explain.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rules of Optimization
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Rule #1: Don't&lt;/p&gt;

&lt;p&gt;Rule #2: Don't Yet&lt;/p&gt;

&lt;p&gt;-- Michael A. Jackson&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before optimizing a query, first try it. Then try it with different parameters. It might not even need optimization yet. Only when the performance is not acceptable, try to improve it until it is acceptable.&lt;/p&gt;

&lt;p&gt;Be sure to test the query with different ranges so see how it scales. I generally test with &lt;em&gt;N&lt;/em&gt;, &lt;em&gt;10N&lt;/em&gt;, and &lt;em&gt;100N&lt;/em&gt;. Where &lt;em&gt;N&lt;/em&gt; is expected nominal range, and &lt;em&gt;10N&lt;/em&gt; would be the worst case. And I always include a &lt;em&gt;100N&lt;/em&gt; which is the range you do not expect, but will happen sooner than you ever hoped. It is just there to get a sense of scaling for the query.&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%2Fy4q4822qtd897vqnxgvy.jpg" 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%2Fy4q4822qtd897vqnxgvy.jpg" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Settings of Interest
&lt;/h1&gt;

&lt;p&gt;PostgreSQL has a lot of &lt;a href="https://www.postgresql.org/docs/current/runtime-config.html" rel="noopener noreferrer"&gt;settings&lt;/a&gt; you can play around with which can make a lot of difference. Especially the &lt;a href="https://www.postgresql.org/docs/current/runtime-config-resource.html" rel="noopener noreferrer"&gt;resource consumption&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/current/runtime-config-query.html" rel="noopener noreferrer"&gt;query planning settings&lt;/a&gt; are of interest.&lt;/p&gt;

&lt;p&gt;Out of the box PostgreSQL is configured really defensively, as if it is running on a machine with only 1GiB of RAM.&lt;/p&gt;

&lt;p&gt;I will highlight some interesting settings here, which are not too dangerous to play around with. But please do not solely rely on this text. Always read the official documentation, and make sure you understand it before playing around with the settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Consumption
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shared Buffers
&lt;/h3&gt;

&lt;p&gt;One of the first settings you want to adjust is &lt;code&gt;shared_buffers&lt;/code&gt;. This is the amount of memory the different PostgreSQL connections can use to share data. Data like indexes which were read from the dist. The recommendation is to set it to 25% to 40% of the system RAM. A PostgreSQL expert told me that with the 9.x series have more that 8GiB assigned to shared buffers generally had no significant effect. &lt;/p&gt;

&lt;p&gt;Unlike various other database systems PostgreSQL relies a lot on OS functionality like disk caching in memory. So if you have a server with 64GiB of RAM, and shared buffers set to "only" 8GiB a lot of indexes might not be in the shared cache of PostgreSQL, but they are in the disk cache, and thus quickly accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temp Buffers
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;temp_buffers&lt;/code&gt; setting controls how much memory can be used for temporary tables before flushing to the disk. This setting can be changed at runtime, but can only be changed at the start of a session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work Mem
&lt;/h3&gt;

&lt;p&gt;An other interesting setting is &lt;code&gt;work_mem&lt;/code&gt;. It affects how much memory can be used for sorting and processing of hash tables before flushing data to the disk. Increasing it has a lot of positive effect on complex queries with a lot of sorting and joining of sub-results. For BI-like queries it is well worth increasing the size.&lt;/p&gt;

&lt;p&gt;The work memory allocation is per operation, which could be multiple in a single session. (Especially in the newer PostgreSQL versions where queries can be executed concurrently). Which means you should be careful not to make it too big, or else you can run into out-of-memory issues. &lt;/p&gt;

&lt;p&gt;This is a setting which can also be changed at runtime for the current session. &lt;/p&gt;

&lt;h2&gt;
  
  
  Query Planning Settings
&lt;/h2&gt;

&lt;p&gt;These settings affect the query planning. It is quite easily to destroy the performance of your server by messing up these settings. So in most cases you need to be really careful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Effective Cache Size
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;effective_cache_size&lt;/code&gt; tells PostgreSQL how much data could be cached in the system memory. This includes cached managed by PostgreSQL, but also caches kept by the OS (like the disk cache).&lt;/p&gt;

&lt;p&gt;This setting has no effect on memory allocation. It just gives an indication to the query planner on how likely data might be in memory.&lt;/p&gt;

&lt;p&gt;On a Linux system you can get an idea on how much data is cached by the system if you have system monitoring which keeps track of amount of memory used for caches during a certain period. &lt;code&gt;/proc/meminfo&lt;/code&gt; would tell you how much is currently used, but you really want to know this for a time frame of a week/month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequential / Random Page Cost
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;seq_page_cost&lt;/code&gt; and &lt;code&gt;random_page_cost&lt;/code&gt; settings determine how costly it is to read data from the disk. The default settings assume spinning disks where random seek times are much higher than sequential access. &lt;/p&gt;

&lt;p&gt;If you have an all-flash storage the random seek times are much closed to sequential access. In that case it would make sense to bring the random cost closer to the sequential cost. To some degree this also applies to SAN storage on spinning disks.&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%2F4uu4fgdux65r0yxa4bgd.jpg" 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%2F4uu4fgdux65r0yxa4bgd.jpg" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Indexes
&lt;/h1&gt;

&lt;p&gt;Improving performance of queries is all about &lt;a href="https://www.postgresql.org/docs/current/indexes.html" rel="noopener noreferrer"&gt;indexes&lt;/a&gt;. Without indexes the only thing the database can do is sequential table scans. But indexes are not free. They cost storage, and time to maintain. Changing data in the table means that affected indexes also need to be updated. More complex/advanced indexes obviously take more time.&lt;/p&gt;

&lt;p&gt;The worst index is an index which is not used. So only create indexes when you need to. And remove indexes which are not used.&lt;/p&gt;

&lt;p&gt;Reminder: as stated earlier, for small tables the query planner might choose to skip using an index. &lt;/p&gt;

&lt;h2&gt;
  
  
  Multi Column and Sorted Indexes
&lt;/h2&gt;

&lt;p&gt;An index which covers multiple columns is generally better than creating multiple indexes for a single column. PostgreSQL will consider using a multi-column index even if some of the columns in the index are not even used in the query. &lt;/p&gt;

&lt;p&gt;For example if we had the following 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;order_dates&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can be used for queries using any combination of the &lt;code&gt;creation_date&lt;/code&gt; and &lt;code&gt;completion_date&lt;/code&gt; column. The benefit of having fewer indexes is that they might live in the shared memory, and caches, for a longer time.&lt;/p&gt;

&lt;p&gt;The default index storage type in PostgreSQL is a B-Tree. For a B-Tree is does not matter if you look up the highest or lowest value, it has the same complexity. But if most of your queries sort in a descending order on an indexed column there will be an extra step in the query execution to sort the results.&lt;/p&gt;

&lt;p&gt;This step can be eliminated by adding an order to the index. There are two importing &lt;a href="https://www.postgresql.org/docs/current/indexes-ordering.html" rel="noopener noreferrer"&gt;ordering parts&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;data ordering&lt;/li&gt;
&lt;li&gt;null values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can control the order for each independently. By default each column is &lt;code&gt;ASC NULLS LAST&lt;/code&gt;, meaning ascending with null values at the end.&lt;/p&gt;

&lt;p&gt;If it was important to know which orders have not be completed yet it might be best to put the null values first.&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;order_dates&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_date&lt;/span&gt; &lt;span class="n"&gt;nulls&lt;/span&gt; &lt;span class="k"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would also be interesting in the most recently completed orders it is probably best to also make the completion_date descending.&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;order_dates&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_date&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- is equivalent to&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;order_dates&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_date&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt; &lt;span class="n"&gt;nulls&lt;/span&gt; &lt;span class="k"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just to reiterate, ordering of the index only affects performance when the query contains an &lt;code&gt;order by&lt;/code&gt; clause on those columns. It does not make the index search faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial Indexes
&lt;/h2&gt;

&lt;p&gt;Indexes also take up space. A large index is not good for caching. Creating a small index can have a massive performance improvement. Creating a smaller index can be achieved with &lt;a href="https://www.postgresql.org/docs/current/indexes-partial.html" rel="noopener noreferrer"&gt;partial indexed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Take the previous example of the &lt;code&gt;order_dates&lt;/code&gt;. If we often need to query orders which have not been completed yet, and it is common for orders to be eventually completed. Then it is possible to create a small index on the completion date being null.&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;open_orders&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completion_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;completion_date&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This index would be really small compared to an full index on the &lt;code&gt;completion_date&lt;/code&gt; column. Because it is small, and probably often used, it will be cached quite a lot. When when it is not, only a few expensive disk read operations are needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index on Expressions
&lt;/h2&gt;

&lt;p&gt;Quite often queries contain expressions in a where clause. For example a case insensitive search:&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="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'devtown'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an normal index on the &lt;code&gt;city&lt;/code&gt; column has no benefit. As this index would contain &lt;code&gt;Devtown&lt;/code&gt;, &lt;code&gt;devtown&lt;/code&gt;, etc. as separate entries. To make this query faster you can create an &lt;a href="https://www.postgresql.org/docs/current/indexes-expressional.html" rel="noopener noreferrer"&gt;index on an expression&lt;/a&gt;:&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;lc_city&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the above query could use this index to quickly find the appropriate rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index-Only Scans
&lt;/h2&gt;

&lt;p&gt;An &lt;a href="https://www.postgresql.org/docs/current/indexes-index-only-scans.html" rel="noopener noreferrer"&gt;Index-Only Scan&lt;/a&gt; is where the index contains all the data which was required. Normally the index produces a pointer to the actual data row, which then needs to be fetched.&lt;/p&gt;

&lt;p&gt;But, if you create an index which contains all the data which is selected in the query then the database does not have to fetch the row. You could consider this as a really fast view on a single table. &lt;/p&gt;

&lt;p&gt;You do not have to make the extra fields parts of the indexed data. You can &lt;em&gt;include&lt;/em&gt; them in the index as additional data.&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;unique&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;product_ean&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;include&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 creates a unique index on the product's ID and it includes the &lt;code&gt;name&lt;/code&gt; column in the index. The following query would be able to perform an index-only scan:&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="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&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;span class="n"&gt;order_line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;order_line&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_ean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An obvious benefit of using &lt;code&gt;INCLUDE(...)&lt;/code&gt; is that you can add additional columns to a unique index without making it part of the uniqueness constraint. But be aware that it will consume storage.&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%2Fqkefjdkdbm8enkw2bwd0.jpg" 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%2Fqkefjdkdbm8enkw2bwd0.jpg" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tuning Tricks
&lt;/h1&gt;

&lt;p&gt;When you start tuning queries it is strongly advised to &lt;a href="https://www.postgresql.org/docs/current/sql-vacuum.html" rel="noopener noreferrer"&gt;vacuum and analyze&lt;/a&gt; or at least &lt;a href="https://www.postgresql.org/docs/current/sql-analyze.html" rel="noopener noreferrer"&gt;analyze&lt;/a&gt; the tables you are going to plan around with.&lt;/p&gt;

&lt;p&gt;Both actions can be done without locking tables. But obviously they will affect performance of running operations.&lt;/p&gt;

&lt;p&gt;As the query optimizer is affected by the gathered statistics it is important that they are relatively fresh. PostgreSQL will auto-vacuum and auto-analyze tables at certain moments in time. It could very well happen that this happens during your tuning process. Then it might look like you improved the query, but this was due to a different query plan with the newer statistics.&lt;/p&gt;

&lt;p&gt;You can check how recently tables where analyzed or vacuumed by checking the &lt;a href="https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-ALL-TABLES-VIEW" rel="noopener noreferrer"&gt;pg_stat_all_tables view&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But now some tricks I applied recently to improve some queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Too Big to Join
&lt;/h2&gt;

&lt;p&gt;This first trick will seem weird. You would expect the query planner to do this by default. But apparently this was not the case on the server where I applied it. Maybe it has changed in newer versions, but for PostgreSQL this trick turned a query which took hours to execute, into a query which takes only a few seconds.&lt;/p&gt;

&lt;p&gt;The goal of this query was to retrieve a bunch of details on orders which were changed during a a specific period. The database structure is roughly as follows.&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%2F87uwuj1y7n65n4ebc9ok.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%2F87uwuj1y7n65n4ebc9ok.png" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These tables contain a lot of rows of data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;orders&lt;/td&gt;
&lt;td&gt;31,889,100&lt;/td&gt;
&lt;td&gt;15 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;order_line&lt;/td&gt;
&lt;td&gt;73,699,400&lt;/td&gt;
&lt;td&gt;70 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;order_line_detail&lt;/td&gt;
&lt;td&gt;2,320,400,000&lt;/td&gt;
&lt;td&gt;265 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Attempt #1: Simple Select
&lt;/h3&gt;

&lt;p&gt;The most straightforward query to get all the details of the orders which were changed in the last 6 hours would be like this:&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_line&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_line_detail&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_update&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'6 hours'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query takes hours to execute. The query plan shows why. Note: because it takes a really long time, the query plans are executed without actual timings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hash Join  (cost=10717890.78..77356068.23 rows=823481 width=28)
  Hash Cond: (d.line_id = l.id)
  -&amp;gt;  Seq Scan on order_line_detail d  (cost=0.00..57928447.92 rows=2320398592 width=28)
  -&amp;gt;  Hash  (cost=10717563.84..10717563.84 rows=26155 width=4)
        -&amp;gt;  Hash Join  (cost=42202.77..10717563.84 rows=26155 width=4)
              Hash Cond: (l.order_systemid = o.id)
              -&amp;gt;  Seq Scan on order_line l  (cost=0.00..9938105.76 rows=73699376 width=8)
              -&amp;gt;  Hash  (cost=42061.31..42061.31 rows=11317 width=4)
                    -&amp;gt;  Index Scan using order_last_update_idx on orders o  (cost=0.57..42061.31 rows=11317 width=4)
                          Index Cond: (last_update &amp;gt; (now() - '06:00:00'::interval))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It selects the orders based on an index. But after that the two biggest tables receive a sequential scan. You might think indexes on the foreign keys are missing. But this is not the case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt #2: Sub-selects
&lt;/h3&gt;

&lt;p&gt;So lets try something else, using sub-selects.&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="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;order_line_detail&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;order_line&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_update&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'6 hours'&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 plan changed, looks more complex, but there are still the two really expensive sequential table scans.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hash Join  (cost=11221517.77..94488998.34 rows=2320398592 width=28)
  Hash Cond: (d.line_id = l.id)
  -&amp;gt;  Seq Scan on order_line_detail d  (cost=0.00..57928447.92 rows=2320398592 width=28)
  -&amp;gt;  Hash  (cost=11136752.37..11136752.37 rows=6781232 width=4)
        -&amp;gt;  Unique  (cost=11102846.21..11136752.37 rows=6781232 width=4)
              -&amp;gt;  Sort  (cost=11102846.21..11119799.29 rows=6781232 width=4)
                    Sort Key: l.id
                    -&amp;gt;  Hash Semi Join  (cost=42195.43..10333409.79 rows=6781232 width=4)
                          Hash Cond: (l.order_systemid = o.id)
                          -&amp;gt;  Seq Scan on order_line l  (cost=0.00..9938105.76 rows=73699376 width=8)
                          -&amp;gt;  Hash  (cost=42053.99..42053.99 rows=11315 width=4)
                                -&amp;gt;  Index Scan using order_last_update_idx on orders o  (cost=0.57..42053.99 rows=11315 width=4)
                                      Index Cond: (last_update &amp;gt; (now() - '06:00:00'::interval))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still not really usable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt #3: Common Table Expressions
&lt;/h3&gt;

&lt;p&gt;PostgreSQL has support for so called &lt;a href="https://www.postgresql.org/docs/current/queries-with.html" rel="noopener noreferrer"&gt;Common Table Expressions&lt;/a&gt; (CTE). While a simple query like this has no benefit for the typical use-case for CTEs, it would not hurt to try.&lt;/p&gt;

&lt;p&gt;The new query became:&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;with&lt;/span&gt; &lt;span class="n"&gt;cte_order&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; 
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_update&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'6 hours'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;cte_line&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;order_line&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cte_order&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;order_line_details&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cte_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks quite complicated for what it is supposed to do. Basically each sub-select was moved to a CTE.&lt;/p&gt;

&lt;p&gt;So lets take a loot at the query. (Note: execute with actual times, because it's so fast)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nested Loop  (cost=1225650.89..19184158.78 rows=1160199296 width=28) (actual time=471.251..1171.805 rows=1500960 loops=1)
  CTE cte_order
    -&amp;gt;  Index Scan using co_last_update_idx on orders o  (cost=0.57..42009.62 rows=11303 width=4) (actual time=0.262..51.518 rows=8777 loops=1)
          Index Cond: (last_update &amp;gt; (now() - '06:00:00'::interval))
  CTE cte_line
    -&amp;gt;  Nested Loop  (cost=254.88..354522.71 rows=36849688 width=4) (actual time=56.620..443.167 rows=35141 loops=1)
          -&amp;gt;  HashAggregate  (cost=254.32..256.32 rows=200 width=4) (actual time=56.567..61.080 rows=8777 loops=1)
                -&amp;gt;  CTE Scan on cte_order  (cost=0.00..226.06 rows=11303 width=4) (actual time=0.265..53.986 rows=8777 loops=1)
          -&amp;gt;  Index Scan using order_line_order_id_idx on order_line l  (cost=0.57..1765.34 rows=599 width=8) (actual time=0.020..0.042 rows=4 loops=8777)
                Index Cond: (order_id = cte_order.id)
  -&amp;gt;  HashAggregate  (cost=829117.98..829119.98 rows=200 width=4) (actual time=471.222..489.199 rows=35141 loops=1)
        -&amp;gt;  CTE Scan on cte_line  (cost=0.00..736993.76 rows=36849688 width=4) (actual time=56.623..454.029 rows=35141 loops=1)
  -&amp;gt;  Index Scan using order_line_detail_entity_id_idx on order_line_detail d  (cost=0.58..89389.59 rows=40294 width=28) (actual time=0.006..0.014 rows=43 loops=35141)
        Index Cond: (line_id = cte_line.id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you have it. No sequential table scans, only index usage. The query took a little over 1 second to find 1,500,960 rows. The cost estimate is seriously off on this one. This is why you should not rely on just the cost estimations, but also try &lt;code&gt;EXPLAIN&lt;/code&gt; with the &lt;code&gt;ANALYZE&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;Despite that all three queries would produce the exact same output, feeding a different construction to the query planner can give quite a different result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pivot Point
&lt;/h2&gt;

&lt;p&gt;This next trick cost me a lot of time to execute. First of all I had to properly format this massive 350 line long reporting query into something that was readable.&lt;/p&gt;

&lt;p&gt;Part of the query sort of looked like this.&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;with&lt;/span&gt; &lt;span class="n"&gt;order_price_transport&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&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;orders&lt;/span&gt;
    &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FUEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FREIGHT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="n"&gt;order_price_other&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&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;orders&lt;/span&gt;
    &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FUEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'FREIGHT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="c1"&gt;-- ... a whole bunch bunch of other CTEs&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;-- ...&lt;/span&gt;
    &lt;span class="n"&gt;est_trans_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;act_trans_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;est_other_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;act_other_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_price_transport&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;est_trans_price&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;est_trans_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;est_trans_price&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="s1"&gt;'estimated'&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_price_transport&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;act_trans_price&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;act_trans_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;act_trans_price&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="s1"&gt;'actual'&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_price_other&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;est_other_price&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;est_other_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;est_other_price&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="s1"&gt;'estimated'&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_price_other&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;act_other_price&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;act_trans_price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;act_other_price&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="s1"&gt;'actual'&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;START_DATE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;END_DATE&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ORGANIZATION_LIST&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The places where I put &lt;code&gt;-- ...&lt;/code&gt; contained even more stuff. Simple to say, this report did not run well. It contained a lot of similar common table expressions (CTEs). Some of them were even joined multiple times.&lt;/p&gt;

&lt;p&gt;The results of CTEs, and views for that matter, are &lt;em&gt;not cached&lt;/em&gt;. They are executed every time they are used. So just from the query which is shown above the &lt;code&gt;price_parts&lt;/code&gt; table is queried 4 times already. &lt;/p&gt;

&lt;p&gt;You will note that the main query contains some where clauses on the &lt;code&gt;orders&lt;/code&gt; table. These were not included in the CTEs which basically used the same order rows. Copying those where clauses to each CTE already makes quite the improvement. But the &lt;code&gt;price_parts&lt;/code&gt; and &lt;code&gt;orders&lt;/code&gt; tables are still consulted much more than needed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;price_parts&lt;/code&gt; table can contain multiple rows of price parts of an order. Each row has a code, amount, and status (if it was an estimation, or the actual price). Part of this report was to produce an overview of certain groups of prices for a selection of orders. Basically, it was meant to pivot the rows of the &lt;code&gt;price_parts&lt;/code&gt; table to columns in the resulting report.&lt;/p&gt;

&lt;p&gt;There is no need to pivot this data at the last possible moment. You can do that in a CTE too. So that's what I did. I combined all the CTEs reasoning on the same tables where the different was mostly the content of the where clauses. This transformed the above query to this (maybe even more complex looking 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;with&lt;/span&gt; &lt;span class="n"&gt;order_prices&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'estimated'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'trans'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;est_trans_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'actual'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'trans'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;act_trans_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;-- ...&lt;/span&gt;
        &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'estimated'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'other'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;est_other_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'actual'&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'other'&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;act_other_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt;
            &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; 
                &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;price_part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'FUEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FREIGHT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s1"&gt;'trans'&lt;/span&gt;
                &lt;span class="c1"&gt;-- ...&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;'other'&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;price_part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
        &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;price_part&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;price_part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;START_DATE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;END_DATE&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ORGANIZATION_LIST&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;
    &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; 
    &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="c1"&gt;-- ...&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;est_trans_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;act_trans_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;est_other_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;act_other_price&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;order_prices&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creation_date&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;START_DATE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;END_DATE&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ORGANIZATION_LIST&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;456&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 most inner select of the &lt;code&gt;order_prices&lt;/code&gt; CTE all the relevant data is gathered from the &lt;code&gt;price_part&lt;/code&gt; table, duplicated the filters on the applicable &lt;code&gt;orders&lt;/code&gt; rows. The rows are are grouped on the order ID, price part code, and status, and a summation is done of the amounts. Instead of returning the actual code of the &lt;code&gt;price_part&lt;/code&gt; row, it is translated into a new group which will be used during the pivot.&lt;/p&gt;

&lt;p&gt;This select query is then used as a table for the outer select of the &lt;code&gt;order_prices&lt;/code&gt; CTE. This is where the pivot takes place. Everything of the &lt;em&gt;data&lt;/em&gt; is selected, and grouped on just the id. For each code and status a column is made using a conditional sum aggregation. &lt;/p&gt;

&lt;p&gt;While the resulting query was not much smaller, as the new CTEs take almost the same amount of lines (only 50 lines less). It did however reduce 21 joins of CTEs to only 5, as there were now only 5 CTEs. Not only are the number of joins less, the side of the data to carry over in each part of the query is also less, and thus less memory is needed to execute the query.&lt;/p&gt;

&lt;p&gt;With these changes the report execution time dropped from 30 minutes, to just 1 minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitless
&lt;/h2&gt;

&lt;p&gt;So this is a case where the query ran well, until the point where we changed the server configuration to make better use of the available system memory. &lt;/p&gt;

&lt;p&gt;The query is quite simple:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7000&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;status_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&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;order_type_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;146630533&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;system_entry_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2019-03-24 23:30:03.524'&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;system_entry_date&lt;/span&gt; &lt;span class="k"&gt;asc&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the query plan (no actual times, because it takes too long).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Limit  (cost=0.56..23639.89 rows=25 width=2972)
  -&amp;gt;  Index Scan using idx_order_entry_date on orders  (cost=0.56..10885436.12 rows=11512 width=2972)
        Index Cond: (system_entry_date &amp;lt; '2019-03-24 23:30:03.524'::timestamp without time zone)
        Filter: ((status_id = ANY ('{2,28}'::integer[])) AND (organization_id = 7000) AND (order_type_id = 146630533))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first sight it looks ok. Except that it is using the index &lt;em&gt;order_entry_date_idx&lt;/em&gt; which is not the best index to use. There is a more specific index which also includes some other columns. With this query plan it is basically executing a sequential table scan. The index still selects the majority of the rows, it's the subsequent filtering which reduces the number of rows.&lt;/p&gt;

&lt;p&gt;If I drop the &lt;code&gt;limit 25&lt;/code&gt; the query plan changes drastically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sort  (cost=36349.54..36378.32 rows=11512 width=2972) (actual time=0.163..0.163 rows=0 loops=1)
  Sort Key: system_entry_date
  Sort Method: quicksort  Memory: 25kB
  -&amp;gt;  Index Scan using idx_order_statustype on orders  (cost=0.56..35573.01 rows=11512 width=2972) (actual time=0.136..0.136 rows=0 loops=1)
        Index Cond: ((status_id = ANY ('{2,28}'::integer[])) AND (order_type_id = 146630533) AND (system_entry_date &amp;lt; '2019-03-24 23:30:03.524'::timestamp without time zone))
        Filter: (organization_id = 7000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time the correct index is being used. Even though the estimated costs are higher, the actual time is much better.&lt;/p&gt;

&lt;p&gt;If I drop the &lt;code&gt;order by&lt;/code&gt; in the query instead, it also uses the correct index. Note that due to the nature of the b-tree index the results would actually be returned in ascending order to being with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Limit  (cost=0.56..77.82 rows=25 width=2972) (actual time=0.142..0.142 rows=0 loops=1)
  -&amp;gt;  Index Scan using idx_order_statustype on orders  (cost=0.56..35573.01 rows=11512 width=2972) (actual time=0.135..0.135 rows=0 loops=1)
        Index Cond: ((status_id = ANY ('{2,28}'::integer[])) AND (order_type_id = 146630533) AND (system_entry_date &amp;lt; '2019-03-24 23:30:03.524'::timestamp without time zone))
        Filter: (organization_id = 7000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I change the &lt;code&gt;in (2, 28)&lt;/code&gt; into &lt;code&gt;in (2)&lt;/code&gt; again the correct index is used. It does not matter if you do &lt;code&gt;foo in (2, 28)&lt;/code&gt; or &lt;code&gt;foo = 2 or foo = 28&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Limit  (cost=0.56..63.64 rows=19 width=2972) (actual time=0.105..0.105 rows=0 loops=1)
  -&amp;gt;  Index Scan using idx_order_statustype on orders  (cost=0.56..63.64 rows=19 width=2972) (actual time=0.103..0.103 rows=0 loops=1)
        Index Cond: ((status_id = 2) AND (order_type_id = 146630533) AND (system_entry_date &amp;lt; '2019-03-24 23:30:03.524'::timestamp without time zone))
        Filter: (organization_id = 7000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even increasing the limit to 40 produces a better result&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Limit  (cost=35936.90..35937.00 rows=40 width=2972) (actual time=3.078..3.078 rows=0 loops=1)
  -&amp;gt;  Sort  (cost=35936.90..35965.68 rows=11512 width=2972) (actual time=3.076..3.076 rows=0 loops=1)
        Sort Key: system_entry_date
        Sort Method: quicksort  Memory: 25kB
        -&amp;gt;  Index Scan using idx_order_statustype on orders  (cost=0.56..35573.01 rows=11512 width=2972) (actual time=3.066..3.066 rows=0 loops=1)
              Index Cond: ((status_id = ANY ('{2,28}'::integer[])) AND (order_type_id = 146630533) AND (system_entry_date &amp;lt; '2019-03-24 23:30:03.524'::timestamp without time zone))
              Filter: (organization_id = 7000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query planner is clearly way too optimistic in using the &lt;em&gt;idx_order_entry_date&lt;/em&gt; index. If you look at the first query plan you see that the pessimistic cost of using the wrong index is actually quite high. But the limit reduces the cost a lot. It goes from &lt;code&gt;cost=0.56..10885436.12&lt;/code&gt; to &lt;code&gt;cost=0.56..23639.89&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After some searching this appears to be a rather &lt;a href="https://www.postgresql.org/message-id/CA%2BU5nMK_TatpM9r7Z9OzZ7Kv-6cmE8Pdw_wrRnyxUypDkAFADQ%40mail.gmail.com" rel="noopener noreferrer"&gt;complex bug&lt;/a&gt; in PostgreSQL.&lt;/p&gt;

&lt;p&gt;In this particular case there were two suitable options available to improve the query:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a higher limit; it only needed to be slightly higher, which did not impact the rest of the process.&lt;/li&gt;
&lt;li&gt;no ordering; this was not vital anyway, and the returned order was good enough already.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In some database system you would solve this problem by using index hinting. But this is &lt;a href="https://www.2ndquadrant.com/en/blog/hinting_at_postgresql/" rel="noopener noreferrer"&gt;not something PostgreSQL developers consider introducing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The trick here is that sometimes relaxing some constraints could result in a better performance. It might seem like a workaround of a bug, which is also is, but sometimes you need these tricks. This problem was observed in 9.5, it could very well not be there anymore in a newer version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexes go wild
&lt;/h2&gt;

&lt;p&gt;B-Tree indexes (the default type) can also be used in wildcard searches when the wildcard is on the end: &lt;code&gt;col LIKE 'foo%'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is a small gotcha though. If the used collation is not 'C' then it does not work out of the box.&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;city_name_idx&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;city_name&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'LONDON%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on postal_location  (cost=0.00..69965.75 rows=4392 width=75) (actual time=114.905..324.757 rows=4346 loops=1)
  Filter: ((city_name)::text ~~ 'LONDON%'::text)
  Rows Removed by Filter: 1974114
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to explicitly define the index's &lt;a href="https://www.postgresql.org/docs/current/indexes-opclass.html" rel="noopener noreferrer"&gt;operator class&lt;/a&gt; to use a &lt;em&gt;pattern&lt;/em&gt; variant.&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;city_name_idx&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city_name&lt;/span&gt; &lt;span class="n"&gt;varchar_pattern_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;city_name&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'LONDON%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitmap Heap Scan on postal_location  (cost=111.74..12683.84 rows=4392 width=75) (actual time=0.288..0.812 rows=4346 loops=1)
  Filter: ((city_name)::text ~~ 'LONDON%'::text)
  -&amp;gt;  Bitmap Index Scan on city_name_idx  (cost=0.00..110.64 rows=4221 width=0) (actual time=0.277..0.277 rows=4346 loops=1)
        Index Cond: (((city_name)::text ~&amp;gt;=~ 'LONDON'::text) AND ((city_name)::text ~&amp;lt;~ 'LONDOO'::text))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that you should also create an index with the default operator class if you want queries involving ordinary &amp;lt;, &amp;lt;=, &amp;gt;, or &amp;gt;= comparisons to use an index. Such queries cannot use the xxx_pattern_ops operator classes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Leading Wildcards
&lt;/h3&gt;

&lt;p&gt;If the wildcard is at the beginning, this will not work and you are back to a sequential scan of the table.&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;explain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;city_name&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%LONDON'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on postal_location  (cost=0.00..69965.75 rows=4392 width=75) (actual time=118.001..361.855 rows=4316 loops=1)
  Filter: ((city_name)::text ~~ '%LONDON'::text)
  Rows Removed by Filter: 1974144
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However you can create an index with an expression to index the reversed value.&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;city_name_rev_idx&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;varchar_pattern_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;postal_location&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%LONDON'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bitmap Heap Scan on postal_location  (cost=257.83..24199.06 rows=9892 width=75) (actual time=0.365..1.133 rows=4316 loops=1)
  Filter: (reverse((city_name)::text) ~~ 'NODNOL%'::text)
  -&amp;gt;  Bitmap Index Scan on city_name_rev_idx  (cost=0.00..255.35 rows=9892 width=0) (actual time=0.351..0.351 rows=4316 loops=1)
        Index Cond: ((reverse((city_name)::text) ~&amp;gt;=~ 'NODNOL'::text) AND (reverse((city_name)::text) ~&amp;lt;~ 'NODNOM'::text))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost estimation is much higher than of the normal index, but still much lower than the sequential table scan. And more importantly, the actual time is almost comparable to normal index, and significantly better than the sequential table scan.&lt;/p&gt;

&lt;p&gt;The reverse wildcard index is especially useful for places where people generally enter the last few characters instead of the first few. For example in case of order numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Readable Code
&lt;/h2&gt;

&lt;p&gt;The last trick should be a no-brainer. But as I mentioned earlier, I had to reformat a query before I could even start optimizing it.&lt;/p&gt;

&lt;p&gt;I handled an other huge reporting query, which was really badly formatted. While formatting the query so that I could try to figure out what it was actually doing I saw something which might be a typo. In one of the sub-selects a wrong table alias was used. When I corrected the alias to the one I though was correct the query was really fast, and apparently the output was correct too.&lt;/p&gt;

&lt;p&gt;This typo would probably have been easily spotted by the original author if they formatted their SQL properly.&lt;/p&gt;

&lt;p&gt;To this day, I do not know what this query was actually supposed to do. But the output was correct, the query was fast, and I was done dealing with this issue within 30 minutes.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>performance</category>
    </item>
    <item>
      <title>Terrible Interview Questions</title>
      <dc:creator>Michiel Hendriks</dc:creator>
      <pubDate>Mon, 29 Apr 2019 11:27:24 +0000</pubDate>
      <link>https://forem.com/elmuerte/terrible-interview-questions-3ka</link>
      <guid>https://forem.com/elmuerte/terrible-interview-questions-3ka</guid>
      <description>&lt;p&gt;Lets have some fun, and come up with the worst job interview question you can think of.&lt;/p&gt;

&lt;p&gt;We have all heard about the job interview questions which are simply bad. Questions which test no practical skill, like asking people to write a quick sort implementation on a whiteboard. Or simply weird questions, which you have no idea about what they could imply, like "Are you a dog person or cat person?".&lt;/p&gt;

&lt;p&gt;So let me hear the worst interview questions you can come up with. Which are so bad, that they become funny. (Bonus points if this was an actual question.)&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>fun</category>
      <category>interview</category>
    </item>
  </channel>
</rss>
