Back to Blog
Databases Performance Optimization Cloud Costs SQL

SQL Indexing: Fast Queries and Lower Cloud Bills

• 11 min read • Written by Marlon

TL;DR:

  • The Trap: Scaling an AWS RDS or Google Cloud SQL instance to an $800/month monster to mask slow queries simply burns corporate capital; Full Table Scans continue monopolizing disk I/O and locking connection pools under traffic spikes.
  • The Architecture: Forensic SQL optimization using EXPLAIN (ANALYZE, BUFFERS), composite B-Tree indexes matching filter cardinality, covering indexes (INCLUDE) for zero-heap Index-Only Scans, and partial indexes for active workloads.
  • The Business Impact: Transforms 8-second sequential disk scans into sub-2ms in-memory index lookups, cuts database CPU utilization by 70%, and enables safe infrastructure downgrades that slash cloud bills by over half.

The Hardware Illusion: Why Throwing CPU at Slow Databases Fails

A sequential Full Table Scan across five million records is a continuous, silent leak draining your company’s AWS budget.

When an application grinds to a halt during peak customer hours, the immediate reflex for many engineering teams is scaling up the database instance.

With a few clicks in the cloud console, they upgrade from an db.t4g.medium to an enterprise db.r6g.2xlarge, doubling CPU cores and memory allocation while spiking monthly hosting costs from $60 to over $750.

It is an expensive illusion that lasts perhaps three weeks.

As incoming transaction volume continues to grow, CPU utilization climbs right back to 95%, connection pool queues freeze, and database read latency deteriorates again.

The underlying issue was never a lack of physical silicon.

You are simply subsidizing unindexed, poorly architected SQL queries with venture capital.

By applying surgical database indexing and understanding PostgreSQL query execution plans, staff software architects routinely deliver what CFOs love: a safe infrastructure downgrade that serves 10x more traffic on half the hardware.

  1. STAGE 01 Telemetry

    Detection via pg_stat

    Query pg_stat_statements to rank queries by mean execution time, total calls, and cumulative disk read buffers.

  2. STAGE 02 Inspection

    EXPLAIN Plan Deep-Dive

    Execute EXPLAIN (ANALYZE, BUFFERS) to isolate Seq Scans, nested loops, and disk block cache misses.

  3. STAGE 03 Index Design

    Surgical Index Engineering

    Deploy composite indexes, INCLUDE covering clauses, and partial WHERE filters matching runtime predicates.

  4. STAGE 04 Cost Savings

    Verification & Downgrade

    Verify sub-2ms latency under load, confirm zero disk buffer reads, and right-size compute instances to cut bills.


Anatomy of the Execution Plan: How to Read EXPLAIN (ANALYZE, BUFFERS)

Most developers run a plain EXPLAIN and look only at the theoretical cost estimate generated by the planner.

In production debugging, theoretical costs are misleading. You must inspect actual runtime metrics and buffer utilization:

EXPLAIN (ANALYZE, BUFFERS, VERBOSE, COSTS)
SELECT id, total_amount, created_at 
FROM orders 
WHERE tenant_id = 'c4b1a2d3-0000-0000-0000-000000000001'
  AND status = 'completed'
ORDER BY created_at DESC 
LIMIT 25;

Key Metrics to Diagnose in the Plan Output

  1. Seq Scan vs Index Scan vs Index Only Scan:

    • Seq Scan: The engine reads every disk page in the relation. Catastrophic on large tables.
    • Index Scan: The engine traverses a B-Tree index to locate row pointers (TIDs), then accesses the table heap on disk to retrieve columns.
    • Index Only Scan: The holy grail. All queried columns exist directly inside the index tree. The engine never touches the table heap on disk, eliminating heap I/O entirely.
  2. Buffers: shared hit vs shared read:

    • shared hit: The required data page was already present in RAM (PostgreSQL shared_buffers). Cost: sub-microsecond.
    • shared read: The engine had to perform a synchronous physical read from the SSD or cloud storage volume. Cost: 5 to 50 milliseconds per block.

If your query plan shows Buffers: shared read=15420, the database is dragging 120 megabytes of raw data off the disk for a single HTTP request!


The Four Indexing Strategies Every Senior Engineer Must Master

1. Composite B-Tree Indexes & the Equality-Range Rule

When filtering by multiple columns (WHERE tenant_id = :id AND created_at >= :date), single-column indexes force PostgreSQL to perform bitmap index scans and heap merges.

You must build Composite Indexes, following the Equality First, Range Second principle:

  • Rule: Place columns filtered by exact equality (=) at the leading edge of the index. Place columns filtered by ranges (>, <, BETWEEN) or sort orders (ORDER BY) at the trailing edge.
-- Optimal composite index: tenant_id (equality) followed by created_at (range/sort)
CREATE INDEX idx_orders_tenant_created 
ON orders (tenant_id, created_at DESC);

