<?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: John Kiprito</title>
    <description>The latest articles on DEV Community by John Kiprito (@johnkipruto).</description>
    <link>https://dev.to/johnkipruto</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4031002%2Fa2cb11b1-fbcf-4b2e-ab58-aa3c70c3fa21.jpg</url>
      <title>DEV Community: John Kiprito</title>
      <link>https://dev.to/johnkipruto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnkipruto"/>
    <language>en</language>
    <item>
      <title>PostgreSQL Query Optimization: Reducing API Response Time from 2.8 Seconds to 74 Milliseconds</title>
      <dc:creator>John Kiprito</dc:creator>
      <pubDate>Wed, 15 Jul 2026 21:41:16 +0000</pubDate>
      <link>https://dev.to/johnkipruto/postgresql-query-optimization-reducing-api-response-time-from-28-seconds-to-74-milliseconds-5a97</link>
      <guid>https://dev.to/johnkipruto/postgresql-query-optimization-reducing-api-response-time-from-28-seconds-to-74-milliseconds-5a97</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Performance issues rarely announce themselves with obvious errors. More often, they appear as subtle increases in latency that gradually worsen as data volume grows.&lt;/p&gt;

&lt;p&gt;While working on a Node.js and PostgreSQL application, I noticed a user-facing API endpoint consistently taking approximately 2.8 seconds to respond. The endpoint was functionally correct, but its performance was unacceptable for production use.&lt;/p&gt;

&lt;p&gt;This article walks through the investigation process, root cause analysis, optimization strategy, and the changes that reduced response time to 74 milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The API endpoint was responsible for retrieving customer orders.&lt;/p&gt;

&lt;p&gt;GET /api/orders?customerId=100&lt;/p&gt;

&lt;p&gt;At first glance, nothing seemed unusual.&lt;/p&gt;

&lt;p&gt;However, monitoring revealed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metric    Before&lt;/li&gt;
&lt;li&gt;Average Response Time 2.8s&lt;/li&gt;
&lt;li&gt;Database Execution Time   2.5s&lt;/li&gt;
&lt;li&gt;Records in Orders Table   1.2M+&lt;/li&gt;
&lt;li&gt;User Experience   Poor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As traffic increased, response times continued to grow.&lt;/p&gt;

&lt;p&gt;The objective was clear:&lt;/p&gt;

&lt;p&gt;Identify the bottleneck and restore acceptable API performance.&lt;/p&gt;

&lt;p&gt;Application Stack&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Express.js&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Database size:&lt;/p&gt;

&lt;p&gt;Orders Table: 1.2+ million rows&lt;br&gt;
Customers Table: 350,000+ rows&lt;br&gt;
Initial Investigation&lt;/p&gt;

&lt;p&gt;The first assumption was that application logic might be responsible.&lt;/p&gt;

&lt;p&gt;I reviewed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Express middleware&lt;/li&gt;
&lt;li&gt;API controller logic&lt;/li&gt;
&lt;li&gt;Network latency&lt;/li&gt;
&lt;li&gt;PostgreSQL logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No significant issues appeared in the application layer.&lt;/p&gt;

&lt;p&gt;The next step was analyzing the database query itself.&lt;/p&gt;

&lt;p&gt;The Query&lt;/p&gt;

&lt;p&gt;The endpoint executed the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="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;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&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;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query looked straightforward.&lt;/p&gt;

&lt;p&gt;However, execution time suggested otherwise.&lt;/p&gt;

&lt;p&gt;Query Analysis Using EXPLAIN ANALYZE&lt;/p&gt;

&lt;p&gt;To understand PostgreSQL's execution strategy, I used:&lt;/p&gt;

&lt;p&gt;EXPLAIN ANALYZE&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;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&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;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output revealed the real problem.&lt;/p&gt;

&lt;p&gt;Seq Scan on orders&lt;/p&gt;

&lt;p&gt;PostgreSQL was performing a sequential scan across the entire table.&lt;/p&gt;

&lt;p&gt;Instead of locating matching records efficiently, the database was examining over one million rows.&lt;/p&gt;

&lt;p&gt;Root Cause&lt;/p&gt;

&lt;p&gt;The filtering column lacked an index.&lt;/p&gt;

