Back to blog
Mar 17, 2024
3 min read

A better way to search arrays in Postgres

Learn how to efficiently search arrays in PostgreSQL using the ANY operator and GIN indexes for better performance
#PostgreSQL #Database #Performance #Tutorial
Share this article

Postgres Elephant

Table of Contents

  1. Example
  2. No Indexes
  3. Index on org_id
  4. Can we do better? Enter Gin Index on tag_ids
  5. Can we do even better? Enter Partial Index
  6. Enter btree_gin extension
  7. Summary
  8. Improvements

Example

Assume resources Expense, Tag, and Org.

  • 5M Expenses
  • 10 Orgs
  • 2K Tags
create table expenses(
id serial primary key,
org_id integer not null,
tag_ids integer[] not null
);
insert into expenses
select
id,
1+id%9 org_id,
gi.user_ids
from
generate_series(1, 5000000) id
join lateral (
select
i%5000000 pid,
array_agg(round(random() * 2000)) user_ids
from generate_series(1, 5.5*5000000) i
group by i%5000000
) gi on pid=id
;

Roughly every expense can have 5 tags. We need to be able to list all expenses in an org with a certain tag.

The org_id filter in most cases comes from access layers where a user with a token can access the data of one org only for which the session is active.

select
*
from
expenses ex
where
ex.org_id=1
and ex.tag_ids @> '{7}'
;

Note: @> is ‘contains’ operator Postgresql: functions-array

No Indexes

Execution Time: 1682.684 ms

Index on org_id

create index on expenses(org_id);
Execution Time: 228.352 ms

Improvement of 7.3x

Can we do better? Enter Gin Index on tag_ids

create index on expenses(org_id);
create index on expenses using gin (tag_ids);
Execution Time: 43.473 ms

Improvement of another 5.2x

Can we do even better? Enter Partial Index

create index on expenses using gin (org_id, tag_ids);
ERROR: data type integer has no default operator class for access method "gin"

Enter btree_gin extension

create extension btree_gin;
create index on expenses using gin (org_id, tag_ids);
Execution Time: 7.293 ms

Improvement of another 5.9x

Summary

Time: 1682.68 -> 228.35 -> 43.47 -> 7.29ms

Improvements

  • total: 230x
  • from just org_id index: 31x

Postgres provides you with an extension out of the box called ‘btree_gin’.

btree_gin provides sample GIN operator classes that implement B-tree equivalent behavior for simple non-composite data types.

References: