Nextgen Blog
HomeLearnAIQuantumRoboticsSecuritySpaceAbout
Nextgen Blog

By Anubhav — full stack engineer, IIT Mandi CSE. Deep dives into AI, software engineering, and the technologies shaping the next generation of tech.

Navigate

  • Home
  • Learn
  • Blog
  • Archive
  • About

Topics

  • Artificial Intelligence
  • Quantum Computing
  • Robotics
  • Cybersecurity
  • Space Tech

Connect

Built with ❤️ & By - Anubhav

© 2026 Nextgen Blog by Anubhav. All rights reserved.

Privacy PolicyTerms of Service
Phase 03 · Partitioning, Replication & CachingChapter 14 of 33
13.
Phase 03Read time ~32 minDifficulty advanced

Database Indexing

In this chapter
  • 01What You Will Learn
  • 02Why Do We Need Indexes?
  • 03Think of an Index Like a Book Index
  • 04Indexes Are Separate Data Structures
  • 05Basic Index Example
  • 06Important: An Index Does Not Guarantee Faster Queries
  • 07Sequential Scan
  • 08Index Scan
  • 09Why the Optimizer May Prefer a Sequential Scan
  • 10B-Tree Indexes
  • 11Why B-Trees Are Useful
  • 12B-Tree Range Queries
  • 13B-Tree Complexity Intuition
  • 14Why Database B-Trees Have Many Children
  • 15Index Pages
  • 16Buffer Cache
  • 17Hash Indexes
  • 18B-Tree vs Hash
  • 19Composite Indexes
  • 20Why Composite Indexes Matter
  • 21Column Order Matters
  • 22Leftmost Prefix Intuition
  • 23Composite Index Example
  • 24Equality Before Range — Useful Heuristic
  • 25Selectivity
  • 26Cardinality
  • 27Selectivity vs Cardinality
  • 28Cardinality
  • 29Selectivity
  • 30Indexing Boolean Columns
  • 31Partial Indexes
  • 32Expression Indexes
  • 33Function Calls Can Affect Index Usage
  • 34Covering Indexes
  • 35Included Columns
  • 36Covering Index Trade-Off
  • 37Clustered Index
  • 38Non-Clustered Index
  • 39PostgreSQL Note
  • 40Primary Keys and Indexes
  • 41Unique Indexes
  • 42Indexes and Sorting
  • 43Indexes and LIMIT
  • 44Indexes and JOINs
  • 45Foreign Keys Do Not Automatically Mean "Create an Index Everywhere"
  • 46Indexes and Pagination
  • 47Composite Cursor Pagination
  • 48Indexing Time-Series Data
  • 49Indexing and Large Tables
  • 50PostgreSQL Concurrent Index Creation
  • 51Index Build Is Not Free
  • 52Write Amplification
  • 53Read-Write Trade-Off
  • 54Over-Indexing
  • 55Under-Indexing
  • 56Duplicate Indexes
  • 57Index Prefix Redundancy
  • 58Query Shape Matters
  • 59Indexing OR Conditions
  • 60Functions and Indexes
  • 61Leading Wildcards
  • 62Full-Text Search
  • 63Index vs Search Engine
  • 64Statistics
  • 65Cardinality Estimation
  • 66EXPLAIN
  • 67EXPLAIN ANALYZE
  • 68Example Query Plan Reasoning
  • 69Index-Only Scan
  • 70Index and Memory
  • 71Indexing Strategy for a Large Table
  • 72Index Design Workflow
  • 73Indexing Checklist for Every Query
  • 74Filtering
  • 75Joining
  • 76Sorting
  • 77Range
  • 78Pagination
  • 79Selectivity
  • 80Result size
  • 81Frequency
  • 82Example: Notification System
  • 83Example: URL Shortener
  • 84Example: Chat Messages
  • 85Indexing vs Caching
  • 86Indexing vs Denormalization
  • 87Strategy A
  • 88Strategy B
  • 89Indexing vs Sharding
  • 90Indexing and Distributed Databases
  • 91Common Index Anti-Patterns
  • 92Anti-pattern 1
  • 93Anti-pattern 2
  • 94Anti-pattern 3
  • 95Anti-pattern 4
  • 96Anti-pattern 5
  • 97Anti-pattern 6
  • 98Anti-pattern 7
  • 99Anti-pattern 8
  • 100Anti-pattern 9
  • 101Anti-pattern 10
  • 102Production Monitoring
  • 103Index Lifecycle
  • 104Interview Question — Why Not Index Everything?
  • 105Interview Question — What Is a Composite Index?
  • 106Interview Question — What Is Selectivity?
  • 107Interview Question — Why Is a Sequential Scan Sometimes Better?
  • 108Interview Question — How Do You Optimize a Slow Query?
  • 109Interview Question — What Makes a Good Composite Index?
  • 110Practical Exercise 1 — User Search
  • 111Practical Exercise 2 — Orders
  • 112Practical Exercise 3 — Query Plan Investigation
  • 113Mini Project — Database Index Optimization
  • 114Step 1
  • 115Step 2
  • 116Step 3
  • 117Step 4
  • 118Step 5
  • 119Step 6
  • 120Production Scenario
  • 121When Indexes Are Not Enough
  • 122Master Index Mental Model
  • 123The Most Important Indexing Formula
  • 124Chapter Summary
  • 125Master Checklist
  • 126What Comes Next?
  • 127Chapter 13 — Database Replication
