Мы добавили два индекса pg_trgm в таблицу, чтобы включить нечеткий поиск по адресу электронной почты или имени, так как нам нужно найти пользователей по имени или адресам электронной почты, которые были написаны с ошибками при регистрации (например, «@ gmail.con»). ANALYZE
был запущен после создания индекса.
Однако выполнение ранжированного поиска по любому из этих индексов в большинстве случаев происходит очень медленно. то есть с увеличенным временем ожидания, запрос может вернуться через 60 секунд, в очень редких случаях - до 15 секунд, но обычно запросы будут задерживаться.
pg_trgm.similarity_threshold
значение по умолчанию 0.3
, но увеличение 0.8
этого значения, похоже, не имеет значения.
Эта конкретная таблица содержит более 25 миллионов строк и постоянно запрашивается, обновляется и вставляется в нее (среднее время для каждой из них составляет менее 2 мс). Это PostgreSQL 9.6.6, работающий на экземпляре RDS db.m4.large с общим хранилищем SSD и более или менее стандартными параметрами. Расширение pg_trgm - версия 1.3.
Запросы:
SELECT * FROM users WHERE email % 'chris@example.com' ORDER BY email <-> 'chris@example.com' LIMIT 10;
SELECT * FROM users WHERE (first_name || ' ' || last_name) % 'chris orr' ORDER BY (first_name || ' ' || last_name) <-> 'chris orr' LIMIT 10;
Эти запросы не нужно выполнять очень часто (десятки раз в день), но они должны основываться на текущем состоянии таблицы и в идеале возвращаться в течение 10 секунд.
Схема:
=> \d+ users
Table "public.users"
Column | Type | Collation | Nullable | Default | Storage
-------------------+-----------------------------+-----------+----------+---------+----------
id | uuid | | not null | | plain
email | citext | | not null | | extended
email_is_verified | boolean | | not null | | plain
first_name | text | | not null | | extended
last_name | text | | not null | | extended
created_at | timestamp without time zone | | | now() | plain
updated_at | timestamp without time zone | | | now() | plain
… | boolean | | not null | false | plain
… | character varying(60) | | | | extended
… | character varying(6) | | | | extended
… | character varying(6) | | | | extended
… | boolean | | | | plain
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE, btree (email)
"users_search_email_idx" gist (email gist_trgm_ops)
"users_search_name_idx" gist (((first_name || ' '::text) || last_name) gist_trgm_ops)
"users_updated_at_idx" btree (updated_at)
Triggers:
update_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE update_modified_column()
Options: autovacuum_analyze_scale_factor=0.01, autovacuum_vacuum_scale_factor=0.05
(Я знаю , что мы , вероятно , следует добавить unaccent()
к users_search_name_idx
и запрос имени ...)
Объясняет:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE (first_name || ' ' || last_name) % 'chris orr' ORDER BY (first_name || ' ' || last_name) <-> 'chris orr' LIMIT 10;
:
Limit (cost=0.42..40.28 rows=10 width=152) (actual time=58671.973..58676.193 rows=10 loops=1)
Buffers: shared hit=66227 read=231821
-> Index Scan using users_search_name_idx on users (cost=0.42..100264.13 rows=25153 width=152) (actual time=58671.970..58676.180 rows=10 loops=1)
Index Cond: (((first_name || ' '::text) || last_name) % 'chris orr'::text)
Order By: (((first_name || ' '::text) || last_name) <-> 'chris orr'::text"
Buffers: shared hit=66227 read=231821
Planning time: 0.125 ms
Execution time: 58676.265 ms
Поиск по электронной почте, скорее всего, будет более длительным, чем поиск по имени, но, вероятно, это связано с тем, что адреса электронной почты очень похожи (например, множество адресов @ gmail.com).
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email % 'chris@example.com' ORDER BY email <-> 'chris@example.com' LIMIT 10;
:
Limit (cost=0.42..40.43 rows=10 width=152) (actual time=58851.719..62181.128 rows=10 loops=1)
Buffers: shared hit=83 read=428918
-> Index Scan using users_search_email_idx on users (cost=0.42..100646.36 rows=25153 width=152) (actual time=58851.716..62181.113 rows=10 loops=1)
Index Cond: ((email)::text % 'chris@example.com'::text)
Order By: ((email)::text <-> 'chris@example.com'::text)
Buffers: shared hit=83 read=428918
Planning time: 0.100 ms
Execution time: 62181.186 ms
Что может быть причиной медленного времени запроса? Что-то делать с количеством читаемых буферов? Я не смог найти много информации об оптимизации этого конкретного типа запросов, и запросы в любом случае очень похожи на те, что описаны в документации по pg_trgm.
Это то, что мы могли бы оптимизировать или лучше реализовать в Postgres, или мы хотели бы, чтобы что-то вроде Elasticsearch лучше подходило для этого конкретного варианта использования?
<->
оператора, который использует индекс?
pg_trgm
хотя бы 1.3? Вы можете проверить с "\ dx" вpsql
.