<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sysemperor</title>
    <description>The latest articles on DEV Community by Sysemperor (@sysemperor).</description>
    <link>https://dev.to/sysemperor</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%2F3921740%2F8ae865c6-4c0c-49f3-8a52-46417ff97ea2.png</url>
      <title>DEV Community: Sysemperor</title>
      <link>https://dev.to/sysemperor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sysemperor"/>
    <language>en</language>
    <item>
      <title>How to Correctly Read a PostgreSQL EXPLAIN ANALYZE Output</title>
      <dc:creator>Sysemperor</dc:creator>
      <pubDate>Thu, 28 May 2026 09:30:40 +0000</pubDate>
      <link>https://dev.to/sysemperor/how-to-correctly-read-a-postgresql-explain-analyze-output-3c73</link>
      <guid>https://dev.to/sysemperor/how-to-correctly-read-a-postgresql-explain-analyze-output-3c73</guid>
      <description>&lt;p&gt;A slow query lands in your lap. You run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; and get back a wall of indented text that looks like someone fed a query plan through a blender. Most developers stare at it for a few minutes and give up.&lt;/p&gt;

&lt;p&gt;You do not need to understand every node. There are five patterns that cover the vast majority of real-world slow queries, and once you can spot them, the output goes from intimidating to immediately actionable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run it
&lt;/h2&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="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&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;ON&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;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&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;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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;order_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;BUFFERS&lt;/code&gt; to also see cache hit rates:&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="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Read the output
&lt;/h2&gt;

&lt;p&gt;The result is a tree of nodes, indented to show parent-child relationships. Each node performs one step of the query. The overall query is the root node; the deepest indented nodes run 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;Limit&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1243&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1243&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;58&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;231&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;234&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Sort&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1243&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1268&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;228&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;229&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&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="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
        &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="n"&gt;heapsort&lt;/span&gt;  &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;HashAggregate&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;868&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;968&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;891&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&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;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
              &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Hash&lt;/span&gt; &lt;span class="k"&gt;Left&lt;/span&gt; &lt;span class="k"&gt;Join&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;295&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;743&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;532&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="mi"&gt;714&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;Hash&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&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;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;Scan&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;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;789&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;012&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;341&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Hash&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;010&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;218&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Planning&lt;/span&gt; &lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;234&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="n"&gt;Execution&lt;/span&gt; &lt;span class="nb"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each node shows two cost/row estimates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cost=start..total&lt;/code&gt;&lt;/strong&gt; — the planner's estimate. &lt;code&gt;start&lt;/code&gt; is the cost before the first row can be returned; &lt;code&gt;total&lt;/code&gt; is the cost to return all rows. Units are arbitrary but consistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;actual time=start..total&lt;/code&gt;&lt;/strong&gt; — the real measured time in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;rows&lt;/code&gt;&lt;/strong&gt; — how many rows were actually returned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;loops&lt;/code&gt;&lt;/strong&gt; — how many times this node ran. Actual time is per loop, so multiply by loops for the total.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Find the slow node
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;actual time&lt;/code&gt; value on a node includes all child nodes. The node doing the most work is the one with the highest &lt;code&gt;actual time&lt;/code&gt; that is not caused by slow children. Compare &lt;code&gt;actual time&lt;/code&gt; at each level to find where time is actually consumed.&lt;/p&gt;

&lt;p&gt;Look for a big jump: if the parent took 48ms and its child took 44ms, the parent itself only contributed 4ms. If the parent took 48ms and all children took 5ms total, the parent's own work is 43ms — investigate that node.&lt;/p&gt;




&lt;h2&gt;
  
  
  The patterns to recognise
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sequential scan on a large table (&lt;code&gt;Seq Scan&lt;/code&gt;):&lt;/strong&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="n"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;Scan&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;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;789&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A sequential scan reads every row. Fine for small tables or when you need most of the rows. A problem when you are selecting a small fraction of a large table. The fix is usually an index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row estimate wildly off:&lt;/strong&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;950000&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The planner estimated 10,000 rows but got 950,000. Bad estimates lead to bad plan choices. Run &lt;code&gt;ANALYZE tablename&lt;/code&gt; to update statistics and see if the estimate improves. Outdated statistics are the most common cause of bad query plans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nested Loop on large tables:&lt;/strong&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="n"&gt;Nested&lt;/span&gt; &lt;span class="n"&gt;Loop&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50000&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&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;A nested loop runs the inner query once for each row in the outer query. Fine when the outer table is small or the inner side is indexed. A nested loop with &lt;code&gt;loops=1000&lt;/code&gt; and a sequential scan inside is almost always the reason a query is slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash Join:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A hash join is usually efficient — PostgreSQL builds an in-memory hash table from the smaller side, then probes it. If memory is too small, the hash spills to disk (&lt;code&gt;Batches: 8&lt;/code&gt; instead of &lt;code&gt;Batches: 1&lt;/code&gt; in the output). Increase &lt;code&gt;work_mem&lt;/code&gt; for the session if hash batches are high:&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;SET&lt;/span&gt; &lt;span class="n"&gt;work_mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'64MB'&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="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Check index usage
&lt;/h2&gt;

&lt;p&gt;When a query is slow on a large table and you expect an index to be used:&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="k"&gt;ANALYZE&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;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&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 see &lt;code&gt;Seq Scan&lt;/code&gt; instead of &lt;code&gt;Index Scan&lt;/code&gt;, either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The index does not exist&lt;/li&gt;
&lt;li&gt;The column is not indexed&lt;/li&gt;
&lt;li&gt;The query is returning a large enough fraction of rows that PostgreSQL prefers a sequential scan (this is correct behaviour)&lt;/li&gt;
&lt;li&gt;Statistics are outdated and PostgreSQL underestimates selectivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check existing indexes:&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="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;indexname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indexdef&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_indexes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Create an index and verify
&lt;/h2&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;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_id&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;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CONCURRENTLY&lt;/code&gt; builds the index without locking the table for writes. Run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; again after the index exists — the plan should show &lt;code&gt;Index Scan&lt;/code&gt; or &lt;code&gt;Bitmap Index Scan&lt;/code&gt; instead of &lt;code&gt;Seq Scan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;SysEmperor's freshly released &lt;a href="https://sysemperor.com/tools/explain-analyzer" rel="noopener noreferrer"&gt;EXPLAIN Query Analyzer&lt;/a&gt; will analyze your EXPLAIN outputs in a clean environment, directly in your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  When the planner is still choosing a bad plan
&lt;/h2&gt;

&lt;p&gt;If you have added an index and the planner is still doing a sequential scan on a selective 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;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;-- update table statistics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that does not help, check whether the column has a very skewed distribution (many NULLs, one dominant value). For those cases, partial indexes and extended statistics give the planner more accurate information to work with.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? SysEmperor has more database and Linux tutorials, free developer tools, and downloadable AI skills at &lt;a href="https://sysemperor.com" rel="noopener noreferrer"&gt;sysemperor.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>sql</category>
      <category>performance</category>
    </item>
    <item>
      <title>git bisect: Find the Commit That Broke Everything</title>
      <dc:creator>Sysemperor</dc:creator>
      <pubDate>Tue, 26 May 2026 08:56:10 +0000</pubDate>
      <link>https://dev.to/sysemperor/git-bisect-find-the-commit-that-broke-everything-2mn3</link>
      <guid>https://dev.to/sysemperor/git-bisect-find-the-commit-that-broke-everything-2mn3</guid>
      <description>&lt;p&gt;There is a particular kind of dread that comes with opening a bug report, running &lt;code&gt;git log&lt;/code&gt;, and seeing 300 commits between "this worked" and "this doesn't." Most people start at HEAD and work backwards one commit at a time. There is a much better way.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; does a binary search through your commit history. You tell it one commit where things were good and one where they are bad. It checks out the midpoint. You test and say good or bad. It halves the search space and repeats. It finds the culprit in at most eight steps regardless of whether you have 50 commits or 5,000.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;You mark a known-good commit and a known-bad commit. Git checks out the midpoint. You test and tell Git whether that commit is good or bad. Git halves the search space and repeats. After a handful of rounds, it pinpoints the first bad commit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start a bisect session
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad                     &lt;span class="c"&gt;# current commit is bad&lt;/span&gt;
git bisect good v1.4.0             &lt;span class="c"&gt;# this tag (or hash) was working&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git checks out a commit halfway between the two. Test the code — does the bug exist?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect good    &lt;span class="c"&gt;# bug is NOT present in this commit&lt;/span&gt;
git bisect bad     &lt;span class="c"&gt;# bug IS present in this commit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat until Git announces the culprit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b2c3d4e5 is the first bad commit
commit b2c3d4e5
Author: ...
Date:   ...

    feat: add pagination to user list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  End the session
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns you to the branch you started from. Always run this when you are done — leaving a bisect session open causes confusing behaviour with other Git commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automate with a test script
&lt;/h2&gt;