In this chapter (127 sections)
01What You Will Learn02Why Do We Need Indexes?03Think of an Index Like a Book Index04Indexes Are Separate Data Structures05Basic Index Example06Important: An Index Does Not Guarantee Faster Queries07Sequential Scan08Index Scan09Why the Optimizer May Prefer a Sequential Scan10B-Tree Indexes11Why B-Trees Are Useful12B-Tree Range Queries13B-Tree Complexity Intuition14Why Database B-Trees Have Many Children15Index Pages16Buffer Cache17Hash Indexes18B-Tree vs Hash19Composite Indexes20Why Composite Indexes Matter21Column Order Matters22Leftmost Prefix Intuition23Composite Index Example24Equality Before Range — Useful Heuristic25Selectivity26Cardinality27Selectivity vs Cardinality28Cardinality29Selectivity30Indexing Boolean Columns31Partial Indexes32Expression Indexes33Function Calls Can Affect Index Usage34Covering Indexes35Included Columns36Covering Index Trade-Off37Clustered Index38Non-Clustered Index39PostgreSQL Note40Primary Keys and Indexes41Unique Indexes42Indexes and Sorting43Indexes and LIMIT44Indexes and JOINs45Foreign Keys Do Not Automatically Mean "Create an Index Everywhere"46Indexes and Pagination47Composite Cursor Pagination48Indexing Time-Series Data49Indexing and Large Tables50PostgreSQL Concurrent Index Creation51Index Build Is Not Free52Write Amplification53Read-Write Trade-Off54Over-Indexing55Under-Indexing56Duplicate Indexes57Index Prefix Redundancy58Query Shape Matters59Indexing OR Conditions60Functions and Indexes61Leading Wildcards62Full-Text Search63Index vs Search Engine64Statistics65Cardinality Estimation66EXPLAIN67EXPLAIN ANALYZE68Example Query Plan Reasoning69Index-Only Scan70Index and Memory71Indexing Strategy for a Large Table72Index Design Workflow73Indexing Checklist for Every Query74Filtering75Joining76Sorting77Range78Pagination79Selectivity80Result size81Frequency82Example: Notification System83Example: URL Shortener84Example: Chat Messages85Indexing vs Caching86Indexing vs Denormalization87Strategy A88Strategy B89Indexing vs Sharding90Indexing and Distributed Databases91Common Index Anti-Patterns92Anti-pattern 193Anti-pattern 294Anti-pattern 395Anti-pattern 496Anti-pattern 597Anti-pattern 698Anti-pattern 799Anti-pattern 8100Anti-pattern 9101Anti-pattern 10102Production Monitoring103Index Lifecycle104Interview Question — Why Not Index Everything?105Interview Question — What Is a Composite Index?106Interview Question — What Is Selectivity?107Interview Question — Why Is a Sequential Scan Sometimes Better?108Interview Question — How Do You Optimize a Slow Query?109Interview Question — What Makes a Good Composite Index?110Practical Exercise 1 — User Search111Practical Exercise 2 — Orders112Practical Exercise 3 — Query Plan Investigation113Mini Project — Database Index Optimization114Step 1115Step 2116Step 3117Step 4118Step 5119Step 6120Production Scenario121When Indexes Are Not Enough122Master Index Mental Model123The Most Important Indexing Formula124Chapter Summary125Master Checklist126What Comes Next?127Chapter 13 — Database Replication

Course: System Design — Zero to Hero
Level: Intermediate → Advanced
Phase: Database Fundamentals
Status: Complete


1. What You Will Learn#

Indexes are one of the most important tools for database performance.

In Chapter 09 we introduced indexes.

In Chapter 10 we saw how SQL queries, query planners, and EXPLAIN interact with indexes.

Now we go much deeper.

You will learn:

  • What a database index really is
  • Why indexes make lookups faster
  • How indexes work conceptually
  • B-Tree indexes
  • Hash indexes
  • Composite indexes
  • Column order in composite indexes
  • Selectivity
  • Cardinality
  • Index scans
  • Sequential scans
  • Covering indexes
  • Clustered vs non-clustered indexes
  • Primary-key indexes
  • Unique indexes
  • Partial indexes
  • Expression indexes
  • Full-text indexes
  • Index-only scans
  • Query planner decisions
  • Statistics
  • EXPLAIN
  • EXPLAIN ANALYZE
  • Index maintenance
  • Write amplification
  • Index storage cost
  • Over-indexing
  • Under-indexing
  • Large-table indexing
  • Pagination indexes
  • Sorting indexes
  • Join indexes
  • Index anti-patterns
  • Production indexing workflow
  • Interview questions
  • Practical exercises
  • An indexing mini-project

The central idea is:

An index is an additional data structure designed around a query access pattern.


2. Why Do We Need Indexes?#

Suppose we have:

users
-----
100,000,000 rows

We execute:

SELECT *
FROM users
WHERE email = 'anubhav@example.com';

Without a suitable index, the database may need to inspect many rows.

Conceptually:

Row 1
  |
  v
Does email match?
 
Row 2
  |
  v
Does email match?
 
Row 3
  |
  v
Does email match?
 
...
 
Row 100,000,000

This is essentially a search through the table.

An index provides an additional structure:

email
  |
  +---- anubhav@example.com -> row location

The database can use this structure to find relevant data more efficiently.


3. Think of an Index Like a Book Index#

Imagine a 2,000-page book.

Without an index:

Search every page

With an index:

System Design .......... Page 120
Networking ............. Page 240
Databases .............. Page 510

The index tells you where to look.

A database index provides a similar conceptual advantage.

But database indexes are more sophisticated because they must support:

  • inserts
  • updates
  • deletes
  • concurrency
  • storage
  • sorting
  • range searches