&lt;p&gt;Because customer_id was not indexed, PostgreSQL had no efficient path to locate matching records.&lt;/p&gt;

&lt;p&gt;Every request required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reading large portions of the table&lt;/li&gt;
&lt;li&gt;Filtering matching rows&lt;/li&gt;
&lt;li&gt;Sorting results afterward&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As table size increased, query performance degraded significantly.&lt;/p&gt;

&lt;p&gt;The Optimization Strategy&lt;br&gt;
Step 1: Create an Index&lt;/p&gt;

&lt;p&gt;The most obvious improvement was indexing the search column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_customer_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;customer_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performance improved immediately.&lt;/p&gt;

&lt;p&gt;However, there was still room for optimization.&lt;/p&gt;

&lt;p&gt;Step 2: Optimize Sorting&lt;/p&gt;

&lt;p&gt;The query also sorted results by creation date.&lt;/p&gt;

&lt;p&gt;A composite index was created:&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;idx_orders_customer_created&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allowed PostgreSQL to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter records efficiently&lt;/li&gt;
&lt;li&gt;Return results in sorted order&lt;/li&gt;
&lt;li&gt;Avoid additional sorting operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After applying the changes, I reran:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution plan changed from:&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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL was now using the new index correctly.&lt;/p&gt;

&lt;p&gt;Results&lt;/p&gt;




&lt;p&gt;Before Optimization&lt;/p&gt;

&lt;p&gt;Metric  Value&lt;br&gt;
API Response Time   2.8s&lt;br&gt;
Query Execution Sequential Scan&lt;br&gt;
CPU Usage   High&lt;br&gt;
User Experience Poor&lt;/p&gt;

&lt;p&gt;After Optimization&lt;/p&gt;

&lt;p&gt;Metric  Value&lt;br&gt;
API Response Time   74ms&lt;br&gt;
Query Execution Index Scan&lt;br&gt;
CPU Usage   Reduced&lt;br&gt;
User Experience Excellent&lt;br&gt;
Improvement&lt;br&gt;
2.8 seconds → 74 milliseconds&lt;/p&gt;

&lt;p&gt;Performance Improvement: ~97.4%&lt;/p&gt;

&lt;p&gt;The endpoint became approximately 38 times faster.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. Database Performance Is Application Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even perfectly written backend code cannot compensate for inefficient database queries.&lt;/p&gt;

&lt;p&gt;Understanding query execution plans is one of the most valuable skills for backend developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Measure Before Optimizing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Assumptions often lead developers in the wrong direction.&lt;/p&gt;

&lt;p&gt;Tools such as:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;provide objective evidence of where bottlenecks exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Indexes Are Powerful but Strategic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Indexes dramatically improve read performance.&lt;/p&gt;

&lt;p&gt;However, they should be created thoughtfully because they introduce additional storage and write overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Small Changes Can Deliver Massive Impact&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final code change consisted of only a few lines of SQL.&lt;/p&gt;

&lt;p&gt;The majority of the effort was understanding the problem correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This optimization reinforced an important engineering principle:&lt;/p&gt;

&lt;p&gt;Performance improvements are often achieved through better understanding, not more code.&lt;/p&gt;

&lt;p&gt;By analyzing query execution plans, identifying a missing index, and implementing a targeted optimization strategy, API response time dropped from 2.8 seconds to 74 milliseconds.&lt;/p&gt;

&lt;p&gt;For backend engineers working with PostgreSQL, regularly reviewing query plans and indexing strategies can prevent scalability bottlenecks long before they impact users.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>backend</category>
      <category>postgres</category>
    </item>
    <item>
      <title>PostgreSQL Best Practices for Backend Developers</title>
      <dc:creator>John Kiprito</dc:creator>
      <pubDate>Wed, 15 Jul 2026 20:25:24 +0000</pubDate>
      <link>https://dev.to/johnkipruto/postgresql-best-practices-for-backend-developers-10he</link>
      <guid>https://dev.to/johnkipruto/postgresql-best-practices-for-backend-developers-10he</guid>
      <description>&lt;ol&gt;
&lt;li&gt;Design Your Database Schema Carefully&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A well-designed schema is the foundation of a successful application. Instead of storing everything in a single table, normalize your data into related tables.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;Reduces duplicate data&lt;br&gt;
Improves consistency&lt;br&gt;
Makes maintenance easier&lt;/p&gt;