&lt;p&gt;If you have a script that exits 0 when the code is good and non-zero when it is bad, you can automate the entire process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad HEAD
git bisect good v1.4.0
git bisect run ./scripts/test-feature.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git runs the script at each step and marks the result automatically. The session finishes without any input from you, often in under a minute.&lt;/p&gt;

&lt;p&gt;The script needs to be reliable — flaky tests will mislead the binary search. It also needs to be compatible with the range of commits being tested, which is sometimes a problem if the test itself depends on a file that did not exist in older commits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Find when a file changed significantly
&lt;/h2&gt;

&lt;p&gt;If you know which file is involved but not which commit touched it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; path/to/file.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This narrows the suspect list. If the list is short enough, reviewing the diffs directly is faster than a bisect. For a long list, start bisect with the earliest commit that touches that file as the known-good point.&lt;/p&gt;




&lt;h2&gt;
  
  
  When bisect is hard to use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The bug is not reproducible with a simple script.&lt;/strong&gt; Manual bisect still works — you just test by hand at each step. Even manually, binary search on 100 commits takes at most 7 rounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The build is broken on many commits.&lt;/strong&gt; Tell Git to skip commits where you cannot test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect skip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git moves to a nearby commit and continues. The final result will be a range of commits rather than a single one if the true culprit is adjacent to a skipped commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug is a performance regression.&lt;/strong&gt; Benchmark scripts work fine as the test command — exit 0 if performance is acceptable, exit 1 if not. The threshold needs to be reliable enough that the result does not flip due to system noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result is a starting point, not the full answer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; finds the commit that first exhibited the bug — but the commit that caused it might be earlier. The commit git identifies might be an innocent victim: it calls a function that was broken by an earlier change, or it exercises a code path that already had a latent bug.&lt;/p&gt;

&lt;p&gt;Once you have the commit, read the diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git show b2c3d4e5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understand what it changed. Then look at the commits immediately before it if the change itself looks harmless. The bug is usually either in the identified commit or in a nearby one that set up the conditions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? SysEmperor has more Git tutorials and free developer tools at &lt;a href="https://sysemperor.com" rel="noopener noreferrer"&gt;sysemperor.com&lt;/a&gt; — including a &lt;a href="https://sysemperor.com/tools/table-editor" rel="noopener noreferrer"&gt;SQL Table Editor&lt;/a&gt;, &lt;a href="https://sysemperor.com/tools/chmod" rel="noopener noreferrer"&gt;chmod Calculator&lt;/a&gt;, and &lt;a href="https://sysemperor.com/skills/development/code-review" rel="noopener noreferrer"&gt;downloadable AI skills for Claude&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>debugging</category>
      <category>programming</category>
      <category>devtips</category>
    </item>
    <item>
      <title>tmux: Persistent Terminal Sessions for Developers</title>
      <dc:creator>Sysemperor</dc:creator>
      <pubDate>Tue, 19 May 2026 05:05:25 +0000</pubDate>
      <link>https://dev.to/sysemperor/tmux-persistent-terminal-sessions-for-developers-436d</link>
      <guid>https://dev.to/sysemperor/tmux-persistent-terminal-sessions-for-developers-436d</guid>
      <description>&lt;p&gt;I lost three hours of work to a dropped SSH connection before I learned about tmux. One detached session later and that problem stopped existing entirely. If you spend any meaningful time on remote servers, tmux is the single highest-leverage tool you can add to your workflow.&lt;/p&gt;

&lt;p&gt;Two things make it worth learning: sessions survive after you close your terminal or lose an SSH connection — your processes keep running and you reconnect to find everything exactly where you left it. And you can split a single terminal into multiple panes and windows without juggling tabs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian / Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;tmux

&lt;span class="c"&gt;# RHEL / Fedora / Rocky&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;tmux

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Core concepts
&lt;/h2&gt;

&lt;p&gt;tmux has three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session&lt;/strong&gt; — a collection of windows. One session per project is the natural fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window&lt;/strong&gt; — a full-screen view inside a session. Think of it like a browser tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pane&lt;/strong&gt; — a split region within a window. One window can hold several panes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;prefix key&lt;/strong&gt; is how you send commands to tmux without the keystrokes going to the program running inside it. The default is &lt;code&gt;Ctrl+B&lt;/code&gt;. Press it, release, then press the command key.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start and attach to sessions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create a new named session:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; myproject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Detach&lt;/strong&gt; (session keeps running in the background):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl+B  d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;List sessions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reattach by name:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux attach &lt;span class="nt"&gt;-t&lt;/span&gt; myproject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short form: &lt;code&gt;tmux a -t myproject&lt;/code&gt;. With only one session, &lt;code&gt;tmux a&lt;/code&gt; is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kill a session:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux kill-session &lt;span class="nt"&gt;-t&lt;/span&gt; myproject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Windows
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  c&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  n&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  p&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to window by number&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  0–9&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename current window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  ,&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close current window&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  &amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Windows appear in the status bar at the bottom. The active one has a &lt;code&gt;*&lt;/code&gt; next to its name.&lt;/p&gt;




&lt;h2&gt;
  
  
  Panes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Split top/bottom&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  "&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split left/right&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  %&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move between panes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  arrow keys&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resize pane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  Ctrl+arrow keys&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zoom to full screen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Ctrl+B  z&lt;/code&gt; (toggle)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close current pane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B  x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zoom is one of the most useful shortcuts — it temporarily expands a pane to fill the entire window without closing the others. Hit it again to return to the split view.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scrollback and copy mode
&lt;/h2&gt;

