<?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: sai anirud varma uppalapati</title>
    <description>The latest articles on DEV Community by sai anirud varma uppalapati (@sai_anirudvarmauppalapa).</description>
    <link>https://dev.to/sai_anirudvarmauppalapa</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%2F3519540%2Fba59d8b5-efff-406d-b4e8-6358b0527562.png</url>
      <title>DEV Community: sai anirud varma uppalapati</title>
      <link>https://dev.to/sai_anirudvarmauppalapa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sai_anirudvarmauppalapa"/>
    <language>en</language>
    <item>
      <title>Optimizing PostgreSQL Queries: From 820 ms to 120 ms with Indexing</title>
      <dc:creator>sai anirud varma uppalapati</dc:creator>
      <pubDate>Sun, 21 Sep 2025 17:42:01 +0000</pubDate>
      <link>https://dev.to/sai_anirudvarmauppalapa/optimizing-postgresql-queries-from-820-ms-to-120-ms-with-indexing-6ic</link>
      <guid>https://dev.to/sai_anirudvarmauppalapa/optimizing-postgresql-queries-from-820-ms-to-120-ms-with-indexing-6ic</guid>
      <description>&lt;p&gt;&lt;u&gt;Introduction:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;When I first built HabitHero, my gamified habit-tracking app, I ran into an unexpected bottleneck. slow queries. As more test users added tasks and streak data, my dashboards and API endpoints started lagging.&lt;/p&gt;

&lt;p&gt;At first, I thought the problem was my code. But when I dug deeper, I realized the real culprit was the database. PostgreSQL was scanning far more rows than necessary.&lt;/p&gt;

&lt;p&gt;By carefully applying indexes, I cut query times by nearly 85% — a change that instantly made the app feel smoother. In this post, I’ll walk through exactly how indexing turned HabitHero’s queries from sluggish to snappy.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The Dataset (Simplified Example)&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;HabitHero tracks tasks, streaks, and categories, but to make this reproducible, I’ll use a simple orders dataset with 1 million rows. The idea is the same: filter queries on frequently accessed fields (like customer_id in this example, or user_id in HabitHero).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer_id INT NOT NULL,
    order_date TIMESTAMP NOT NULL,
    total_amount NUMERIC(10,2) NOT NULL
);

-- Insert 1M rows for simulation
INSERT INTO orders (customer_id, order_date, total_amount)
SELECT (random()*10000)::INT,
       NOW() - (random() * interval '365 days'),
       (random()*500)::NUMERIC(10,2)
FROM generate_series(1, 1000000);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;The Query (Baseline)&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Let’s say we need all orders for a given customer in the last year (similar to “get all tasks completed by a user in the last 30 days” in HabitHero):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM orders
WHERE customer_id = 1234
  AND order_date &amp;gt;= NOW() - INTERVAL '1 year';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Baseline performance (no indexes)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE customer_id = 1234
  AND order_date &amp;gt;= NOW() - INTERVAL '1 year';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Result was:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on orders  (cost=0.00..19345.00 rows=5000 width=48)
   Filter: ((customer_id = 1234) AND (order_date &amp;gt;= (now() - '1 year'::interval)))
Execution Time: 820 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Now lets Add Indexes:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Index on customer_id&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE INDEX idx_customer_id ON orders(customer_id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and re-run query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using idx_customer_id on orders
Execution Time: 220 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Composite Index (customer_id, order_date)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE INDEX idx_customer_date ON orders(customer_id, order_date);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using idx_customer_date on orders
Execution Time: 120 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing More Queries&lt;/p&gt;

&lt;p&gt;Query 1: Range filter (amount &amp;gt; 400)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM orders
WHERE total_amount &amp;gt; 400;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Without index: 620 ms&lt;/li&gt;
&lt;li&gt;With index on total_amount: 130 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In HabitHero, I noticed the same effect when filtering tasks by XP points earned.&lt;/p&gt;

&lt;p&gt;Query 2: Sorting by date&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT *
FROM orders
WHERE customer_id = 1234
ORDER BY order_date DESC
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Without index: 710 ms&lt;/li&gt;
&lt;li&gt;With composite index: 140 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For HabitHero, this helped in showing “last 10 completed tasks” instantly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;No Index&lt;/th&gt;
&lt;th&gt;Single Index&lt;/th&gt;
&lt;th&gt;Composite Index&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;customer + date Filter&lt;/td&gt;
&lt;td&gt;820ms&lt;/td&gt;
&lt;td&gt;220ms&lt;/td&gt;
&lt;td&gt;120ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amount &amp;gt; 400&lt;/td&gt;
&lt;td&gt;620ms&lt;/td&gt;
&lt;td&gt;130ms&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sort by Date&lt;/td&gt;
&lt;td&gt;710ms&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;140ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;u&gt;Lessons Learned&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Index what you query often, user_id and created_at solved 80% of my slow queries in HabitHero.&lt;/li&gt;
&lt;li&gt;Don’t over index, I tried indexing everything once and inserts slowed badly.&lt;/li&gt;
&lt;li&gt;Measure before &amp;amp; after, Always use EXPLAIN ANALYZE.&lt;/li&gt;
&lt;li&gt;Think ahead, Design indexes for future dashboard/reporting needs too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;Conclusion&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Indexes turned HabitHero’s slow queries into snappy responses — the same way they transformed this simulated dataset.&lt;/p&gt;

&lt;p&gt;On paper, the difference between 820 ms and 120 ms seems small. In practice, those milliseconds compound into massive time and cost savings at scale.&lt;/p&gt;

&lt;p&gt;If you’re building an app like HabitHero (or any system with user-driven queries), proper indexing is one of the highest ROI optimizations you can make.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