&lt;p&gt;A clean database structure will save countless hours as your application grows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Indexes Wisely&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Indexes can dramatically improve query performance, especially when searching large datasets.&lt;/p&gt;

&lt;p&gt;For example, frequently searched columns such as email addresses, usernames, and foreign keys often benefit from indexing.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;Faster query execution&lt;br&gt;
Better application responsiveness&lt;br&gt;
Improved scalability&lt;/p&gt;

&lt;p&gt;However, avoid creating unnecessary indexes because they consume storage and can slow down insert and update operations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always Use Parameterized Queries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Security should always be a priority. Parameterized queries help prevent SQL injection attacks and improve database security.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;Protects against malicious input&lt;br&gt;
Improves application security&lt;br&gt;
Better query optimization&lt;/p&gt;

&lt;p&gt;Never build SQL queries by directly concatenating user input.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Proper Data Types&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Choosing the correct data type for each column improves performance and data integrity.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;INTEGER for numeric values&lt;br&gt;
BOOLEAN for true/false values&lt;br&gt;
TIMESTAMP for dates and times&lt;br&gt;
UUID for unique identifiers&lt;br&gt;
JSONB for semi-structured data&lt;/p&gt;

&lt;p&gt;Using appropriate data types reduces storage requirements and improves query performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take Advantage of JSONB&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One of PostgreSQL's most powerful features is JSONB support. It allows developers to store and query structured JSON data efficiently while maintaining the advantages of a relational database.&lt;/p&gt;

&lt;p&gt;Use cases:&lt;/p&gt;

&lt;p&gt;Product metadata&lt;br&gt;
User preferences&lt;br&gt;
Dynamic application settings&lt;br&gt;
Event tracking&lt;/p&gt;

&lt;p&gt;JSONB provides flexibility without sacrificing performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optimize Queries Regularly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As applications grow, database performance becomes increasingly important.&lt;/p&gt;

&lt;p&gt;Regularly review:&lt;/p&gt;

&lt;p&gt;Slow queries&lt;br&gt;
Complex joins&lt;br&gt;
Large table scans&lt;br&gt;
Missing indexes&lt;/p&gt;

&lt;p&gt;Tools like EXPLAIN ANALYZE can help identify bottlenecks and opportunities for optimization.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Transactions for Critical Operations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Transactions ensure data consistency and reliability.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;p&gt;Financial transfers&lt;br&gt;
Order processing&lt;br&gt;
Inventory updates&lt;br&gt;
Multi-step business operations&lt;/p&gt;

&lt;p&gt;Transactions guarantee that either all operations succeed or none are applied, preventing inconsistent data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implement Database Backups&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every production system should have a backup strategy.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;p&gt;Protection against accidental deletion&lt;br&gt;
Disaster recovery&lt;br&gt;
Business continuity&lt;/p&gt;

&lt;p&gt;Regular backups should be tested to ensure they can be restored successfully when needed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Monitor Database Performance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Monitoring helps identify problems before users notice them.&lt;/p&gt;

&lt;p&gt;Key metrics include:&lt;/p&gt;

&lt;p&gt;Query execution times&lt;br&gt;
Connection counts&lt;br&gt;
CPU usage&lt;br&gt;
Memory consumption&lt;br&gt;
Database growth&lt;/p&gt;

&lt;p&gt;Monitoring allows teams to proactively maintain system performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Connection Pooling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creating a new database connection for every request can become expensive.&lt;/p&gt;

&lt;p&gt;Connection pooling helps:&lt;/p&gt;

&lt;p&gt;Reduce database overhead&lt;br&gt;
Improve performance&lt;br&gt;
Support higher traffic loads&lt;br&gt;
Increase application scalability&lt;/p&gt;

&lt;p&gt;Most modern backend frameworks and libraries provide built-in support for connection pooling.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;/p&gt;

&lt;p&gt;PostgreSQL is much more than a traditional relational database. By following best practices such as proper schema design, indexing, query optimization, transactions, connection pooling, and performance monitoring, developers can build secure, scalable, and efficient applications.&lt;/p&gt;

&lt;p&gt;Mastering these techniques early will help you create production-ready systems that remain reliable as your applications and user base continue to grow.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>backend</category>
      <category>database</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
