Мы добавили два индекса 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
.<->
оператора, который использует индекс?Ответы:
Вы могли бы быть в состоянии получить лучшую производительность,
gin_trgm_ops
а неgist_trgm_ops
. Что лучше, это довольно непредсказуемо, оно чувствительно к распределению текстовых шаблонов и длины в ваших данных и в ваших условиях запроса. Вы просто должны попробовать это и посмотреть, как это работает для вас. Одна вещь заключается в том, что метод GIN будет весьма чувствительнымpg_trgm.similarity_threshold
, в отличие от метода GiST. Это также будет зависеть от того, какая у вас версия pg_trgm. Если вы начали со старой версии PostgreSQL, но обновили ееpg_upgrade
, возможно, у вас не самая последняя версия. Планировщик не лучше предсказывает, какой тип индекса лучше, чем мы. Таким образом, чтобы проверить это, вы не можете просто создать оба, вы должны отбросить другой, чтобы заставить планировщика использовать тот, который вы хотите.В конкретном случае столбца электронной почты может быть лучше разделить их на имя пользователя и домен, а затем запросить аналогичное имя пользователя с точным доменом и наоборот. Тогда крайняя распространенность основных поставщиков облачной электронной почты с меньшей вероятностью будет загрязнять индексы триграммами, которые добавляют мало информации.
Наконец, каков вариант использования для этого? Знание, почему вам нужно выполнить эти запросы, может привести к лучшим предложениям. В частности, зачем вам нужно выполнять поиск сходства по электронным письмам, если они были проверены на предмет доставки и отправки нужному человеку? Возможно, вы могли бы построить частичный индекс только для тех электронных писем, которые еще не были проверены?
источник