4. Indexes Are Separate Data Structures#

An important mental model:

Table
 |
 +-- Actual rows/data
 |
 +-- Index 1
 |
 +-- Index 2
 |
 +-- Index 3

An index is not simply a "flag" on a column.

It is an additional structure maintained by the database.

This explains why indexes consume:

  • disk space
  • memory/cache
  • CPU
  • write resources

5. Basic Index Example#

Suppose:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    email TEXT NOT NULL,
    name TEXT
);

Create an index:

CREATE INDEX idx_users_email
ON users(email);

Now the database has an additional access path for:

email

A query such as:

SELECT id, name
FROM users
WHERE email = 'anubhav@example.com';

may use the index.


6. Important: An Index Does Not Guarantee Faster Queries#

This is one of the most important concepts.

Creating:

CREATE INDEX idx_users_email
ON users(email);

does not guarantee:

every query involving email

will become faster.

The database optimizer chooses an execution plan based on:

  • table size
  • statistics
  • selectivity
  • estimated cost
  • query shape
  • available indexes
  • required ordering
  • amount of data returned

7. Sequential Scan#

A sequential scan reads table rows sequentially.

Conceptually:

Table
 |
 +-- Row 1
 +-- Row 2
 +-- Row 3
 +-- ...
 +-- Row N

It can be surprisingly efficient.

For example:

SELECT *
FROM users;

If you need almost every row, an index provides little benefit.

The database may correctly choose a sequential scan.


8. Index Scan#

An index scan roughly works like:

Query
  |
  v
Index
  |
  v
Matching key
  |
  v
Row location
  |
  v
Table row

This is useful when the query can efficiently narrow down the result.


9. Why the Optimizer May Prefer a Sequential Scan#

Suppose:

Table = 1,000 rows
Query returns = 900 rows

Using an index may require:

Index lookup
    +
many row fetches

A sequential scan may simply read the table efficiently.

Therefore:

Index scans are not inherently better than sequential scans.

The goal is:

Choose the cheapest execution strategy for the workload.


10. B-Tree Indexes#

B-Trees are one of the most important index structures.

They are designed to keep keys ordered and support efficient:

  • equality searches
  • range searches
  • ordering

Conceptually:

                 [50]
               /      \
             /          \
          [20]          [80]
         /   \          /   \
      [10] [30]      [60] [90]

Real database B-Trees are more complex and optimized for storage pages.


11. Why B-Trees Are Useful#

Suppose we need:

WHERE age = 30

A B-Tree can efficiently navigate to the relevant key.

It can also support range queries:

WHERE age >= 30
AND age < 40

And ordering:

ORDER BY age

when the query and index structure align appropriately.


12. B-Tree Range Queries#

Suppose:

Index:
 
10
20
30
40
50
60
70
80

Query:

WHERE age BETWEEN 30 AND 60

The database can locate the beginning of the range and traverse the relevant portion.

This is one reason B-Trees are so widely useful.


13. B-Tree Complexity Intuition#

A balanced tree can reduce search work dramatically.

A simplified intuition is:

Sequential scan:
O(N)
 
Tree lookup:
roughly O(log N)

Real database performance is more complicated because of:

  • page layout
  • caching
  • storage
  • random I/O
  • CPU
  • concurrency
  • optimizer decisions

Do not treat Big-O as a complete performance model.


14. Why Database B-Trees Have Many Children#

A database index is usually page-oriented.

A database does not necessarily build a tiny binary tree like:

        50
       /  \
     20    80

Instead, nodes can contain many keys.

Conceptually:

+--------------------------------+
| 10 | 20 | 30 | 40 | 50 | ... |
+--------------------------------+

This reduces tree height and can make storage access efficient.


15. Index Pages#

Think of a database as operating on pages.

Conceptually:

Index
 |
 +-- Page 1
 +-- Page 2
 +-- Page 3
 +-- Page 4

A B-Tree organizes these pages.

This is why database indexing is closely connected to:

  • memory
  • buffer cache
  • disk I/O

16. Buffer Cache#

Databases often keep frequently accessed pages in memory.

Conceptually:

Query
  |
  v
Buffer cache
  |
  +---- page already in memory -> fast
  |
  +---- page not cached -> storage access

Therefore, the same query can behave differently depending on whether relevant pages are cached.

This is another reason benchmark results must be interpreted carefully.


17. Hash Indexes#

A hash index organizes keys around a hash function.

Conceptually:

key
 |
 v
hash(key)
 |
 v
bucket
 |
 v
value/location

This can be useful for equality-style lookup:

WHERE user_id = 123

But hash indexes generally do not provide the same natural ordering/range-query capabilities as B-Trees.


18. B-Tree vs Hash#

Feature B-Tree Hash
Equality Good Good
Range queries Good Generally not suitable
Ordering Good No natural ordering
Prefix/range-style access Often useful Generally not
Common database default Very common More specialized

Exact support and implementation depend on the database.


19. Composite Indexes#

A composite index contains multiple columns.

Example:

CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at);

This is one index with two indexed columns.


20. Why Composite Indexes Matter#

Suppose the common query is:

SELECT *
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC;

An index on:

(user_id, created_at)

matches the query shape much better than separate unrelated indexes in many cases.

The index provides:

user_id
    +
created_at ordering

in one access path.


21. Column Order Matters#

Compare:

(user_id, created_at)

with:

(created_at, user_id)

They are not equivalent.

Suppose the query is:

WHERE user_id = 123
ORDER BY created_at DESC

The first index aligns naturally with:

user_id -> created_at

The second may not provide the same benefit.

Therefore:

Composite index design must consider query predicates and ordering.


22. Leftmost Prefix Intuition#

For:

(user_id, created_at, status)

the first column is especially important.

Potentially useful access patterns include:

user_id

and:

user_id + created_at

and:

user_id + created_at + status

A query using only:

created_at

does not generally get the same benefit from this index.

This is called the leftmost-prefix intuition.

Exact optimizer behavior depends on the database.


23. Composite Index Example#

Suppose:

SELECT id, amount
FROM orders
WHERE user_id = 123
AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;

Potential candidate:

(user_id, status, created_at DESC)

But the correct order depends on:

  • query patterns
  • selectivity
  • cardinality
  • workload
  • database optimizer

Do not blindly follow a single formula.


24. Equality Before Range — Useful Heuristic#

A common index-design heuristic is:

Equality filters
       ↓
Range/filtering column
       ↓
Ordering columns

For example:

WHERE user_id = 123
AND created_at >= ...
ORDER BY created_at

may suggest:

(user_id, created_at)

This is a heuristic, not a universal law.

Always verify using the actual database's query plan.


25. Selectivity#

Selectivity describes how effectively a condition narrows down rows.

Suppose:

100 million users

and:

email = 'x@example.com'

matches:

1 row

Very selective.

But:

country = 'India'

might match:

20 million rows

Much less selective.

Highly selective conditions are often good candidates for index-driven lookups.


26. Cardinality#

Cardinality refers broadly to the number of distinct values in a column or result set.

Example:

gender:
Male
Female

Low cardinality.

email:
almost every value unique

High cardinality.

High cardinality often makes an equality index more selective, but index usefulness still depends on the complete query and workload.


27. Selectivity vs Cardinality#

They are related but not identical.

Cardinality#

How many distinct values exist.

Selectivity#

How much a particular predicate narrows the result.

Example:

email

may have high cardinality and high selectivity for:

WHERE email = '...'

But:

country

may have relatively low cardinality and low selectivity for a common country.


28. Indexing Boolean Columns#

A common question:

Should I index a boolean column?

Suppose:

is_active

contains:

true = 99%
false = 1%

A normal index may not always be useful for:

WHERE is_active = true

because most rows match.

But an index can sometimes be useful for the rare value.

The correct answer depends on:

  • data distribution
  • query pattern
  • table size
  • database optimizer

29. Partial Indexes#

Some databases support indexes over only rows satisfying a condition.

PostgreSQL example:

CREATE INDEX idx_active_users_email
ON users(email)
WHERE deleted_at IS NULL;

This index contains only active/non-deleted users.

Benefits can include:

  • smaller index
  • lower maintenance cost
  • efficient targeted queries

when the query matches the predicate.


30. Expression Indexes#

Sometimes queries operate on expressions.

Example:

SELECT *
FROM users
WHERE LOWER(email) = 'anubhav@example.com';

An ordinary index on:

email

may not directly provide the desired access path.

A database supporting expression indexes can use:

CREATE INDEX idx_users_lower_email
ON users(LOWER(email));

This aligns the index with the expression used by the query.


31. Function Calls Can Affect Index Usage#

Suppose:

WHERE LOWER(name) = 'anubhav'

while the index is:

(name)

Depending on the database, this may prevent a normal index from being used effectively.

Potential solutions:

  • expression index
  • normalized stored value
  • different query design

Again:

Inspect the actual query plan.


32. Covering Indexes#

A covering index contains enough information to answer a query without needing to fetch the full table row, when the database can use that structure for an index-only access path.

Suppose:

SELECT user_id, created_at
FROM orders
WHERE user_id = 123
ORDER BY created_at DESC;

An index on:

(user_id, created_at)

may contain everything required for the query.

This can reduce table access.


33. Included Columns#

Some databases allow non-key columns to be stored with an index.

PostgreSQL example:

CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC)
INCLUDE (amount, status);

The indexed columns determine the ordering/search structure.

Included columns can provide additional data for index-only access without being part of the search key.


34. Covering Index Trade-Off#

A covering index can improve reads.

But it can also:

increase index size
increase write cost
increase storage
increase cache pressure

Therefore:

Covering indexes are optimization tools, not defaults.


35. Clustered Index#

The terminology differs across database systems, so be careful.

In systems with a clustered-index concept, the table's data organization is closely tied to an index ordering.

Conceptually:

Clustered index
      |
      v
Data stored according to index organization

This can make certain range access patterns efficient.

But the exact implementation differs by database.


36. Non-Clustered Index#

A non-clustered index is conceptually separate from the underlying table storage.

Index
 |
 +-- key -> row locator

The database can:

Find key in index
       |
       v
Locate table row
       |
       v
Read data

This can involve additional table lookups.


37. PostgreSQL Note#

PostgreSQL's common B-Tree indexes should not be explained as if they were identical to SQL Server's clustered/non-clustered index model.

PostgreSQL tables are heap-organized by default, and indexes point to table tuples.

This is an important interview nuance:

Do not memorize index terminology without understanding the database engine.


38. Primary Keys and Indexes#

A primary key usually has an associated unique index or equivalent enforcement mechanism.

For example:

CREATE TABLE users (
    id BIGINT PRIMARY KEY
);

The database needs an efficient mechanism to enforce:

unique id

and support primary-key lookup.


39. Unique Indexes#

Example:

CREATE UNIQUE INDEX idx_users_email
ON users(email);

This enforces uniqueness.

But when uniqueness is part of the logical schema, a constraint is often clearer:

email TEXT UNIQUE

or:

ALTER TABLE users
ADD CONSTRAINT users_email_unique UNIQUE (email);

40. Indexes and Sorting#

Suppose:

SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 20;

An appropriate index can sometimes help the database avoid sorting a huge number of rows.

For example:

created_at DESC

can provide data in the required order.

This is especially useful for:

latest records
recent events
leaderboards
time-based feeds

41. Indexes and LIMIT#

Consider:

SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 20;

If the database has a useful ordering index, it may only need to find the first relevant entries rather than sorting the entire table.

Conceptually:

Without useful index:
 
Read many rows
   ↓
Sort
   ↓
Take 20
 
With suitable index:
 
Index
   ↓
First 20

The exact plan depends on filters and other query conditions.


42. Indexes and JOINs#

Suppose:

SELECT *
FROM users u
JOIN orders o
    ON o.user_id = u.id
WHERE u.id = 123;

An index on:

orders.user_id

can be valuable because the database needs to locate orders belonging to user 123.

This is a common example of indexing a foreign-key column based on access patterns.


43. Foreign Keys Do Not Automatically Mean "Create an Index Everywhere"#

A foreign key constraint and an index are different concepts.

A foreign key enforces relationship integrity.

An index improves access patterns.

For example:

orders.user_id

may be worth indexing because queries frequently:

find orders for user
join orders to users

But the decision should come from workload and database behavior.


44. Indexes and Pagination#

Suppose we use keyset pagination:

SELECT *
FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 50;

An index on:

id

can support this access pattern naturally.

For time-based pagination:

SELECT *
FROM events
WHERE created_at < $1
ORDER BY created_at DESC
LIMIT 50;

an appropriate index on:

created_at

may be useful.


45. Composite Cursor Pagination#

Suppose timestamps are not unique.

Using only:

created_at

can create ambiguity.

A more stable ordering might be:

(created_at, id)

Query:

WHERE
    (created_at, id) < ($last_created_at, $last_id)
ORDER BY created_at DESC, id DESC
LIMIT 50;

The exact syntax and indexing strategy depend on the database.

The important principle is:

Use a deterministic ordering for cursor pagination.


46. Indexing Time-Series Data#

Suppose:

events
------
id
service_id
created_at
payload

Common query:

WHERE service_id = 123
AND created_at >= ...
ORDER BY created_at DESC

A candidate composite index:

(service_id, created_at DESC)

matches the access pattern.

This pattern appears frequently in:

  • logs
  • events
  • notifications
  • user activity
  • orders
  • messages

47. Indexing and Large Tables#

Suppose:

1 billion rows

Adding an index can be expensive.

Potential concerns:

Index build time
Disk space
I/O
CPU
Locking/concurrency
Replication impact
Deployment duration

Production index creation must be planned carefully.

Some databases provide online/concurrent index creation mechanisms.

The exact commands differ by database.


48. PostgreSQL Concurrent Index Creation#

For PostgreSQL, one option is:

CREATE INDEX CONCURRENTLY idx_orders_user_created
ON orders(user_id, created_at DESC);

This can reduce blocking of normal table operations compared with a standard index build, but it has its own trade-offs and operational requirements.

Always understand the database-specific behavior before using it in production.


49. Index Build Is Not Free#

Creating:

10 new indexes

on a huge production table can generate substantial:

  • CPU
  • I/O
  • storage
  • replication traffic

It can also affect application performance.

Index migrations should be treated as production changes.


50. Write Amplification#

Suppose:

Table
+
5 indexes

An insert may require maintaining:

Table data
Index 1
Index 2
Index 3
Index 4
Index 5

Conceptually:

1 logical write
      |
      +---- table
      +---- index 1
      +---- index 2
      +---- index 3
      +---- index 4
      +---- index 5

This is write amplification.


51. Read-Write Trade-Off#

Indexes create a fundamental trade-off:

More indexes
      |
      +---- Better read access
      |
      +---- More storage
      |
      +---- More write work
      |
      +---- More maintenance

System design requires balancing these costs.


52. Over-Indexing#

Over-indexing means creating more indexes than the workload justifies.

Symptoms:

Many indexes
High write latency
Large storage usage
Slow writes
Index maintenance overhead

A table with:

20 indexes

is not automatically better optimized than one with:

5 carefully chosen indexes

53. Under-Indexing#

Under-indexing can produce:

Large sequential scans
High CPU
High I/O
High latency
Database overload

The goal is not:

minimum indexes

or:

maximum indexes

The goal is:

The right indexes for the actual workload.


54. Duplicate Indexes#

Suppose a table has:

INDEX(email)
INDEX(email)

The second index may provide little or no benefit while consuming resources.

Another subtle case:

INDEX(user_id, created_at)
INDEX(user_id)

The second index may or may not be necessary depending on workload and database behavior.

Review indexes periodically.


55. Index Prefix Redundancy#

Suppose:

INDEX(user_id, created_at)

already exists.

A separate:

INDEX(user_id)

may be redundant for some workloads because the composite index can often support lookups beginning with user_id.

But this is not an automatic rule.

Consider:

  • index size
  • query plan
  • ordering
  • included columns
  • workload
  • write costs

56. Query Shape Matters#

Consider two queries:

WHERE user_id = 123

and:

WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 20

They may benefit from different index designs.

The second query may benefit strongly from:

(user_id, created_at DESC)

because the index supports both filtering and ordering.


57. Indexing OR Conditions#

Suppose:

WHERE email = $1
OR phone = $2

Possible strategies include separate indexes:

email
phone

The optimizer may combine access paths or choose another plan.

Do not assume:

OR = slow

or:

two indexes = automatically fast

Inspect the query plan.


