<?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: sweemeng</title>
    <description>The latest articles on Forem by sweemeng (@sweemeng).</description>
    <link>https://forem.com/sweemeng</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%2F265071%2Ff82ea55c-5a18-4336-8f5e-01ce9195ffa0.png</url>
      <title>Forem: sweemeng</title>
      <link>https://forem.com/sweemeng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sweemeng"/>
    <language>en</language>
    <item>
      <title>pg_stat_statements are your friends.</title>
      <dc:creator>sweemeng</dc:creator>
      <pubDate>Wed, 19 Feb 2025 12:31:48 +0000</pubDate>
      <link>https://forem.com/sweemeng/pgstatstatements-are-your-friends-464n</link>
      <guid>https://forem.com/sweemeng/pgstatstatements-are-your-friends-464n</guid>
      <description>&lt;p&gt;Let me tell you a story of the day our users face slowness in our application. As we investigate further, we found out that our RDS instance CPU use is at constant 100%.&lt;/p&gt;

&lt;p&gt;One of the things we do is to increase our instance size, and that is not helpful. Which lead to one of my go-to tools in PostgreSQL for this kind of issue, &lt;code&gt;pg_stat_statements&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a rails application, there's many queries to the database. Tracing individual queries can be painful. There is where pg_stat_statements come in. When enabled, this logs all database queries to the database. This also collects various statistics in the query.&lt;/p&gt;

&lt;p&gt;This is a module that AWS have compiled in their PostgreSQL RDS instance. It is not something that is enabled by default. But it is easy to enable, AWS provides documentation for this.&lt;/p&gt;

&lt;p&gt;To use this, do a SQL query, this is a view. The following is my usual query to get the statistics that I need. This will get the total run time in minutes, and the mean execution time, number of calls, and actual query. This is ordered by the total_min field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  (total_exec_time / 1000 / 60) as total_min,
  mean_exec_time as avg_ms,
  calls,
  query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 5; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an example of how you use this. First create extensions, only for the first time. Then set extended view &lt;code&gt;\x&lt;/code&gt; to make view easier. Then actual query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db=&amp;gt; CREATE EXTENSION pg_stat_statements; 
db=&amp;gt; \x
db=&amp;gt; SELECT
  (total_exec_time / 1000 / 60) as total_min,
  mean_exec_time as avg_ms,
  calls,
  query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 5;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following. I edited the query; this is query post optimization. But you do not usually get the parameter, it will be set as $1 for first parameter, $2 for second etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total_min | 49.976002760516685
avg_ms    | 38.661167684772884
calls     | 77560
query     | SELECT field FROM "tables" WHERE "table"."field" = $1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you should do with this information is find out the function and from the function, infer potential parameter for running the query. Then run EXPLAIN query to find out issue with said database query.&lt;/p&gt;

&lt;p&gt;Now back to the story of the slowness in our database. From running query on pg_stat_statements . I with help of EXPLAIN I found out some of our column should be indexed added. That helped speed up our query.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>SQL Explain is your friend: PostgreSQL edition</title>
      <dc:creator>sweemeng</dc:creator>
      <pubDate>Wed, 19 Feb 2025 12:28:07 +0000</pubDate>
      <link>https://forem.com/sweemeng/sql-explain-is-your-friend-postgresql-edition-3aka</link>
      <guid>https://forem.com/sweemeng/sql-explain-is-your-friend-postgresql-edition-3aka</guid>
      <description>&lt;p&gt;Disclosure: I came out with the content, database setup, and queries. But I use ChatGPT and perplexity to fix the grammar, tone, and spelling.&lt;/p&gt;

&lt;p&gt;So you discovered an application running slow. You suspect that the application is running a slow database query. How do you find out?&lt;br&gt;
One way is to use &lt;code&gt;pg_stat_statements&lt;/code&gt;. The &lt;code&gt;pg_stat_statements&lt;/code&gt; tool collect all queries and their performance metrics. It allows you to identify the slowest query, but it does not show why they are slow. This is where the &lt;code&gt;EXPLAIN&lt;/code&gt; comes in, complementing &lt;code&gt;pg_stat_statements&lt;/code&gt; in your investigation.&lt;/p&gt;

&lt;p&gt;The SQL &lt;code&gt;EXPLAIN&lt;/code&gt; query is a way for you view the execution plan of your query. It provides information that will help you make changes to improve performance. &lt;code&gt;EXPLAIN&lt;/code&gt; query is not an SQL standard, but many database engines supports it. Since I primarily use PostgreSQL— and different engines output information in various formats — I will focus solely on PostgreSQL’s implementation.&lt;/p&gt;

&lt;p&gt;There are several forms of the &lt;code&gt;EXPLAIN&lt;/code&gt; command that I use. While there are additional variations available that you might find useful, the following are the ones I typically rely on:&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%2F0o1v2ocpxnpddxhvx9mu.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%2F0o1v2ocpxnpddxhvx9mu.png" alt="Various Form of SQL EXPLAIN" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key differences&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EXPLAIN $query&lt;/code&gt;: Display the query plan without executing query&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPLAIN ANALYZE $query&lt;/code&gt;: Executes the query and returns the query plan along with the execution time. Note that if you use this with UPDATE, DELETE, or INSERT statements, it will modify your data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPLAIN (ANALYZE, BUFFERS) $query&lt;/code&gt;: Performs the same as EXPLAIN ANALYZE but additionally provides information on shared buffers.
Let’s start with an example database I have created:-&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvxiin8wx7ho33lm1if2.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%2Fvvxiin8wx7ho33lm1if2.png" alt="The database schema" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is around 200 users, and 2000 medias.&lt;br&gt;
My go-to query is the &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt; command. Below is an example session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;medium_tutorial=# EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM medias WHERE user_id=109;
                                          QUERY PLAN                                           
