Two more enable_* planner toggles, and they belong together because the thing they switch off is, in both cases, an index — the difference is how the index gets used. Both default on, both are user context, and both carry the family warning from enable_async_append: these are diagnostic instruments, not tuning knobs. You flip them in a session to see the planner’s second choice, not in postgresql.conf to force its hand.
The reason to take them as a pair is that understanding when to reach for one requires understanding the boundary between them.
Two ways to use an index
A plain index scan walks the index in order and, for each matching entry, immediately jumps to the heap to fetch that row. Index entry, heap fetch, index entry, heap fetch — interleaved. This is wonderfully cheap when you’re fetching a handful of rows, because you do a handful of heap jumps. It gets expensive as the row count climbs, for a specific reason: the heap jumps are in index order, not physical order, so they scatter randomly across the table, and the same heap page can get visited many times if multiple matching rows happen to live on it but the index visits them non-consecutively. Lots of random, possibly-repeated page access.
A bitmap scan is PostgreSQL’s answer to that scaling problem, and it always appears in EXPLAIN as a pair of nodes: a Bitmap Index Scan feeding a Bitmap Heap Scan. The first node scans the index but fetches nothing — it builds a bitmap of all the row locations that match. The second node then walks that bitmap in physical page order, visiting each heap page exactly once and grabbing every matching row on it before moving on. The random, repeated heap access of a plain index scan becomes one orderly pass over only the pages that contain matches. Two consequences fall out of that design worth knowing: because it’s collecting TIDs before fetching, a bitmap scan can combine several indexes with BitmapAnd and BitmapOr nodes, using two indexes on one table in a single query — something a plain index scan can’t do — and because it knows its target pages in advance, the Bitmap Heap Scan is the one plan node that does its own prefetching, issuing effective_io_concurrency reads ahead (the parameter from its own post).
So the planner chooses between them largely on row count. Few rows: a plain index scan, with its cheap handful of heap jumps. Many rows: a bitmap scan, paying to build a bitmap in exchange for orderly single-visit heap access. Very many rows — a large fraction of the table — and both lose to a sequential scan, which is enable_seqscan’s department. The two parameters here let you push the planner off whichever choice it made and see what the alternative costs.
One wrinkle on enable_indexscan: turning it off also disables index-only scans, the variant where a covering index satisfies the query entirely from the index without touching the heap at all. They’re governed by the same switch, so enable_indexscan = off removes both the plain index scan and the index-only scan from the planner’s menu in one move.
Symptoms that warrant reaching for them
The diagnostic situations are fairly distinct.
Reach for enable_indexscan = off when the planner chose a plain index scan and the query is slower than you think it should be, and you suspect the row count is high enough that a bitmap scan — or even a sequential scan — would have been the better call. The tell is an Index Scan in EXPLAIN ANALYZE whose actual rows is large, often with the query spending its time on heap access. Switch index scans off, re-run, and see whether the planner’s fallback (usually a bitmap scan) is actually faster; if it is, the question becomes why the planner mispriced the index scan, and the answer is frequently a random_page_cost set too low for your storage or stale statistics making the planner underestimate the row count. The experiment tells you the plan was wrong; the cost settings tell you the reason.
Reach for enable_bitmapscan = off in the mirror situation: a Bitmap Heap Scan that’s underperforming, where you want to know if a plain index scan or a sequential scan would do better. Two specific tells live in the bitmap plan’s own EXPLAIN ANALYZE output. Rows Removed by Index Recheck with a large count means the bitmap grew too big for work_mem and went lossy — degrading from “exact row locations” to “these pages might contain matches,” forcing a recheck of every tuple on those pages — which is a sign you’re either selecting too much for a bitmap scan to be worthwhile or starving it of work_mem. And Heap Blocks: lossy= with a high number is the same story from the other side. Switching bitmap scans off lets you measure the alternatives; seeing the lossy counts tells you the bitmap scan was straining in the first place.
In both cases the discipline is identical and bears repeating once for the pair: the SET ... = off is a probe, run in a session you will close, whose purpose is to reveal the planner’s reasoning so you can fix the actual cause — statistics, random_page_cost, work_mem, effective_cache_size. Leaving either parameter off in your configuration to force a plan is treating the symptom and lying to the planner about what plans exist; the next query that genuinely needed the disabled access method will pay for it. Diagnose, learn the cause, set the parameters back, and fix the thing the probe pointed at.