Covering But Still Slow

To kill the heap fetches on a hot query, an engineer builds a covering index:

CREATE INDEX orders_user_covering ON orders (user_id) INCLUDE (status, total_cents);

The plan now says Index Only Scan, but latency barely moved:

 Index Only Scan using orders_user_covering on orders
   (cost=0.43..1180.22 rows=3910 width=12)
   (actual time=0.041..214.883 rows=3908.00 loops=1)
   Index Cond: (user_id = 91733)
   Heap Fetches: 3871
   Buffers: shared hit=2104 read=1802

What is Heap Fetches: 3871 telling you?

A

3,871 of the 3,908 rows are dead tuples left by earlier updates, and the scan had to fetch each one from the heap and discard it.

B

Nearly every matching row sits on a heap page whose visibility-map bit is clear, so the scan has to visit the heap to decide visibility.

C

INCLUDE columns live only in the index's internal pages, so status and total_cents still have to be read from the heap.

D

The index is not really covering: for an index-only scan, status and total_cents have to be key columns, not payload columns.