Hi PostgreSQL users, I was looking at a slow query in our CMDB that using postgresql-12.3 as its backend. I since I am using the pg_trgm extension elsewhere I decided to give it a try. To my surprise, the query plan did not change. But when I disabled the index scan I got the much, much faster scan using a bitmap index scan. Any ideas about why that is being chosen? Here are the details: shared_buffers = 12GB work_mem = 128MB maintenance_work_mem = 2GB effective_io_concurrency = 200 max_worker_processes = 24 max_parallel_maintenance_workers = 4 max_parallel_workers_per_gather = 4 max_parallel_workers = 24 random_page_cost = 1.1 seq_page_cost = 1.0 effective_cache_size = 36GB default_statistics_target = 500 from_collapse_limit = 30 join_collapse_limit = 30 Slow version with index scan: # explain analyze SELECT DISTINCT main.* FROM Articles main JOIN ObjectCustomFieldValues ObjectCustomFieldValues_1 ON ( ObjectCustomFieldValues_1.Disabled = '0' ) AND ( ObjectCustomFieldValues_1.ObjectId = main.id ) WHERE (ObjectCustomFieldValues_1.LargeContent ILIKE '%958575%' OR ObjectCustomFieldValues_1.Content ILIKE '%958575%') AND (main.Disabled = '0') ORDER BY main.SortOrder ASC, main.Name ASC; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Unique (cost=892.65..892.68 rows=1 width=137) (actual time=21165.464..21165.464 rows=0 loops=1) -> Sort (cost=892.65..892.66 rows=1 width=137) (actual time=21165.462..21165.462 rows=0 loops=1) Sort Key: main.sortorder, main.name, main.id, main.summary, main.class, main.parent, main.uri, main.creator, main.created, main.lastupdatedby, main.lastupdated Sort Method: quicksort Memory: 25kB -> Merge Join (cost=0.71..892.64 rows=1 width=137) (actual time=21165.453..21165.453 rows=0 loops=1) Merge Cond: (main.id = objectcustomfieldvalues_1.objectid) -> Index Scan using articles_pkey on articles main (cost=0.14..9.08 rows=142 width=137) (actual time=0.007..0.007 rows=1 loops=1) Filter: (disabled = '0'::smallint) -> Index Scan using objectcustomfieldvalues3 on objectcustomfieldvalues objectcustomfieldvalues_1 (cost=0.56..807603.40 rows=915 width=4) (actual time=21165.441..21165.441 rows=0 loops=1) Filter: ((disabled = 0) AND ((largecontent ~~* '%958575%'::text) OR ((content)::text ~~* '%958575%'::text))) Rows Removed by Filter: 19030904 Planning Time: 1.198 ms Execution Time: 21165.552 ms (13 rows) Time: 21167.239 ms (00:21.167) Fast version with enable_indexscan = 0: # explain analyze SELECT DISTINCT main.* FROM Articles main JOIN ObjectCustomFieldValues ObjectCustomFieldValues_1 ON ( ObjectCustomFieldValues_1.Disabled = '0' ) AND ( ObjectCustomFieldValues_1.ObjectId = main.id ) WHERE (ObjectCustomFieldValues_1.LargeContent ILIKE '%958575%' OR ObjectCustomFieldValues_1.Content ILIKE '%958575%') AND (main.Disabled = '0') ORDER BY main.SortOrder ASC, main.Name ASC; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Unique (cost=1315.42..1315.45 rows=1 width=137) (actual time=0.306..0.306 rows=0 loops=1) -> Sort (cost=1315.42..1315.43 rows=1 width=137) (actual time=0.305..0.305 rows=0 loops=1) Sort Key: main.sortorder, main.name, main.id, main.summary, main.class, main.parent, main.uri, main.creator, main.created, main.lastupdatedby, main.lastupdated Sort Method: quicksort Memory: 25kB -> Hash Join (cost=52.89..1315.41 rows=1 width=137) (actual time=0.296..0.297 rows=0 loops=1) Hash Cond: (objectcustomfieldvalues_1.objectid = main.id) -> Bitmap Heap Scan on objectcustomfieldvalues objectcustomfieldvalues_1 (cost=45.27..1305.40 rows=915 width=4) (actual time=0.213..0.213 rows=0 loops=1) Recheck Cond: ((largecontent ~~* '%958575%'::text) OR ((content)::text ~~* '%958575%'::text)) Filter: (disabled = 0) -> BitmapOr (cost=45.27..45.27 rows=1136 width=0) (actual time=0.210..0.210 rows=0 loops=1) -> Bitmap Index Scan on objectcustomfieldvalues_largecontent_trgm (cost=0.00..15.40 rows=1 width=0) (actual time=0.041..0.041 rows=0 loops=1) Index Cond: (largecontent ~~* '%958575%'::text) -> Bitmap Index Scan on objectcustomfieldvalues_content_trgm (cost=0.00..29.41 rows=1135 width=0) (actual time=0.168..0.168 rows=0 loops=1) Index Cond: ((content)::text ~~* '%958575%'::text) -> Hash (cost=5.84..5.84 rows=142 width=137) (actual time=0.079..0.079 rows=146 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 34kB -> Seq Scan on articles main (cost=0.00..5.84 rows=142 width=137) (actual time=0.010..0.053 rows=146 loops=1) Filter: (disabled = '0'::smallint) Rows Removed by Filter: 5 Planning Time: 1.308 ms Execution Time: 0.356 ms (21 rows) Time: 2.113 ms And the schema information: # \d articles Table "public.articles" Column | Type | Collation | Nullable | Default ---------------+-----------------------------+-----------+----------+-------------------------------------- id | integer | | not null | nextval('articles_id_seq'::regclass) name | character varying(255) | | not null | ''::character varying summary | character varying(255) | | not null | ''::character varying sortorder | integer | | not null | 0 class | integer | | not null | 0 parent | integer | | not null | 0 uri | character varying(255) | | | creator | integer | | not null | 0 created | timestamp without time zone | | | lastupdatedby | integer | | not null | 0 lastupdated | timestamp without time zone | | | disabled | smallint | | not null | 0 Indexes: "articles_pkey" PRIMARY KEY, btree (id) # \d objectcustomfieldvalues Table "public.objectcustomfieldvalues" Column | Type | Collation | Nullable | Default -----------------+-----------------------------+-----------+----------+--------------------------------------------------------- id | integer | | not null | nextval('ticketcustomfieldvalues_id_s'::text::regclass) objectid | integer | | not null | customfield | integer | | not null | content | character varying(255) | | | creator | integer | | not null | 0 created | timestamp without time zone | | | lastupdatedby | integer | | not null | 0 lastupdated | timestamp without time zone | | | objecttype | character varying(255) | | not null | largecontent | text | | | contenttype | character varying(80) | | | contentencoding | character varying(80) | | | sortorder | integer | | not null | 0 disabled | integer | | not null | 0 Indexes: "ticketcustomfieldvalues_pkey" PRIMARY KEY, btree (id) CLUSTER "objectcustomfieldvalues1" btree (customfield, objecttype, objectid, content) "objectcustomfieldvalues2" btree (customfield, objecttype, objectid) "objectcustomfieldvalues3" btree (objectid, objecttype) "objectcustomfieldvalues4" btree (id) WHERE id IS NULL OR id = 0 "objectcustomfieldvalues_content_trgm" gin (content gin_trgm_ops) "objectcustomfieldvalues_largecontent_trgm" gin (largecontent gin_trgm_ops) "ticketcustomfieldvalues1" btree (customfield, objectid, content) Any suggestions would be appreciated. Regards, Ken