-------------------------------------------------------------------------------------------------
Seq Scan on medias  (cost=0.00..40.00 rows=8 width=4) (actual time=0.150..0.270 rows=8 loops=1)
  Filter: (user_id = 109)
  Rows Removed by Filter: 1992
  Buffers: shared hit=15
Planning Time: 0.063 ms
Execution Time: 0.285 ms
(6 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s parse the output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Seq Scan on medias&lt;/code&gt;
This indicates that the query is performing a sequential scan on the medias table—meaning it is not using an index. (I will show an example of an index search later.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(cost=0.00..40.00 rows=8 width=4)&lt;/code&gt;
The query planner estimates an initial cost of 0.00 and a final cost of 40.00. It also estimates that the query will return 8 rows, with each row being 4 bytes wide. These cost metrics help the planner decide on the most optimal execution plan.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(actual time=0.150..0.270 rows=8 width=4)&lt;/code&gt;
This shows the actual time taken to execute the query. In this case, the execution started at 0.150 ms and ended at 0.270 ms.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Rows Removed by Filter: 1992&lt;/code&gt;
This indicates that 1992 rows were scanned and then discarded based on the filter condition. A high number of rows being filtered out may impact performance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Buffers: shared hit=15&lt;/code&gt;
This shows that 15 blocks of data were retrieved from the shared buffer cache. (In PostgreSQL, each block is typically 8KB.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Execution Time: 0.285 ms&lt;/code&gt;
The total time taken to execute the query was 0.285 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s add an index in the medias table.&lt;br&gt;
&lt;code&gt;CREATE INDEX medias_user_id_idx ON medias(user_id);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now rerun the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;medium_tutorial=# EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM medias WHERE user_id=109;
                                                       QUERY PLAN                                                        
---------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on medias  (cost=4.34..18.09 rows=8 width=4) (actual time=0.057..0.065 rows=8 loops=1)
  Recheck Cond: (user_id = 109)
  Heap Blocks: exact=5
  Buffers: shared hit=5 read=2
  -&amp;gt;  Bitmap Index Scan on medias_user_id_idx  (cost=0.00..4.34 rows=8 width=0) (actual time=0.046..0.046 rows=8 loops=1)
        Index Cond: (user_id = 109)
        Buffers: shared read=2
Planning:
  Buffers: shared hit=10 read=1
Planning Time: 0.782 ms
Execution Time: 0.082 ms
(11 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, there are a couple of differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Bitmap Heap Scan on Media&lt;/code&gt;
This indicates that the query is now using the index.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Buffers: shared hit=5 read=2&lt;/code&gt;
The new metric, read, shows the number of blocks read from disk (each block is typically 8KB, so 2 blocks equal 16KB). These blocks are then stored in the cache.&lt;/li&gt;
&lt;li&gt;Time Breakdown:
The output now provides a more detailed breakdown of the execution time, including both planning and execution phases.&lt;/li&gt;
&lt;li&gt;Faster Execution Time:
Finally, note that the overall execution time is faster after adding the index!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, you can also use &lt;code&gt;EXPLAIN&lt;/code&gt; to test your assumptions. Here are a couple of reasons why it's important to do so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unexpected Query Plans:
The query planner might not produce the query plan you expect, choosing instead a plan that it deems more cost-effective.&lt;/li&gt;
&lt;li&gt;ORM-Generated Queries:
Your ORM may generate queries that are not optimal. For example, it might generate a SELECT * query—even when you only need a few columns—which can be inefficient if the table has many columns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a concrete example. One might assume that a query like selecting id from users would simply use the index on medias. &lt;br&gt;
However, the actual plan shows that a join is performed instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;medium_tutorial=# EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM medias WHERE user_id in (SELECT id FROM users limit 100);
                                                    QUERY PLAN                                                     
---------------------------------------------------------------------------------------------------------------------
Hash Semi Join  (cost=3.25..54.62 rows=1000 width=4) (actual time=0.093..1.102 rows=1000 loops=1)
  Hash Cond: (medias.user_id = users.id)
  Buffers: shared hit=17
  -&amp;gt;  Seq Scan on medias  (cost=0.00..35.00 rows=2000 width=8) (actual time=0.009..0.345 rows=2000 loops=1)
        Buffers: shared hit=15
  -&amp;gt;  Hash  (cost=2.00..2.00 rows=100 width=4) (actual time=0.065..0.066 rows=100 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 12kB
        Buffers: shared hit=2
        -&amp;gt;  Limit  (cost=0.00..2.00 rows=100 width=4) (actual time=0.009..0.042 rows=100 loops=1)
              Buffers: shared hit=2
              -&amp;gt;  Seq Scan on users  (cost=0.00..4.00 rows=200 width=4) (actual time=0.008..0.025 rows=100 loops=1)
                    Buffers: shared hit=2
Planning:
  Buffers: shared hit=14 read=1
Planning Time: 0.397 ms
Execution Time: 1.207 ms
(16 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the plan reveals that the query uses a join rather than simply scanning the index on medias. This unexpected join is a perfect example of why it's essential to test your assumptions with &lt;code&gt;EXPLAIN&lt;/code&gt; before finalizing query optimizations.&lt;/p&gt;

&lt;p&gt;In conclusion, if you suspect that a query is running slowly, start by checking it with &lt;code&gt;pg_stat_statements&lt;/code&gt; and then use &lt;code&gt;EXPLAIN&lt;/code&gt; to dive deeper into its execution plan. These tools are essential for validating your assumptions and gaining a clear understanding of your database's behavior—especially in complex systems where subtle differences in execution can significantly impact performance.&lt;/p&gt;

</description>
      <category>postgressql</category>
      <category>database</category>
    </item>
  </channel>
</rss>
