ARCHITECTURE IN MOTION / EPISODE 009

Your Order History Is Slow.
The Index Is Not the Query.

Explore why a SQL query that returns just 20 orders can still examine thousands of rows. Move the controls to compare access patterns—not invented benchmark timings.

01 · Follow the request

🛒 Shopper→Spring Boot API→HikariCP→PostgreSQL Planner→B-tree / Heap

Indexes are chosen by a cost-based optimizer. An index that exists is not necessarily an index the planner will use.

02 · Reproduce a duplicated order

A shopper loads two orders, then another order arrives before they click Next. Compare the next page returned by OFFSET and a saved cursor.

03 · Interactive query-work simulator

Conceptual simulation only: this is a deterministic teaching model, not a real database, measured execution plan, or timing benchmark. It models skipped rows and a simplified access-path compatibility score.

Rows skipped before page
1,980
Illustrative candidate rows examined
2,000
Query shape
Range + OFFSET
Candidate work compared with customer order count2%

For simplicity, the simulator assumes a full candidate set and 20-row pages; real plans depend on selectivity, statistics, data layout, caches, visibility and optimizer choices. Keyset still incurs work finding the cursor position, and a function expression may be supported by an expression index.

04 · The actual SQL fix

Try the query on PostgreSQL 15+ with the sample schema from the full blog. Notice the tie-breaker and exclusive cursor:

CREATE INDEX idx_orders_customer_created_id
ON orders (customer_id, created_at DESC, order_id DESC)
INCLUDE (status, total_amount);

SELECT order_id, created_at, total_amount
FROM orders
WHERE customer_id = 42
  AND created_at >= TIMESTAMPTZ '2026-10-30 00:00+00'
  AND created_at < TIMESTAMPTZ '2026-11-02 00:00+00'
  AND (created_at, order_id) <
      (TIMESTAMPTZ '2026-10-31 12:00+00', 1005)
ORDER BY created_at DESC, order_id DESC
LIMIT 3;
OrderCreated UTCTotal
1004Oct 31 08:30$240.00
1003Oct 30 10:00$19.50
1002Oct 30 10:00$129.00

05 · Practice challenges

Beginner — Return Oct 31 orders without DATE(created_at)
SELECT order_id FROM orders WHERE customer_id = 42
AND created_at >= TIMESTAMPTZ '2026-10-31 00:00+00'
AND created_at < TIMESTAMPTZ '2026-11-01 00:00+00'
ORDER BY created_at DESC, order_id DESC;
-- Expected: 1005, 1004
Intermediate — Cursor after order 1006
SELECT order_id FROM orders WHERE customer_id = 42
AND (created_at, order_id) <
(TIMESTAMPTZ '2026-11-01 06:00+00', 1006)
ORDER BY created_at DESC, order_id DESC LIMIT 2;
-- Expected: 1005, 1004
Architect — Why can an INCLUDE index still read heap pages?

MVCC visibility can require heap access. Validate `Heap Fetches`, buffer reads and actual plans. For a large production rollout, assess disk/WAL and use `CREATE INDEX CONCURRENTLY` when appropriate.

06 · Senior engineer quiz

Three original interview-style questions. Submit to see explanations for every answer.