&lt;p&gt;Mouse scroll does not work inside tmux by default. To scroll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl+B  [
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enters copy mode. Arrow keys and &lt;code&gt;Page Up&lt;/code&gt;/&lt;code&gt;Page Down&lt;/code&gt; navigate. Press &lt;code&gt;/&lt;/code&gt; to search, &lt;code&gt;n&lt;/code&gt; for the next match, &lt;code&gt;q&lt;/code&gt; to exit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-start a session on login
&lt;/h2&gt;

&lt;p&gt;tmux sessions do not survive a reboot. For development machines where you always want a session waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# add to ~/.bashrc or ~/.zshrc&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMUX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;tmux attach &lt;span class="nt"&gt;-t&lt;/span&gt; main 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; main
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[ -z "$TMUX" ]&lt;/code&gt; guard prevents tmux from launching inside an existing tmux session, which would nest them.&lt;/p&gt;




&lt;h2&gt;
  
  
  ~/.tmux.conf
&lt;/h2&gt;

&lt;p&gt;A minimal config that most people find immediately useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start window and pane numbering at 1 instead of 0&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; base-index 1
setw &lt;span class="nt"&gt;-g&lt;/span&gt; pane-base-index 1

&lt;span class="c"&gt;# Enable mouse: click to focus panes, drag to resize&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; mouse on

&lt;span class="c"&gt;# Increase scrollback&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; history-limit 10000

&lt;span class="c"&gt;# Status bar: show session name on the left, time on the right&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; status-left &lt;span class="s2"&gt;"[#S] "&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; status-right &lt;span class="s2"&gt;"%H:%M"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload the config without restarting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Ctrl+B  :source-file ~/.tmux.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mouse support is the single option worth enabling immediately if you are used to a GUI terminal. It makes pane navigation and resizing click-based while you build up the keyboard shortcuts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? SysEmperor has more Linux tutorials and free developer tools — including a &lt;a href="https://sysemperor.com/tools/cron" rel="noopener noreferrer"&gt;Cron Visual Editor&lt;/a&gt;, &lt;a href="https://sysemperor.com/tools/fstab" rel="noopener noreferrer"&gt;fstab Editor&lt;/a&gt;, and a growing library of downloadable AI skills at &lt;a href="https://sysemperor.com" rel="noopener noreferrer"&gt;sysemperor.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>terminal</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI Isn't Replacing Programmers. It's Replacing the OS.</title>
      <dc:creator>Sysemperor</dc:creator>
      <pubDate>Sat, 09 May 2026 12:50:32 +0000</pubDate>
      <link>https://dev.to/sysemperor/ai-isnt-replacing-programmers-its-replacing-the-os-o52</link>
      <guid>https://dev.to/sysemperor/ai-isnt-replacing-programmers-its-replacing-the-os-o52</guid>
      <description>&lt;p&gt;The tech world is having a collective anxiety attack about AI. Every week there is a new think-piece about which jobs will disappear first, which languages will become obsolete, and whether learning to code still means anything. Most of this conversation is missing the point entirely.&lt;/p&gt;

&lt;p&gt;We're a small team at SysEmperor, but we agree on one thing: AI is not replacing programmers. It is replacing the operating system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Think about what an OS actually does
&lt;/h2&gt;

&lt;p&gt;An operating system is, at its core, a general-purpose interface between you and raw computing power. It does not do anything specific by default. It sits there, capable of anything — processing files, running servers, drawing interfaces, connecting to networks — waiting for the right input.&lt;/p&gt;

&lt;p&gt;Give it the right software and it becomes a video editor. Give it different software and it becomes a database server. Give it a compiler and a blank file and you can build whatever you want from scratch. The OS is a box of infinite potential. What you get out depends entirely on what you put in.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;




&lt;h2&gt;
  
  
  AI is that same box
&lt;/h2&gt;

&lt;p&gt;A large language model, by itself, does nothing specific. It sits there, capable of almost anything — writing code, analyzing documents, generating content, reasoning through problems — waiting for the right input.&lt;/p&gt;

&lt;p&gt;Give it the right prompt and it becomes a data analyst. Give it a different prompt and it becomes a legal document reviewer. Give it a well-crafted system prompt and a set of tools and you have built something that would have taken months to code from scratch.&lt;/p&gt;

&lt;p&gt;The parallel is not metaphorical. It is structural. An OS exposes raw compute. An LLM exposes raw intelligence. In both cases, the interface determines everything you actually get out of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Software was the previous abstraction layer
&lt;/h2&gt;

&lt;p&gt;When operating systems became mainstream, a new layer emerged: software. Most people could not write assembly or C. They did not need to. Microsoft Word handled documents. Excel handled numbers. Photoshop handled images.&lt;/p&gt;

&lt;p&gt;Software abstracted away the complexity of the OS. You no longer needed to understand file systems to edit a photo. The software handled the interface; the OS handled the hardware. Millions of people became productive without writing a single line of code.&lt;/p&gt;

&lt;p&gt;But this abstraction had a cost. Software is rigid. It does what its developers built it to do, within the boundaries they defined. If you needed something slightly different, you were stuck. The power of the underlying OS was there — but inaccessible without programming skills.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt engineering is the next abstraction layer
&lt;/h2&gt;

&lt;p&gt;What programming was to the OS, prompt engineering is to AI. It is the method by which someone can unlock the full capability of the platform beneath them.&lt;/p&gt;

&lt;p&gt;A well-engineered prompt is not just a question. It defines context, constraints, output format, and reasoning approach. It can encode complex workflows, handle edge cases, and produce consistent results across thousands of uses. Done well, a prompt can replace what would previously have required a custom application — months of development, a team of engineers, ongoing maintenance.&lt;/p&gt;

&lt;p&gt;We are already watching this play out. The first wave of SaaS services being disrupted is not coincidental. When a well-structured prompt can perform the core function of a single-purpose tool, that tool stops being worth paying for. Not for everything, not yet — but the direction is clear and the pace is accelerating.&lt;/p&gt;




&lt;h2&gt;
  
  
  But not everyone will write good prompts — and that is the whole point
&lt;/h2&gt;

&lt;p&gt;Here is the part most people skip.&lt;/p&gt;

&lt;p&gt;Not everyone could program. That is exactly why software existed in the first place. The OS could theoretically do anything, but most people needed someone to package that capability into something usable. Developers built those packages. Everyone else bought and ran them.&lt;/p&gt;

&lt;p&gt;The same dynamic is already playing out with AI. The raw capability of a language model is technically accessible to anyone with an API key. In practice, writing a complex, reliable, well-scoped prompt that performs a specific professional workflow consistently — and keeps working as models update — is a real skill. Not everyone will develop it. Not everyone will want to.&lt;/p&gt;

&lt;p&gt;There will be a class of people fluent in this new language. And there will be a much larger class of people who want the output without the craft. That second group needs packaged prompts. They need what software was: someone else's expertise, ready to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills are the new software
&lt;/h2&gt;

&lt;p&gt;This is the thesis we are building on at &lt;a href="https://sysemperor.com" rel="noopener noreferrer"&gt;SysEmperor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ready-to-use AI skills — carefully crafted system prompts with defined roles, constraints, and behaviours, packaged for immediate use — will become as common and as necessary as software packages are today. Not because AI is hard to use in a basic sense, but because using it well, consistently, for a specific professional purpose, requires real work that most people reasonably do not want to do themselves.&lt;/p&gt;

&lt;p&gt;The analogy holds all the way down. Software licenses are expensive for the same reason that some tasks will not be worth spending AI credits on — some people will still reach for a SaaS tool or a traditional application, for the same reason some developers still hand-code things they could automate. A more capable layer does not eliminate every option below it. It just changes which layer most people choose to operate at.&lt;/p&gt;

&lt;p&gt;That's why we started to offer a growing library of skills for Claude — downloadable, ready to use, no prompt engineering required. &lt;a href="https://sysemperor.com/skills" rel="noopener noreferrer"&gt;Browse the skills →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  This is not chaos. It is a layer shift.
&lt;/h2&gt;

&lt;p&gt;Every time a new abstraction layer emerged in computing, the same alarm bells rang. Compilers would make assembly programmers obsolete. GUIs would kill the command line. SaaS would destroy on-premise software. Web apps would make native developers irrelevant.&lt;/p&gt;

&lt;p&gt;Each time, the layer below did not vanish. It moved down the stack. Used by fewer people directly, but no less essential to everything built on top.&lt;/p&gt;

&lt;p&gt;The requirements from users have not changed either. People need to write things, analyze data, manage projects, communicate, build products. The underlying human needs are identical to what they were in 1995.&lt;/p&gt;

&lt;p&gt;What changes is the layer you use to meet those needs. That layer is shifting again. The interface is changing. The demands are not.&lt;/p&gt;

&lt;p&gt;AI is not bringing the chaos that some headlines are selling. It is the next iteration of a pattern that has repeated consistently since the first personal computer sat on a desk: raw capability, then an abstraction layer, then packaged tools for everyone who does not want to work at the abstraction layer directly.&lt;/p&gt;

&lt;p&gt;We have been here before. We know how this goes. The only question is which layer you want to work at — and whether you get there early enough to matter.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is from the team at &lt;a href="https://sysemperor.com" rel="noopener noreferrer"&gt;SysEmperor&lt;/a&gt; — a free toolkit for developers and sysadmins, with a growing library of downloadable AI skills for Claude. We also publish practical tutorials on Linux, DevOps, Git, PostgreSQL, and more.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>future</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
