Kami menambahkan dua indeks pg_trgm ke tabel, untuk mengaktifkan pencarian fuzzy baik dengan alamat email atau nama, karena kami perlu menemukan pengguna berdasarkan nama, atau alamat email yang salah eja saat mendaftar (mis. "@ Gmail.con"). ANALYZE
dijalankan setelah pembuatan indeks.
Namun, melakukan pencarian peringkat pada salah satu indeks ini sangat lambat dalam sebagian besar kasus. yaitu dengan peningkatan batas waktu, permintaan mungkin kembali dalam 60 detik, pada kesempatan yang sangat jarang secepat 15 detik, tetapi biasanya permintaan akan habis waktu.
pg_trgm.similarity_threshold
adalah nilai default dari 0.3
, tetapi menabrak ini 0.8
tampaknya tidak membuat perbedaan.
Tabel khusus ini memiliki lebih dari 25 juta baris, dan terus-menerus ditanyai, diperbarui, dan dimasukkan ke dalam (waktu rata-rata untuk masing-masing adalah di bawah 2ms). Penyiapannya adalah PostgreSQL 9.6.6 yang berjalan pada instance RDS db.m4.large dengan penyimpanan SSD tujuan umum, dan parameter default lebih banyak atau kurang. Ekstensi pg_trgm adalah versi 1.3.
Pertanyaan:
SELECT * FROM users WHERE email % '[email protected]' ORDER BY email <-> '[email protected]' LIMIT 10;
SELECT * FROM users WHERE (first_name || ' ' || last_name) % 'chris orr' ORDER BY (first_name || ' ' || last_name) <-> 'chris orr' LIMIT 10;
Kueri ini tidak perlu dijalankan sangat sering (puluhan kali sehari), tetapi harus didasarkan pada kondisi tabel saat ini, dan idealnya kembali dalam waktu sekitar 10 detik.
Skema:
=> \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
(Saya sadar bahwa kita harus mungkin juga menambahkan unaccent()
untuk users_search_name_idx
dan query nama ...)
Menjelaskan:
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
Pencarian email lebih cenderung ke waktu daripada pencarian nama, tetapi itu mungkin karena alamat email sangat mirip (misalnya banyak alamat @ gmail.com).
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email % '[email protected]' ORDER BY email <-> '[email protected]' 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 % '[email protected]'::text)
Order By: ((email)::text <-> '[email protected]'::text)
Buffers: shared hit=83 read=428918
Planning time: 0.100 ms
Execution time: 62181.186 ms
Apa yang bisa menjadi alasan untuk waktu permintaan yang lambat? Ada hubungannya dengan jumlah buffer yang dibaca? Saya tidak dapat menemukan banyak informasi tentang cara mengoptimalkan jenis permintaan ini, dan pertanyaannya sangat mirip dengan yang ada dalam dokumentasi pg_trgm.
Apakah ini sesuatu yang bisa kami optimalkan, atau terapkan lebih baik di Postgres, atau ingin sesuatu seperti Elasticsearch lebih cocok untuk kasus penggunaan khusus ini?
sumber
pg_trgm
setidaknya 1,3? Anda dapat memeriksa dengan "\ dx" dipsql
.<->
operator yang menggunakan indeks?Jawaban:
Anda mungkin bisa mendapatkan kinerja yang lebih baik
gin_trgm_ops
daripadagist_trgm_ops
. Mana yang lebih baik sangat tidak terduga, sensitif terhadap distribusi pola dan panjang teks dalam data Anda dan dalam istilah kueri Anda. Anda cukup banyak hanya harus mencobanya dan melihat cara kerjanya untuk Anda. Satu hal adalah bahwa metode GIN akan sangat sensitifpg_trgm.similarity_threshold
, tidak seperti metode GiST. Ini juga akan tergantung pada versi pg_trgm apa yang Anda miliki. Jika Anda mulai dengan PostgreSQL versi lama tetapi memperbaruinya denganpg_upgrade
, Anda mungkin tidak memiliki versi terbaru. Perencana tidak lebih baik dalam memprediksi tipe indeks mana yang lebih unggul daripada yang dapat kita lakukan. Jadi untuk mengujinya, Anda tidak bisa hanya membuat keduanya, Anda harus menjatuhkan yang lain, untuk memaksa perencana untuk menggunakan yang Anda inginkan.Dalam kasus khusus kolom email, Anda mungkin lebih baik membaginya menjadi nama pengguna dan domain, dan kemudian meminta nama pengguna yang mirip dengan domain yang tepat dan sebaliknya. Kemudian prevalensi ekstrim dari penyedia surel cloud besar kurang cenderung mencemari indeks dengan trigram yang menambahkan sedikit informasi.
Akhirnya, apa gunanya kasus ini? Mengetahui mengapa Anda perlu menjalankan kueri ini dapat menghasilkan saran yang lebih baik. Khususnya, mengapa Anda perlu melakukan pencarian kesamaan pada email, setelah mereka diverifikasi sebagai pengiriman dan pergi ke orang yang tepat? Mungkin Anda bisa membuat indeks parsial hanya pada himpunan bagian dari email yang belum diverifikasi?
sumber