When GIN indexes are used to search with very long keyword lists, performance degrades significantly. This article explains why GIN index keyword search has O(n^2) time complexity.
Here is the detail of why that query have O(N^2) inside GIN implementation.
Details
Inspect the index example_keys_idx
postgres=# select oid,* from pg_class where relname = 'example_keys_idx';-[ RECORD 1]-------+-----------------
oid |20699relname | example_keys_idx
relnamespace |20692reltype |0reloftype |0relowner |10relam |2742relfilenode |20699reltablespace |0relpages |2051reltuples |300000relallvisible |0reltoastrelid |0relhasindex | f
relisshared | f
relpersistence | p
relkind | i
relnatts |1relchecks |0relhasoids | f
relhasrules | f
relhastriggers | f
relhassubclass | f
relrowsecurity | f
relforcerowsecurity | f
relispopulated | t
relreplident | n
relispartition | f
relrewrite |0relfrozenxid |0relminmxid |0relacl |reloptions |{fastupdate=off}relpartbound |
Find index information via index’s oid
postgres=# select * from pg_index where indexrelid = 20699;-[ RECORD 1]--+------
indexrelid |20699indrelid |20693indnatts |1indnkeyatts |1indisunique | f
indisprimary | f
indisexclusion | f
indimmediate | t
indisclustered | f
indisvalid | t
indcheckxmin | f
indisready | t
indislive | t
indisreplident | f
indkey |2indcollation |0indclass |10075indoption |0indexprs |indpred |
Find corresponding operator class for that index via indclass
postgres=# select * from pg_opclass where oid = 10075;-[ RECORD 1]+----------
opcmethod |2742opcname | array_ops
opcnamespace |11opcowner |10opcfamily |2745opcintype |2277opcdefault | t
opckeytype |2283
Find four operator corresponding to operator family array_ops
Line 4177, we see a nested loop to iterate two array, which makes it O(N^2)
for(i=0;i<nelems1;i++){Datumelt1;boolisnull1;/* Get element, checking for NULL */elt1=array_iter_next(&it1,&isnull1,i,typlen,typbyval,typalign);/*
* We assume that the comparison operator is strict, so a NULL can't
* match anything. XXX this diverges from the "NULL=NULL" behavior of
* array_eq, should we act like that?
*/if(isnull1){if(matchall){result=false;break;}continue;}for(j=0;j<nelems2;j++)