Thời gian truy vấn chậm cho các tìm kiếm tương tự với các chỉ số pg_trgm


9

Chúng tôi đã thêm hai chỉ số pg_trgm vào một bảng để cho phép tìm kiếm mờ theo địa chỉ email hoặc tên, vì chúng tôi cần tìm người dùng theo tên hoặc địa chỉ email đã bị sai chính tả trong quá trình đăng ký (ví dụ: "@ gmail.con"). ANALYZEđược chạy sau khi tạo chỉ mục.

Tuy nhiên, thực hiện tìm kiếm được xếp hạng trên một trong hai chỉ số này là rất chậm trong phần lớn các trường hợp. tức là với thời gian chờ tăng lên, một truy vấn có thể trở lại sau 60 giây, trong những trường hợp rất hiếm có nhanh như 15 giây, nhưng thông thường các truy vấn sẽ hết thời gian.

pg_trgm.similarity_thresholdlà giá trị mặc định của 0.3, nhưng việc tăng giá trị này 0.8dường như không tạo ra sự khác biệt.

Bảng đặc biệt này có hơn 25 triệu hàng và liên tục được truy vấn, cập nhật và chèn vào (thời gian trung bình cho mỗi hàng là dưới 2ms). Thiết lập là PostgreSQL 9.6.6 chạy trên phiên bản RDS db.m4.large với bộ lưu trữ SSD cho mục đích chung và các tham số mặc định nhiều hơn hoặc ít hơn. Phần mở rộng pg_trgm là phiên bản 1.3.

Truy vấn:

  • 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;
    

Các truy vấn này không cần phải chạy thường xuyên (hàng chục lần một ngày), nhưng chúng nên được dựa trên trạng thái bảng hiện tại và lý tưởng trở lại trong vòng khoảng 10 giây.


Lược đồ:

=> \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

(Tôi biết rằng có lẽ chúng ta cũng nên thêm unaccent()vào users_search_name_idxvà truy vấn tên tên)


Giải thích:

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

Tìm kiếm email có nhiều thời gian hơn tìm kiếm tên, nhưng điều đó có lẽ là do các địa chỉ email rất giống nhau (ví dụ: rất nhiều địa chỉ @ 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

Điều gì có thể là một lý do cho thời gian truy vấn chậm? Một cái gì đó để làm với số lượng bộ đệm được đọc? Tôi không thể tìm thấy nhiều thông tin về việc tối ưu hóa loại truy vấn cụ thể này và các truy vấn rất giống với các truy vấn trong tài liệu pg_trgm.

Đây có phải là thứ gì đó mà chúng ta có thể tối ưu hóa, hoặc triển khai tốt hơn trong Postgres, hoặc sẽ tìm kiếm thứ gì đó như Elaticsearch phù hợp hơn cho trường hợp sử dụng cụ thể này?


1
Là phiên bản của bạn pg_trgmít nhất 1.3? Bạn có thể kiểm tra với "\ dx" trong psql.
jjanes

Bạn đã có thể sao chép bất kỳ truy vấn top-n nào được xếp hạng bằng cách sử dụng <->toán tử sử dụng một chỉ mục?
Colin 't Hart

Giả sử rằng các cài đặt là mặc định tôi sẽ chơi với ngưỡng tương tự. Bằng cách đó bạn có thể nhận được kết quả nhỏ hơn, vì vậy có thể chi phí chung có thể giảm ...
Michał Zaborowski

@jjanes Cảm ơn con trỏ. Có, phiên bản là 1.3.
Christopher Orr

1
@ MichałZaborowski Như đã đề cập trong câu hỏi, tôi đã thử điều đó, nhưng tiếc là không thấy sự cải thiện nào.
Christopher Orr

Câu trả lời:


1

Bạn có thể có thể có được hiệu suất tốt hơn với gin_trgm_opshơn gist_trgm_ops. Cái nào tốt hơn là không thể đoán trước, nó nhạy cảm với việc phân phối các mẫu văn bản và độ dài trong dữ liệu của bạn và trong các thuật ngữ truy vấn của bạn. Bạn khá nhiều chỉ cần thử nó và xem nó hoạt động như thế nào cho bạn. Một điều là phương thức GIN sẽ khá nhạy cảm pg_trgm.similarity_threshold, không giống như phương pháp GiST. Nó cũng sẽ phụ thuộc vào phiên bản pg_trgm mà bạn có. Nếu bạn đã bắt đầu với phiên bản cũ hơn của PostgreSQL nhưng đã cập nhật nó pg_upgrade, bạn có thể không có phiên bản mới nhất. Công cụ lập kế hoạch không tốt hơn trong việc dự đoán loại chỉ số nào tốt hơn chúng ta có thể làm. Vì vậy, để kiểm tra nó, bạn không thể tạo cả hai, bạn phải bỏ cái kia, để buộc người lập kế hoạch sử dụng cái bạn muốn.

Trong trường hợp cụ thể của cột email, bạn có thể tốt hơn nên chia chúng thành tên người dùng và tên miền, sau đó truy vấn tên người dùng tương tự với tên miền chính xác và ngược lại. Sau đó, mức độ phổ biến cực cao của các nhà cung cấp email đám mây lớn ít có khả năng gây ô nhiễm các chỉ mục bằng bát quái có thêm ít thông tin.

Cuối cùng trường hợp sử dụng cho việc này là gì? Biết lý do tại sao bạn cần chạy các truy vấn này có thể dẫn đến các đề xuất tốt hơn. Cụ thể, tại sao bạn cần thực hiện tìm kiếm tương tự trên email, một khi chúng đã được xác minh là có thể gửi được và đến đúng người? Có lẽ bạn có thể xây dựng một chỉ mục một phần chỉ trên tập hợp con các email chưa được xác minh?


Cảm ơn bạn về thông tin. Thay vào đó, tôi sẽ thử chỉ số GIN và chơi với ngưỡng. Ngoài ra, vâng, đó là một điểm tuyệt vời về việc có một chỉ mục một phần cho các địa chỉ chưa được xác minh. Tuy nhiên, ngay cả đối với các địa chỉ email được xác minh, có thể có các kết quả mờ cần thiết (ví dụ: mọi người quên các dấu chấm trong địa chỉ @ gmail.com), nhưng đó có thể là trường hợp có một bảng riêng biệt với các cột miền và phần cục bộ được chuẩn hóa, như bạn đã đề cập.
Christopher Orr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.