58. Functions and Indexes#

Consider:

WHERE DATE(created_at) = '2026-10-01'

Applying a function to every row can interfere with efficient use of a normal created_at index.

A range predicate is often more index-friendly:

WHERE created_at >= '2026-10-01'
AND created_at < '2026-10-02'

This also avoids wrapping the indexed column in a function.


59. Leading Wildcards#

Consider:

WHERE name LIKE 'Anu%'

This can be compatible with certain index strategies.

But:

WHERE name LIKE '%Anu%'

requires finding a substring anywhere in the value and may not be efficiently handled by an ordinary B-Tree index.

For text search, specialized full-text or trigram indexes may be more appropriate depending on the database.


60. Full-Text Search#

If the requirement is:

Search articles containing:
"distributed systems database"

a normal equality index is not the right abstraction.

A search engine or full-text indexing mechanism may be appropriate.

Examples:

PostgreSQL full-text search
Elasticsearch
OpenSearch

The correct choice depends on the application's search requirements.


61. Index vs Search Engine#

Do not confuse:

Database index

with:

Search engine index

A database B-Tree is excellent for:

WHERE id = 123
WHERE created_at > ...
ORDER BY created_at

A search engine is designed for richer text search:

relevance
tokenization
stemming
fuzzy matching
faceting
ranking

Later chapters will explore search systems separately.


62. Statistics#

Query optimizers rely on statistics to estimate:

How many rows match?
How selective is this condition?
Which join strategy is likely cheaper?
Should we use an index?

If statistics are stale, the optimizer may choose a poor plan.

This is one reason databases provide mechanisms for statistics collection and maintenance.


63. Cardinality Estimation#

Suppose the optimizer estimates:

Expected rows = 10

but actual result:

10,000,000 rows

The optimizer's assumptions were badly wrong.

This can cause an inappropriate execution strategy.

For example:

Expected 10 rows
    |
    v
Index lookup seems cheap
 
Actual 10M rows
    |
    v
Index access becomes very expensive

This is why:

estimated rows
vs
actual rows

is important when analyzing query plans.


64. EXPLAIN#

Example:

EXPLAIN
SELECT *
FROM users
WHERE email = 'anubhav@example.com';

The database may show a plan resembling:

Index Scan
  Index Cond: email = ...

or:

Seq Scan
  Filter: email = ...

The exact output depends on the database.


65. EXPLAIN ANALYZE#

Example:

EXPLAIN ANALYZE
SELECT *
FROM users
WHERE email = 'anubhav@example.com';

This can show:

Planning Time
Execution Time
Estimated Rows
Actual Rows
Loops

It lets us compare estimates with reality.


66. Example Query Plan Reasoning#

Suppose:

Estimated rows = 1
Actual rows = 1
Execution time = 2 ms

Looks healthy.

Now:

Estimated rows = 10
Actual rows = 2,000,000
Execution time = 2 seconds

The query planner's estimates are badly wrong.

Investigate:

Statistics
Data distribution
Query predicates
Indexes
Correlation

67. Index-Only Scan#

In some databases, the query can be answered directly from index information.

Conceptually:

Query
  |
  v
Index
  |
  v
Result

instead of:

Query
  |
  v
Index
  |
  v
Table
  |
  v
Result

This can reduce table access.

But database-specific visibility or storage rules can affect whether a true index-only scan is possible.


68. Index and Memory#

Indexes that fit into memory or are frequently cached can perform very well.

Large indexes may cause:

Cache pressure

and reduce the amount of memory available for:

table pages
other indexes
working data

Therefore:

Index size is also a memory/cache design consideration.


69. Indexing Strategy for a Large Table#

Suppose:

orders = 500 million rows

Queries:

orders by user
recent orders
orders by status
orders by ID

Potential candidates:

PRIMARY KEY(id)
INDEX(user_id, created_at)
INDEX(status, created_at)

But before adding all three:

  1. Measure query frequency.
  2. Check query plans.
  3. Check selectivity.
  4. Estimate write cost.
  5. Check index size.
  6. Consider whether one index can serve multiple queries.

70. Index Design Workflow#

A strong production workflow:

1. Identify important query
          ↓
2. Measure latency/frequency
          ↓
3. Inspect EXPLAIN
          ↓
4. Understand predicates
          ↓
5. Understand ordering
          ↓
6. Check existing indexes
          ↓
7. Design candidate index
          ↓
8. Test with representative data
          ↓
9. Compare query plans
          ↓
10. Measure production impact

This is far better than:

Add an index because the query is slow.


71. Indexing Checklist for Every Query#

Ask:

Filtering#

WHERE columns?

Joining#

JOIN columns?

Sorting#

ORDER BY columns?

Range#

> < >= <= BETWEEN?

Pagination#

LIMIT?
Cursor?

Selectivity#

How many rows match?

Result size#

How many rows are returned?

Frequency#

How often does this query run?

72. Example: Notification System#

Suppose:

notifications
-------------
id
user_id
created_at
is_read
message

Query:

SELECT id, message, created_at
FROM notifications
WHERE user_id = 123
AND is_read = false
ORDER BY created_at DESC
LIMIT 50;

Candidate index:

(user_id, is_read, created_at DESC)

But the best index depends on:

How many notifications are unread?
How frequently are reads/writes performed?
How many users?
How often does this endpoint run?

73. Example: URL Shortener#

Suppose:

urls
----
short_code
long_url
created_at

Critical query:

SELECT long_url
FROM urls
WHERE short_code = $1;

The index should make:

short_code lookup

efficient.

Usually:

UNIQUE(short_code)

is a natural schema constraint and provides an access path.

This is a classic high-read, low-latency lookup.


74. Example: Chat Messages#

Schema:

messages
--------
conversation_id
message_id
created_at
sender_id
content

Query:

SELECT message_id, sender_id, content, created_at
FROM messages
WHERE conversation_id = $1
ORDER BY created_at DESC
LIMIT 50;

Candidate index:

(conversation_id, created_at DESC)

This aligns:

partition/filter
+
ordering
+
limit

with the query.


75. Indexing vs Caching#

Suppose:

Database query = 10 ms
Cache lookup = 1 ms

Caching can reduce repeated database work.

But an index may reduce:

10 ms -> 2 ms

for a database lookup.

They solve different problems:

Index
  -> make database access efficient
 
Cache
  -> avoid database access for repeated data

Production systems often use both.


76. Indexing vs Denormalization#

Suppose a query requires expensive joins.

Two possible strategies:

Strategy A#

Optimize indexes:

Table A
  |
JOIN
  |
Table B

Strategy B#

Denormalize:

Store required data together

Indexes preserve more normalized data modeling.

Denormalization can simplify reads at the cost of duplication.

The correct choice depends on workload.


77. Indexing vs Sharding#

Indexes optimize access within a database/partition.

Sharding distributes data across multiple nodes.

Think:

Sharding
   |
   +--> Which node has the data?
 
Index
   |
   +--> Where inside that node can I find it efficiently?

Large distributed systems often need both.


78. Indexing and Distributed Databases#

In distributed systems, an index may itself have distributed implications.

Questions include:

Where is the index stored?
How is it replicated?
How is it updated?
What happens during partition failure?
Can secondary indexes become hotspots?

This is one reason distributed secondary indexes can be more complex than local indexes.


79. Common Index Anti-Patterns#

Anti-pattern 1#

Index every column.

Anti-pattern 2#

Create indexes without examining queries.

Anti-pattern 3#

Ignore index write cost.

Anti-pattern 4#

Use the wrong composite-column order.

Anti-pattern 5#

Assume every index is used.

Anti-pattern 6#

Ignore data distribution.

Anti-pattern 7#

Ignore query result size.

Anti-pattern 8#

Use a normal B-Tree for a full-text search problem.

Anti-pattern 9#

Create indexes on expressions without understanding query shape.

Anti-pattern 10#

Never remove obsolete indexes.


80. Production Monitoring#

Useful database metrics include:

Query latency
Queries/sec
Slow queries
CPU
Memory
Disk I/O
Buffer/cache hit ratio
Connection count
Lock waits
Index size
Table size
Replication lag

For indexes specifically:

Index usage
Index scans
Unused indexes
Index size
Write overhead

Exact metrics vary by database.


81. Index Lifecycle#

Indexes should be treated as production assets.

Lifecycle:

Design
  ↓
Create
  ↓
Measure
  ↓
Monitor
  ↓
Tune
  ↓
Retain or remove

An index that was useful six months ago may become unnecessary after:

schema changes
query changes
traffic changes
data growth
application changes

82. Interview Question — Why Not Index Everything?#

Strong answer:

Indexes improve certain reads, but they consume storage and increase the work required for inserts, updates, and deletes. They also create memory/cache pressure and maintenance overhead. Therefore indexes should be designed around important access patterns and validated with query plans and measurements.


83. Interview Question — What Is a Composite Index?#

A composite index is an index over multiple columns.

Example:

(user_id, created_at)

It can support queries that use the leading columns and may also support ordering/range access depending on the query.

Column order matters.


84. Interview Question — What Is Selectivity?#

Selectivity describes how strongly a predicate narrows the result set.

Example:

email = X

may match one row among millions.

That is highly selective.

A condition matching most rows is less selective.


85. Interview Question — Why Is a Sequential Scan Sometimes Better?#

Because an index is not free.

If a query needs a large fraction of a table, using an index may require many random or scattered table accesses.

Reading the table sequentially can be cheaper.

The optimizer chooses based on cost estimates.


86. Interview Question — How Do You Optimize a Slow Query?#

A strong system-design answer:

Measure
  ↓
EXPLAIN / EXPLAIN ANALYZE
  ↓
Inspect scan/join/sort
  ↓
Check row estimates
  ↓
Check indexes
  ↓
Check statistics
  ↓
Check CPU/I/O/locks
  ↓
Rewrite query or add/change index
  ↓
Measure again

87. Interview Question — What Makes a Good Composite Index?#

Consider:

Equality predicates
Range predicates
Ordering
Join conditions
Selectivity
Query frequency
Write cost

Then validate the design using the actual database query planner.


88. Practical Exercise 1 — User Search#

Table:

users
-----
id
email
name
country
created_at

Queries:

SELECT id, name
FROM users
WHERE email = $1;
SELECT id, name
FROM users
WHERE country = $1
ORDER BY created_at DESC
LIMIT 50;

Determine candidate indexes.

Think about:

email
(country, created_at)

Then explain why each index matches its query.


89. Practical Exercise 2 — Orders#

Table:

orders
------
id
user_id
status
amount
created_at

Queries:

SELECT *
FROM orders
WHERE user_id = $1
ORDER BY created_at DESC
LIMIT 20;
SELECT COUNT(*)
FROM orders
WHERE status = 'PAID';
SELECT *
FROM orders
WHERE created_at >= $1
AND created_at < $2;

Design candidate indexes.

Then ask:

Are all three indexes actually necessary?

This is the important part.


90. Practical Exercise 3 — Query Plan Investigation#

Suppose:

Table:
100 million rows
 
Query:
SELECT *
FROM events
WHERE user_id = 123
ORDER BY created_at DESC
LIMIT 50;

You observe:

Execution time = 2.5 seconds

Investigate:

Is there an index?
What is the index order?
How many rows are estimated?
How many rows are actually scanned?
Is a sort occurring?
Is the table heavily cached?
Is the query returning too many columns?

Then design an optimization.


91. Mini Project — Database Index Optimization#

Use the e-commerce schema from previous chapters:

users
products
orders
order_items
payments

Assume these API endpoints:

GET /users/:id/orders
GET /products/:id
GET /products?category=...
GET /orders/:id
GET /users/:id/payments
GET /orders/recent

For each endpoint:

Step 1#

Write the SQL query.

Step 2#

Identify:

WHERE
JOIN
ORDER BY
LIMIT

Step 3#

Design candidate indexes.

Step 4#

Explain:

Why this index?
Why this column order?
What is the selectivity?
What is the write cost?

Step 5#

Use:

EXPLAIN

to inspect the plan.

Step 6#

Determine whether the index is actually useful.


92. Production Scenario#

Suppose your database has:

500 million orders

Traffic:

100,000 reads/sec
20,000 writes/sec

You discover:

GET /users/:id/orders

is slow.

Do not immediately shard.

First investigate:

Query
 ↓
Index
 ↓
Query plan
 ↓
Data distribution
 ↓
Database resources

If a correct composite index reduces the workload sufficiently, sharding may not yet be necessary.

This illustrates an important system-design principle:

Optimize the current bottleneck before adding architectural complexity.


93. When Indexes Are Not Enough#

Sometimes indexing cannot solve the problem.

Possible reasons:

Dataset too large for one node
Write throughput too high
Hot partition
Global traffic
Complex search
Large analytical workload
High availability requirements

Then we may need:

Caching
Read replicas
Partitioning
Sharding
Search infrastructure
Data warehouses
Streaming systems

These will appear throughout later chapters.


94. Master Index Mental Model#

Remember:

                    Query
                      |
                      v
                Query Planner
                      |
          +-----------+-----------+
          |                       |
          v                       v
       Index                    Table
          |                       |
          v                       |
    Candidate rows <-------------+
          |
          v
       Result

The optimizer decides whether an index helps.


95. The Most Important Indexing Formula#

There is no universal formula.

Instead remember:

Good Index
=
Query Pattern
+
Data Distribution
+
Selectivity
+
Ordering
+
Workload Frequency
-
Write Cost
-
Storage Cost

This is a mental model, not a literal mathematical equation.


96. Chapter Summary#

You now understand indexes at a much deeper level.

The important concepts are:

Index
  |
  +-- B-Tree
  +-- Hash
  +-- Composite
  +-- Covering
  +-- Partial
  +-- Expression
  |
  +-- Selectivity
  +-- Cardinality
  +-- Query planner
  +-- Sequential scan
  +-- Index scan
  +-- EXPLAIN
  |
  +-- Read optimization
  +-- Write amplification
  +-- Storage cost
  +-- Maintenance

The most important lesson is:

An index is not simply "something that makes a column faster." It is a data structure designed to accelerate specific access patterns.

A strong database engineer asks:

What query are we optimizing?
        ↓
How selective is it?
        ↓
How often does it run?
        ↓
What ordering does it need?
        ↓
What index supports it?
        ↓
What will that index cost on writes?
        ↓
What does EXPLAIN say?
        ↓
Did production latency actually improve?

97. Master Checklist#

You should now be able to explain:

  • Why indexes exist
  • Index as an additional data structure
  • Sequential scan
  • Index scan
  • B-Tree
  • B-Tree range queries
  • Hash indexes
  • B-Tree vs hash
  • Composite indexes
  • Column order
  • Leftmost-prefix intuition
  • Selectivity
  • Cardinality
  • Boolean-column indexing
  • Partial indexes
  • Expression indexes
  • Covering indexes
  • Included columns
  • Clustered indexes
  • Non-clustered indexes
  • PostgreSQL heap/index model
  • Primary-key indexes
  • Unique indexes
  • Indexes for sorting
  • Indexes for joins
  • Indexes for pagination
  • Time-series indexing
  • Large-table indexing
  • Online/concurrent index creation
  • Write amplification
  • Over-indexing
  • Under-indexing
  • Duplicate indexes
  • Query shape
  • Function/expression effects
  • Leading wildcard problem
  • Full-text search vs database indexes
  • Query statistics
  • Cardinality estimation
  • EXPLAIN
  • EXPLAIN ANALYZE
  • Index-only scans
  • Index memory/cache impact
  • Production indexing workflow
  • Index monitoring
  • Index lifecycle

98. What Comes Next?#

Chapter 13 — Database Replication#

Next we move from optimizing a single database to distributing database data across multiple machines.

We will learn:

Why replication exists
        ↓
Primary / Replica architecture
        ↓
Leader / Follower
        ↓
Synchronous replication
        ↓
Asynchronous replication
        ↓
Replication lag
        ↓
Read replicas
        ↓
Read-after-write consistency
        ↓
Failover
        ↓
Leader election
        ↓
Split-brain
        ↓
Replication topologies
        ↓
Multi-region replication
        ↓
Conflict handling
        ↓
High availability
        ↓
Production replication architecture

This is where database fundamentals begin connecting directly to high availability, scalability, and distributed systems.

Finished this chapter?

Mark it as complete to track your mastery.

Previous ChapterChapter 11 — NoSQL DatabasesNext ChapterChapter 13 — Database Replication
PrevNext