I'm new to typeahead / full text searching but I'm trying to figure out an algorithm that yields the best results for my search use-case. The additional module pg_trgm
is installed. I have a table search
that looks like:
Column | Type | Modifiers
------------+-----------------------+-----------
id | character varying(36) | collate C
user_id | integer |
type | text |
search_on | text | collate C
data | json |
Indexes:
"index_searchables_on_id" UNIQUE, btree (id)
"index_searchables_on_search_on" gist (search_on gist_trgm_ops)
"index_searchables_on_type" btree (type)
"index_searchables_on_user_id" btree (user_id)
I'm querying rows using something like:
SELECT * FROM searchables
WHERE search_on % 'mysearch'
ORDER BY search_on <-> 'mysearch'
And I get ordered lists which roughly match what I want, but I have certain patterns that I'd like to match higher based on how the matching worked.
For instance, I'd like to weight prefix matches higher than the normal trigram matching algorithm. So if, for instance, the string abc
is at the beginning of my column's text, I'd want that factored into the ordering to come higher than something who might have more trigram matches on abc
but doesn't start with that string. The equivalent of doing a WHERE col like 'mysearch%'
Is there any way I can incorporate a prefix matching to alter the order clause that'd still yield an efficient query but prioritize prefixes before other matches?
Additional Info
So i've gone down the path of adding the prefix ordering, but i'm noticing significant performance issues. Real user monitoring shows some of these queries taking many seconds each (I have some that take 15+ seconds for certain terms). My examples below show queries in the 100's of ms upwards of 1s, but in my monitoring system, for a db under slight load, it's hitting 15+s for a single query.
The database itself isn't huge:
=> select count(*) from searchables;
count
--------
622398
So we're not even in the millions. search_on
is typically just company and stock names, so for example "alphabet inc. class c goog", which is a combination of the security name and ticker.
What I'm finding is that the addition of the prefix seems to add a LOT of overhead. And the speed of the queries I'm getting are much lower than I'd have expected. I realize a lot of factors go into how quickly a query returns but I'm just surprised at how slow this is. My setup is this:
Postgres: 9.6.6
AWS RDS: m4.large
500GB General Purpose SSD
Standard AWS Parameter Group
Here's a few sample EXPLAIN
s. Am I missing something here? Is 100's of ms the expected performance to search small strings over a few hundred thousand rows? It makes it pretty difficult to produce a decent user experience when you add in hte overhead of my web server + network. 300ms of a direct access query to the DB is not really cutting it for my use-case.
Query 1: Prefix Preference
EXPLAIN ANALYZE SELECT *
FROM "searchables"
WHERE (search_on % 'partners value inves')
ORDER BY search_on NOT LIKE 'partners value inves%', search_on <-> 'partners value inves' LIMIT 50;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=2243.61..2243.73 rows=50 width=241) (actual time=1497.445..1497.479 rows=50 loops=1)
-> Sort (cost=2243.61..2245.16 rows=622 width=241) (actual time=1497.443..1497.454 rows=50 loops=1)
Sort Key: ((search_on !~~ 'partners value inves%'::text)), ((search_on <-> 'partners value inves'::text))
Sort Method: top-N heapsort Memory: 38kB
-> Bitmap Heap Scan on searchables (cost=77.23..2222.94 rows=622 width=241) (actual time=340.089..1433.072 rows=100430 loops=1)
Recheck Cond: (search_on % 'partners value inves'::text)
Heap Blocks: exact=20161
-> Bitmap Index Scan on index_searchables_on_search_on (cost=0.00..77.08 rows=622 width=0) (actual time=336.245..336.245 rows=100430 loops=1)
Index Cond: (search_on % 'partners value inves'::text)
Planning time: 0.092 ms
Execution time: 1497.529 ms
Query 2 - Non prefix preference
EXPLAIN ANALYZE SELECT *
FROM "searchables"
WHERE (search_on % 'partners value inves')
ORDER BY search_on <-> 'partners value inves' LIMIT 50;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.41..204.43 rows=50 width=240) (actual time=330.725..331.394 rows=50 loops=1)
-> Index Scan using index_searchables_on_search_on on searchables (cost=0.41..2538.40 rows=622 width=240) (actual time=330.721..331.369 rows=50 loops=1)
Index Cond: (search_on % 'partners value inves'::text)
Order By: (search_on <-> 'partners value inves'::text)
Planning time: 0.092 ms
Execution time: 331.446 ms
I'm wondering if there's any tuning of RDS I should be paying attention to, or a better type of index I can use to do this prefix preferred, similarity matching for a typeahead?