Rails 8 App: Setup Test DB in PostgreSQL | Covering Index | BRIN Indexes | Hash Indexes | Createย super fast indexes

Let’s look into some of the features of sql data indexing. This will be super helpful while developing our Rails 8 Application.

๐Ÿ’Ž Part 1: What is a Covering Index?

Normally when you query:

SELECT * FROM users WHERE username = 'bob';

  • Database searches username index (secondary).
  • Finds a pointer (TID or PK).
  • Then fetches full row from table (heap or clustered B-tree).

Problem:

  • Heap fetch = extra disk read.
  • Clustered B-tree fetch = extra traversal.

๐Ÿ“œ Covering Index idea:

โœ… If the index already contains all the columns you need,
โœ… Then the database does not need to fetch the full row!

It can answer the query purely by scanning the index! โšก

Boom โ€” one disk read, no extra hop!

โœ๏ธ Example in PostgreSQL:

Suppose your query is:

SELECT username FROM users WHERE username = 'bob';

  • You only need username.
  • But by default, PostgreSQL indexes only store the index column (here, username) + TID.

โœ… So in this case โ€” already covering!

No heap fetch needed!

โœ๏ธ Example in MySQL InnoDB:

Suppose your query is:

SELECT username FROM users WHERE username = 'bob';

  • Secondary index (username) contains:
    • username (indexed column)
    • user_id (because secondary indexes in InnoDB always store PK)

โ™ฆ๏ธ So again, already covering!
No need to jump to the clustered index!

๐ŸŽฏ Key point:

If your query only asks for columns already inside the index,
then only the index is touched โž” no second lookup โž” super fast!

๐Ÿ’Ž Part 2: Real SQL Examples

โœจ PostgreSQL

Create a covering index for common query:

CREATE INDEX idx_users_username_email ON users (username, email);

Now if you run:

SELECT email FROM users WHERE username = 'bob';

Postgres can:

  • Search index on username
  • Already have email in index
  • โœ… No heap fetch!

(And Postgres is smart: it checks index-only scan automatically.)

โœจ MySQL InnoDB

Create a covering index:

CREATE INDEX idx_users_username_email ON users (username, email);

โœ… Now query:

SELECT email FROM users WHERE username = 'bob';

Same behavior:

  • Only secondary index read.
  • No need to touch primary clustered B-tree.

๐Ÿ’Ž Part 3: Tips to design smart Covering Indexes

โœ… If your query uses WHERE on col1 and SELECT col2,
โœ… Best to create index: (col1, col2).

โœ… Keep indexes small โ€” don’t add 10 columns unless needed.
โœ… Avoid huge TEXT or BLOB columns in covering indexes โ€” they make indexes heavy.

โœ… Composite indexes are powerful:

CREATE INDEX idx_users_username_email ON users (username, email);

โ†’ Can be used for:

  • WHERE username = ?
  • WHERE username = ? AND email = ?
  • etc.

โœ… Monitor index usage:

  • PostgreSQL: EXPLAIN ANALYZE
  • MySQL: EXPLAIN

โœ… Always check if Index Only Scan or Using Index appears in EXPLAIN plan!

๐Ÿ“š Quick Summary Table

DatabaseNormal QueryWith Covering Index
PostgreSQLB-tree โž” Heap fetch (unless TID optimization)B-tree scan only
MySQL InnoDBSecondary B-tree โž” Primary B-treeSecondary B-tree only
Result2 steps1 step
SpeedSlowerFaster

๐Ÿ† Great! โ€” Now We Know:

๐ŸงŠ How heap fetch works!
๐ŸงŠ How block lookup is O(1)!
๐ŸงŠ How covering indexes skip heap fetch!
๐ŸงŠ How to create super fast indexes for PostgreSQL and MySQL!


๐Ÿฆพ Advanced Indexing Tricks (Real Production Tips)

Now it’s time to look into super heavy functionalities that Postgres supports for making our sql data search/fetch super fast and efficient.

1. ๐ŸŽฏ Partial Indexes (PostgreSQL ONLY)

โœ… Instead of indexing the whole table,
โœ… You can index only the rows you care about!

Example:

Suppose 95% of users have status = 'inactive', but you only search active users:

SELECT * FROM users WHERE status = 'active' AND email = 'bob@example.com';

๐Ÿ‘‰ Instead of indexing the whole table:

CREATE INDEX idx_active_users_email ON users (email) WHERE status = 'active';

โ™ฆ๏ธ PostgreSQL will only store rows with status = 'active' in this index!

Advantages:

  • Smaller index = Faster scans
  • Less space on disk
  • Faster index maintenance (less updates/inserts)

Important:

  • MySQL (InnoDB) does NOT support partial indexes ๐Ÿ˜” โ€” only PostgreSQL has this superpower.

2. ๐ŸŽฏ INCLUDE Indexes (PostgreSQL 11+)

โœ… Normally, a composite index uses all columns for sorting/searching.
โœ… With INCLUDE, extra columns are just stored in index, not used for ordering.

Example:

CREATE INDEX idx_username_include_email ON users (username) INCLUDE (email);

Meaning:

  • username is indexed and ordered.
  • email is only stored alongside.

Now query:

SELECT email FROM users WHERE username = 'bob';

โž” Index-only scan โ€” no heap fetch.

Advantages:

  • Smaller & faster than normal composite indexes.
  • Helps to create very efficient covering indexes.

Important:

  • MySQL 8.0 added something similar with INVISIBLE columns but it’s still different.

3. ๐ŸŽฏ Composite Index Optimization

โœ… Always order columns inside index smartly based on query pattern.

Golden Rules:

โšœ๏ธ Equality columns first (WHERE col = ?)
โšœ๏ธ Range columns second (WHERE col BETWEEN ?)
โšœ๏ธ SELECT columns last (for covering)

Example:

If query is:

SELECT email FROM users WHERE status = 'active' AND created_at > '2024-01-01';

Best index:

CREATE INDEX idx_users_status_created_at ON users (status, created_at) INCLUDE (email);

โ™ฆ๏ธ status first (equality match)
โ™ฆ๏ธ created_at second (range)
โ™ฆ๏ธ email included (covering)

Bad Index: (wrong order)

CREATE INDEX idx_created_at_status ON users (created_at, status);

โ†’ Will not be efficient!

4. ๐ŸŽฏ BRIN Indexes (PostgreSQL ONLY, super special!)

โœ… When your table is very huge (millions/billions of rows),
โœ… And rows are naturally ordered (like timestamp, id increasing),
โœ… You can create a BRIN (Block Range Index).

Example:

CREATE INDEX idx_users_created_at_brin ON users USING BRIN (created_at);

โ™ฆ๏ธ BRIN stores summaries of large ranges of pages (e.g., min/max timestamp per 128 pages).

โ™ฆ๏ธ Ultra small index size.

โ™ฆ๏ธ Very fast for large range queries like:

SELECT * FROM users WHERE created_at BETWEEN '2024-01-01' AND '2024-04-01';

Important:

  • BRIN โ‰  B-tree
  • BRIN is approximate, B-tree is precise.
  • Only useful if data is naturally correlated with physical storage order.

MySQL?

  • MySQL does not have BRIN natively. PostgreSQL has a big advantage here.

5. ๐ŸŽฏ Hash Indexes (special case)

โœ… If your query is always exact equality (not range),
โœ… You can use hash indexes.

Example:

CREATE INDEX idx_users_username_hash ON users USING HASH (username);

Useful for:

  • Simple WHERE username = 'bob'
  • Never ranges (BETWEEN, LIKE, etc.)

โš ๏ธ Warning:

  • Hash indexes used to be “lossy” before Postgres 10.
  • Now they are safe, but usually B-tree is still better unless you have very heavy point lookups.

๐Ÿ˜Ž PRO-TIP: Which Index Type to Use?

Use caseIndex type
Search small ranges or equalityB-tree
Search on huge tables with natural order (timestamps, IDs)BRIN
Only exact match, super heavy lookupHash
Search only small part of table (active users, special conditions)Partial index
Need to skip heap fetchINCLUDE / Covering Index

๐Ÿ—บ๏ธ Quick Visual Mindmap:

Your Query
โ”‚
โ”œโ”€โ”€ Need Equality + Range? โž” B-tree
โ”‚
โ”œโ”€โ”€ Need Huge Time Range Query? โž” BRIN
โ”‚
โ”œโ”€โ”€ Exact equality only? โž” Hash
โ”‚
โ”œโ”€โ”€ Want Smaller Index (filtered)? โž” Partial Index
โ”‚
โ”œโ”€โ”€ Want to avoid Heap Fetch? โž” INCLUDE columns (Postgres) or Covering Index

๐Ÿ† Now we Know:

๐ŸงŠ Partial Indexes
๐ŸงŠ INCLUDE Indexes
๐ŸงŠ Composite Index order tricks
๐ŸงŠ BRIN Indexes
๐ŸงŠ Hash Indexes
๐ŸงŠ How to choose best Index

โœ… This is serious pro-level database knowledge.


Enjoy SQL! ๐Ÿš€