2. Covering Indexes with the INCLUDE Clause

In older PostgreSQL versions, including columns solely to avoid heap lookups bloated the index B-Tree, reducing branching efficiency.

PostgreSQL 11+ introduced the INCLUDE clause:

CREATE INDEX idx_orders_covering_search
ON orders (tenant_id, status)
INCLUDE (total_amount, currency, customer_id);
  • The leading columns (tenant_id, status) form the search key in the B-Tree index.
  • The INCLUDE columns are stored only in the leaf pages as payload data.
  • This produces an Index Only Scan without swelling the non-leaf B-Tree navigation nodes.

3. Partial Indexes: 90% Smaller RAM Footprint

In transactional applications, data distribution is rarely uniform. For example, 98% of orders are completed or archived, while only 2% are in pending_payment or processing.

Indexing the entire table wastes hundreds of megabytes of precious RAM.

-- Partial index: only indexes the 2% of rows requiring fast operational lookups
CREATE INDEX idx_orders_active_pipeline 
ON orders (tenant_id, created_at DESC) 
WHERE status IN ('pending_payment', 'processing');

The resulting index is 98% smaller in disk size, easily fits entirely within RAM cache, and causes zero write overhead when older orders are archived.

4. BRIN (Block Range Index) for Massive Append-Only Timeseries

For tables storing hundreds of millions of append-only logs, telemetry events, or sensor metrics ordered naturally by created_at, a standard B-Tree index can consume 40 gigabytes of disk.

A BRIN (Block Range Index) stores only the minimum and maximum values for physical block ranges (e.g., every 128 pages):

CREATE INDEX idx_telemetry_brin_created 
ON system_telemetry USING BRIN (created_at);
  • A BRIN index is thousands of times smaller than a B-Tree (megabytes instead of gigabytes).
  • It provides fast range scans while preserving disk and RAM cache for critical relations.

Code Comparison: Unindexed Catastrophe vs Surgical Indexing Engine

Naive: Unindexed Query Suffocating PostgreSQL Disk Buffers

-- Naive State: No composite index, missing covering attributes, forcing full Seq Scan
SELECT 
    id, 
    total_amount, 
    customer_id, 
    created_at 
FROM orders 
WHERE tenant_id = 'e7b1a2d3-1111-2222-3333-444455556666'
  AND status = 'pending_fulfillment'
ORDER BY created_at DESC 
LIMIT 20;

-- Execution Plan: 5.4 seconds, 100% CPU lock, reading 82,000 blocks from disk
-- EXPLAIN (ANALYZE, BUFFERS):
--   Limit  (cost=184320.10..184320.15 rows=20 width=48) (actual time=5412.10..5412.12 rows=20 loops=1)
--     ->  Sort  (cost=184320.10..184325.00 rows=1960 width=48)
--           Sort Key: created_at DESC
--           ->  Seq Scan on orders  (cost=0.00..184200.00 rows=1960 width=48) (actual time=18.40..5395.20)
--                 Filter: ((tenant_id = 'e7b1a2d3-...'::uuid) AND (status = 'pending_fulfillment'::text))
--                 Rows Removed by Filter: 4,892,100
--                 Buffers: shared hit=1200 read=82150 (HEAVY DISK I/O BOTTLENECK)
-- Planning Time: 0.150 ms
-- Execution Time: 5412.250 ms

Production: Surgical Partial Covering Index with Index-Only Scan

-- Production Solution: Partial Covering Index matching exact query predicate and ordering
CREATE INDEX CONCURRENTLY idx_orders_tenant_pending_covering 
ON orders (tenant_id, created_at DESC) 
INCLUDE (total_amount, customer_id, id)
WHERE status = 'pending_fulfillment';

-- Optimized Query Execution: Sub-2ms, 0 disk reads, pure in-memory Index Only Scan
EXPLAIN (ANALYZE, BUFFERS)
SELECT 
    id, 
    total_amount, 
    customer_id, 
    created_at 
FROM orders 
WHERE tenant_id = 'e7b1a2d3-1111-2222-3333-444455556666'
  AND status = 'pending_fulfillment'
ORDER BY created_at DESC 
LIMIT 20;

-- EXPLAIN (ANALYZE, BUFFERS) Result:
-- Limit  (cost=0.42..8.85 rows=20 width=48) (actual time=0.045..0.082 rows=20 loops=1)
--   ->  Index Only Scan using idx_orders_tenant_pending_covering on orders  
--         Index Cond: (tenant_id = 'e7b1a2d3-1111-2222-3333-444455556666'::uuid)
--         Heap Fetches: 0 (ZERO DISK HEAP READS)
--         Buffers: shared hit=4 read=0 (100% IN-MEMORY HIT)
-- Planning Time: 0.112 ms
-- Execution Time: 1.105 ms (OVER 4,900x SPEEDUP!)

