Question presented to candidate: "Your query uses an index but still shows a Bitmap Heap Scan reading thousands of table pages. How would you use a covering index with INCLUDE to get rid of those heap fetches, and what has to be true for PostgreSQL to actually skip the table?"
What a strong answer should cover:
- What a Bitmap Heap Scan is: a Bitmap Index Scan collects matching row locations, sorts them by page, then the heap is read page by page; it sits between an Index Scan and a Seq Scan, can combine indexes with BitmapAnd / BitmapOr, and goes lossy (page-level, with
Rows Removed by Index Recheck) when the bitmap exceedswork_mem - Why it can be slow: a few percent of rows scattered across the table can touch nearly every page, so the "index" plan reads most of the table anyway
- The fix: a covering index such as
CREATE INDEX ... (score) INCLUDE (id)(PostgreSQL 11+) so the planner can choose an Index Only Scan;INCLUDEcolumns ride in the leaf entries, are not searchable or sorted, and do not change what aUNIQUEindex enforces - The catch: Index Only Scans depend on the visibility map; until
VACUUMmarks pages all-visible the planner may not even choose one, and recently updated pages show up asHeap Fetches; tune autovacuum on hot tables - Costs:
INCLUDEindexes are never deduplicated and are bigger, updates of an included column cannot be HOT; checkHeap FetchesandBufferswithEXPLAIN (ANALYZE, BUFFERS)before and after
Clarifying questions expected:
- Which columns does the query select, filter and sort on, and how many rows does it return?
- How write-heavy is the table, and is autovacuum keeping up (when was it last vacuumed)?
- Which PostgreSQL version (INCLUDE needs 11+)?
Code / implementation expected: Yes — the plan before, the INCLUDE index, the plan after with Heap Fetches, and how VACUUM changes it.
Schema used by the examples: