Question on GIN index on array.
I have 2 million rows in a work table.And I need to find work that a user can do, based on the skills he has and also the highest priority work. User always will have more skills.
Started with RDMS standard way of doing this, but performance of the queries is bad, so in search for other options found postgres supports arrays containment queries and Also arrays can be indexed.
Table:
CREATE TABLE
work
(
work_id TEXT DEFAULT nextval('work_id_seq'::regclass) NOT NULL,
priority_score BIGINT NOT NULL,
work_data JSONB,
created_date TIMESTAMP(6) WITHOUT TIME ZONE NOT NULL,
current_status CHARACTER VARYING,
PRIMARY KEY (work_id)
);
Index:
CREATE INDEX test_gin_1 ON work USING gin(jsonarray2intarray((work_data ->> 'skills'::text);
Function:
CREATE OR REPLACE FUNCTION jsonarray2intarray" (text) RETURNS integer[]
IMMUTABLE
AS $body$
SELECT translate($1, '[]', '{}')::int[]
$body$ LANGUAGE sql
Sample data:
282941 1564 {"skills": [213, 311, 374, 554]}
With the below query the response is slow. There is only one record with 254,336,391,485 as skill array
with T as (
SELECT work_id,
priority_score,
current_status,
work_data
FROM work
WHERE jsonarray2intarray( work.work_data ->> 'skills') <@ '{254,336,391,485 }'
AND work.current_status = 'ASSIGNABLE'
ORDER BY priority_score DESC, created_date )
select * from t LIMIT 1 FOR UPDATE skip locked
Limit (cost=45095.54..45095.56 rows=1 width=296) (actual time=3776.169..3776.170 rows=1 loops=1)
Output: t.work_id,t.priority_score, t.current_status,t.work_data
CTE t
-> Sort (cost=45059.29..45095.54 rows=14503 width=325) (actual time=3776.166..3776.166 rows=1 loops=1)
Output: work.work_id,work.priority_score, work.current_status,work.work_data
Sort Key: work.priority_score DESC, work.created_date
Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on work (cost=524.44..41872.83 rows=14503 width=325) (actual time=37.718..3776.159 rows=1 loops=1)
Output: work.work_id,work.priority_score, work.current_status,work.work_data
Recheck Cond: (jsonarray2intarray((work.work_data ->> 'skills'::text)) <@ '{254,336,391,485}'::integer[])
Rows Removed by Index Recheck: 1072296
Filter: ((work.current_status)::text = 'ASSIGNABLE'::text)
Heap Blocks: exact=41243 lossy=26451
-> Bitmap Index Scan on test_gin_1 (cost=0.00..520.81 rows=14509 width=0) (actual time=30.699..30.699 rows=154888 loops=1)
Index Cond: (jsonarray2intarray((work.work_data ->> 'skills'::text)) <@ '{254,336,391,485}'::integer[])
-> CTE Scan on t (cost=0.00..290.06 rows=14503 width=296) (actual time=3776.168..3776.168 rows=1 loops=1)
Output: t.work_id,t.priority_score, t.current_status,t.work_data
Planning time: 0.161 ms
Execution time: 3776.202 ms
Same query with different input is fast. There are around 26K records with skill 101, 103:
with T as (
SELECT work_id,
priority_score,
current_status,
work_data
FROM work
WHERE jsonarray2intarray( work.work_data ->> 'skills') <@ '{101, 103 }'
AND work.current_status = 'ASSIGNABLE'
ORDER BY priority_score DESC, created_date )
select * from t LIMIT 1 FOR UPDATE skip locked
Limit (cost=45076.55..45076.57 rows=1 width=296) (actual time=116.185..116.186 rows=1 loops=1)
Output: t.work_id,t.priority_score, t.current_status,t.work_data
CTE t
-> Sort (cost=45040.26..45076.55 rows=14513 width=325) (actual time=116.182..116.182 rows=1 loops=1)
Output: work.work_id,work.priority_score, work.current_status,work.work_data
Sort Key: work.priority_score DESC, work.created_date
Sort Method: external merge Disk: 8088kB
-> Bitmap Heap Scan on work (cost=476.52..41853.05 rows=14513 width=325) (actual time=9.223..94.591 rows=26301 loops=1)
Output: work.work_id,work.priority_score, work.current_status,work.work_data
Recheck Cond: (jsonarray2intarray((workd.work_data ->> 'skills'::text)) <@ '{101,103}'::integer[])
Filter: ((workd.current_status)::text = 'ASSIGNABLE'::text)
Rows Removed by Filter: 1357
Heap Blocks: exact=2317
-> Bitmap Index Scan on test_gin_1 (cost=0.00..472.89 rows=14519 width=0) (actual time=4.638..4.638 rows=39871 loops=1)
Index Cond: (jsonarray2intarray((workd.work_data ->> 'skills'::text)) <@ '{101,103}'::integer[])
-> CTE Scan on t (cost=0.00..290.26 rows=14513 width=296) (actual time=116.184..116.184 rows=1 loops=1)
Output: t.work_id,t.priority_score, t.current_status,t.work_data
Planning time: 0.160 ms
Execution time: 117.278 ms
I am looking for suggestions on getting a consistent responses.
NOTE: Approach which is not postgres specific:
The query is taking around 40 to 50 secs which is very bad
I added a new table work_data apart from the work table as defined above
CREATE TABLE work_data
(
skill_id bigint,
work_id bigint
)
Query:
select work.id
from work
inner join work_data on (work.id=work_data.work_id)
group by work.id
having sum(case when work_data.skill_id in (2269,3805,828,9127) then 0 else 1 end)=0
work
. Which is it?