Automated Migration Guard in TypeScript

// Migration Guard: Validates that queries use Index Only Scans during CI integration testing
import { Client } from "pg";
import { describe, it, expect } from "vitest";

describe("Database Query Performance Gate", () => {
  it("executes orders pipeline query in sub-5ms with zero disk reads", async () => {
    const client = new Client({ connectionString: process.env.TEST_DATABASE_URL });
    await client.connect();

    try {
      const explainQuery = `
        EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
        SELECT id, total_amount, customer_id, created_at 
        FROM orders 
        WHERE tenant_id = 'e7b1a2d3-1111-2222-3333-444455556666'
          AND status = 'pending_fulfillment'
        ORDER BY created_at DESC 
        LIMIT 20;
      `;

      const result = await client.query(explainQuery);
      const plan = result.rows[0]["QUERY PLAN"][0];
      const executionTime = plan["Execution Time"]; // in milliseconds
      const nodeType = plan["Plan"]["Node Type"];
      const sharedReadBuffers = plan["Plan"]["Shared Read Blocks"] || 0;

      // Assertions: Hard performance thresholds
      expect(executionTime).toBeLessThan(5.0); // Under 5ms
      expect(nodeType).toBe("Limit");
      expect(plan["Plan"]["Plans"][0]["Node Type"]).toBe("Index Only Scan");
      expect(sharedReadBuffers).toBe(0); // Pure memory cache hit
    } finally {
      await client.end();
    }
  });
});

Real-World Case Studies: Relational Scale on Lean Budgets

Strategio (Startup Perú 2G Winner): Multi-Tenant BI on Sub-$50/Month Cloud

Strategio computes business intelligence analytics across hundreds of thousands of retail transactions.

  • The Problem: Aggregation queries computing gross margins across multi-tenant product lines were taking over 4,500ms, pushing database CPU to 90% and forcing consideration of an expensive managed analytics cluster costing $400/month.
  • The Solution: We profiled query patterns with pg_stat_statements, eliminated three redundant single-column indexes, and deployed composite covering indexes with INCLUDE clauses for metric calculations.
  • The Result: Query execution times dropped from 4,500ms to 4.2ms, and the entire platform ran on a single shared PostgreSQL instance with cloud costs remaining under $50 USD/month.

PróximaBolilla (proximabolilla.com): Millions of Simulation Draws in Under 3ms

PróximaBolilla performs real-time lottery combinatorics and statistical modeling across massive historical draws.

  • The Problem: During live draw simulations, queries filtering draws by date range and lottery game were causing massive disk I/O, slowing websocket broadcast loops to over 2.4 seconds.
  • The Solution: We implemented partial B-Tree indexes on active game states and composite BRIN indexes on historical draw timestamps.
  • The Result: Latency dropped from 2,400ms to 1.8ms, allowing the engine to evaluate over 1,000,000 combinations in real time without increasing cloud instance tiers.

Pre-Flight Checklist for PostgreSQL Index Optimization

Before deploying new indexes or scaling database hardware, verify these five architectural gates:

  • Verify pg_stat_statements Telemetry: Have you identified the top 5 queries by cumulative disk read time (shared_blks_read) rather than optimizing arbitrary queries?
  • Inspect EXPLAIN (ANALYZE, BUFFERS): Does your query plan show Index Only Scan with Heap Fetches: 0 for your primary high-volume read endpoints?
  • Respect the Equality-Range Rule: Are composite index columns ordered with exact equality matches (=) first and range/sort columns (<, >, ORDER BY) last?
  • Leverage Partial Indexes for Skewed Data: If filtering by a status that represents less than 10% of total rows, have you added a WHERE predicate to the index?
  • Deploy with CREATE INDEX CONCURRENTLY: In production environments, are all indexes created concurrently to prevent write table locks during build time?

Architectural Advisory

High-performance database engineering is not about purchasing larger cloud servers; it is about respecting how relational storage engines access disk blocks and cache memory.

At Coins5, Marlon helps fast-growing startups and enterprises audit slow database queries, engineer surgical index architectures, and permanently reduce cloud hosting bills.

If your database CPU is spiking or your AWS RDS invoice is escalating every month, schedule a 15-minute performance audit call or reach out directly on WhatsApp.

Tired of High AWS RDS or Cloud SQL Invoices?

Let's diagnose your slow query logs and optimize your PostgreSQL indexing strategy in a 15-minute performance audit call.

Marlon - Senior Architect
Author & Systems Architect

Marlon / Coins5

Senior Full Stack Developer & Cloud Architect. Building custom software and production MVPs with Clean Architecture, high-concurrency cloud design, and AI velocity.

Book 15m Call