Tìm kiếm toàn văn bản chậm cho các thuật ngữ có độ xuất hiện cao


8

Tôi có một bảng chứa dữ liệu được trích xuất từ ​​các tài liệu văn bản. Dữ liệu được lưu trữ trong một cột được gọi "CONTENT"mà tôi đã tạo chỉ mục này bằng GIN:

CREATE INDEX "File_contentIndex"
  ON "File"
  USING gin
  (setweight(to_tsvector('english'::regconfig
           , COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));

Tôi sử dụng truy vấn sau đây để thực hiện tìm kiếm toàn văn bản trên bảng:

SELECT "ITEMID",
  ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') , 
  plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') 
  @@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;

Bảng Tệp chứa 250 000 hàng và mỗi "CONTENT"mục bao gồm một từ ngẫu nhiên và một chuỗi văn bản giống nhau cho tất cả các hàng.

Bây giờ, khi tôi tìm kiếm một từ ngẫu nhiên (1 lần nhấn trong toàn bộ bảng), truy vấn sẽ chạy rất nhanh (<100 ms). Tuy nhiên, khi tôi tìm kiếm một từ có trong tất cả các hàng, truy vấn sẽ chạy rất chậm (10 phút trở lên).

EXPLAIN ANALYZEcho thấy đối với tìm kiếm 1 lần, Quét chỉ mục Bitmap theo sau là Quét heap Bitmap được thực hiện. Đối với tìm kiếm chậm, Seq Scan được thực hiện thay vào đó là quá trình mất nhiều thời gian.

Cấp, nó không thực tế để có cùng một dữ liệu trong tất cả các hàng. Nhưng vì tôi không thể kiểm soát các tài liệu văn bản được người dùng tải lên, cũng như các tìm kiếm họ thực hiện nên có thể xảy ra một kịch bản tương tự (tìm kiếm theo các thuật ngữ có tỷ lệ xuất hiện rất cao trong DB). Làm cách nào tôi có thể tăng hiệu suất của truy vấn tìm kiếm cho kịch bản như vậy?

Chạy PostgreSQL 9.3.4

Kế hoạch truy vấn từ EXPLAIN ANALYZE:

Tìm kiếm nhanh (1 lần truy cập trong DB)

"Limit  (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"  ->  Sort  (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
"        Sort Method: quicksort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "File"  (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
"              Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"              ->  Bitmap Index Scan on "File_contentIndex"  (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
"                    Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"

Tìm kiếm chậm (250k lượt truy cập trong DB)

"Limit  (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
"  ->  Sort  (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on "File"  (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
"              Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
"              Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"

1
Từ đỉnh đầu của tôi: Các chỉ số GIN đã nhận được những cải tiến lớn trong Postgres 9.4 (và một số chi tiết khác trong phiên bản 9.5 sắp tới). Nó chắc chắn sẽ trả tiền để nâng cấp lên 9,4 hiện tại. Và tôi cũng sẽ điều tra hiệu suất của GiST thay vì chỉ số GIN. Thủ phạm trong truy vấn của bạn là ORDER BY "RANK" DESC. Tôi sẽ điều tra pg_trgmvới chỉ số GiST và các toán tử tương tự / khoảng cách thay thế. Hãy xem xét: dba.stackexchange.com/questions/56224/ . Thậm chí có thể tạo ra kết quả "tốt hơn" (bên cạnh việc nhanh hơn).
Erwin Brandstetter

Hệ điều hành nào bạn đang chạy phiên bản PostgreSQL của bạn trên?
Kassandry

Bạn có thể lặp lại những điều này với explain (analyze, buffers), tốt nhất là với track_io_timing được đặt thành ONkhông? Không có cách nào phải mất 520 giây để quét seq bảng đó, trừ khi bạn đã lưu nó trên RAID của đĩa mềm. Một cái gì đó chắc chắn là bệnh lý ở đó. Ngoài ra, cài đặt của bạn là gì random_page_costvà các thông số chi phí khác?
jjanes

@danjo Tôi đang đối mặt với những vấn đề tương tự ngay cả khi tôi không sử dụng đặt hàng. Bạn có thể cho tôi biết làm thế nào bạn sửa nó?
Sahil Bahl

Câu trả lời:


11

Trường hợp sử dụng nghi vấn

... Mỗi mục nhập NỘI DUNG bao gồm một từ ngẫu nhiên và một chuỗi văn bản giống nhau cho tất cả các hàng.

Một chuỗi văn bản giống nhau cho tất cả các hàng chỉ là cước vận chuyển chết. Hủy bỏ nó và nối nó trong một khung nhìn nếu bạn cần hiển thị nó.

Rõ ràng, bạn nhận thức được rằng:

Cấp, nó không thực tế ... Nhưng vì tôi không thể kiểm soát văn bản ...

Nâng cấp phiên bản Postgres của bạn

Chạy PostgreSQL 9.3.4

Mặc dù vẫn còn trên Postgres 9.3, ít nhất bạn nên nâng cấp lên bản phát hành điểm mới nhất (hiện tại là 9.3.9). Các khuyến nghị chính thức của dự án:

Chúng tôi luôn khuyên tất cả người dùng chạy bản phát hành nhỏ mới nhất có sẵn cho bất kỳ phiên bản chính nào đang được sử dụng.

Tốt hơn nữa, nâng cấp lên 9,4 đã nhận được những cải tiến lớn cho các chỉ mục GIN .

Vấn đề chính 1: Dự toán chi phí

Chi phí của một số chức năng tìm kiếm văn bản đã bị đánh giá thấp nghiêm trọng lên đến và bao gồm cả phiên bản 9.4. Chi phí đó được tăng theo yếu tố 100 trong phiên bản 9.5 sắp tới như @jjanes mô tả trong câu trả lời gần đây của anh ấy:

Dưới đây là chủ đề tương ứng nơi điều này đã được thảo luậnthông điệp cam kết của Tom Lane.

Như bạn có thể thấy trong thông điệp cam kết, to_tsvector()là một trong những chức năng đó. Bạn có thể áp dụng thay đổi ngay lập tức (dưới dạng siêu người dùng):

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

mà sẽ làm cho nhiều khả năng là chỉ số chức năng của bạn được sử dụng.

Vấn đề chính 2: KNN

Vấn đề cốt lõi là Postgres phải tính thứ hạng với ts_rank()260 nghìn hàng ( rows=261011) trước khi có thể đặt hàng và chọn top 5. Điều này sẽ tốn kém , ngay cả khi bạn đã khắc phục các vấn đề khác như đã thảo luận. Đó là một vấn đề K-gần-xóm (KNN) bởi thiên nhiên và có những giải pháp đối với trường hợp có liên quan. Nhưng tôi không thể nghĩ ra một giải pháp chung cho trường hợp của bạn, vì việc tính toán thứ hạng phụ thuộc vào đầu vào của người dùng. Tôi sẽ cố gắng loại bỏ phần lớn các trận đấu xếp hạng thấp sớm để việc tính toán đầy đủ chỉ phải được thực hiện đối với một vài ứng cử viên tốt.

Một cách tôi có thể nghĩ đến là kết hợp tìm kiếm toàn văn bản của bạn với tìm kiếm tương tự trigram - cung cấp một triển khai hoạt động cho vấn đề KNN. Bằng cách này, bạn có thể chọn trước các trận đấu "tốt nhất" với LIKEvị ngữ làm ứng cử viên ( LIMIT 50ví dụ trong truy vấn phụ ) và sau đó chọn 5 hàng xếp hạng hàng đầu theo tính toán xếp hạng của bạn trong truy vấn chính.

Hoặc áp dụng cả hai vị từ trong cùng một truy vấn và chọn các kết quả khớp gần nhất theo độ tương tự của bát quái (sẽ tạo ra các kết quả khác nhau) như trong câu trả lời liên quan này:

Tôi đã làm một số nghiên cứu thêm và bạn không phải là người đầu tiên gặp phải vấn đề này. Các bài viết liên quan trên pssql-general:

Công việc đang diễn ra để cuối cùng thực hiện một tsvector <-> tsquerynhà điều hành.

Oleg Bartunov và Alexander Korotkov thậm chí đã trình bày một nguyên mẫu hoạt động (sử dụng ><làm toán tử thay vì <->trước đó) nhưng rất phức tạp để tích hợp trong Postgres, toàn bộ cơ sở hạ tầng cho các chỉ mục GIN phải được làm lại (hầu hết được thực hiện ngay bây giờ).

Vấn đề chính 3: trọng lượng và chỉ số

Và tôi đã xác định thêm một yếu tố thêm vào sự chậm chạp của truy vấn. Mỗi tài liệu:

Các chỉ mục GIN không bị mất đối với các truy vấn tiêu chuẩn, nhưng hiệu suất của chúng phụ thuộc vào logarit về số lượng từ duy nhất. ( Tuy nhiên, chỉ mục GIN chỉ lưu trữ các từ (từ vựng) của các tsvectorgiá trị chứ không phải nhãn trọng số của chúng. Do đó, cần phải kiểm tra lại hàng trong bảng khi sử dụng truy vấn có trọng số.)

Nhấn mạnh đậm của tôi. Ngay khi có trọng lượng, mỗi hàng phải được lấy từ đống (không chỉ là kiểm tra khả năng hiển thị giá rẻ) và các giá trị dài phải được khử, làm tăng thêm chi phí. Nhưng dường như có một giải pháp cho điều đó:

Định nghĩa chỉ số

Nhìn vào chỉ mục của bạn một lần nữa, dường như không có ý nghĩa gì để bắt đầu. Bạn chỉ định một trọng số cho một cột duy nhất, điều này là vô nghĩa miễn là bạn không ghép các cột khác với một trọng số khác .

COALESCE() cũng không có nghĩa gì miễn là bạn không thực sự ghép nhiều cột hơn.

Đơn giản hóa chỉ mục của bạn:

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

Và truy vấn của bạn:

SELECT "ITEMID", ts_rank(to_tsvector('english', "CONTENT")
                       , plainto_tsquery('english', 'searchTerm')) AS rank
FROM   "File"
WHERE  to_tsvector('english', "CONTENT")
       @@ plainto_tsquery('english', 'searchTerm')
ORDER  BY rank DESC
LIMIT  5;

Vẫn còn đắt cho một cụm từ tìm kiếm phù hợp với mọi hàng, nhưng có lẽ ít hơn nhiều .

Ngoài ra

Tất cả những vấn đề này cộng lại, chi phí điên rồ là 520 giây cho truy vấn thứ hai của bạn đang bắt đầu có ý nghĩa. Nhưng vẫn có thể có nhiều vấn đề hơn. Bạn đã cấu hình máy chủ của bạn?
Tất cả các lời khuyên thông thường để tối ưu hóa hiệu suất áp dụng.

Nó làm cho cuộc sống của bạn dễ dàng hơn nếu bạn không làm việc với các số nhận dạng trường hợp CaMeL trích dẫn kép:


Tôi cũng đang chạy vào đây. Với Postgresql 9.6, chúng tôi sử dụng viết lại truy vấn cho các từ đồng nghĩa vì vậy tôi không nghĩ sử dụng tìm kiếm tương tự trigram để giới hạn số lượng tài liệu sẽ hoạt động tốt.
pholly

Kinh ngạc! USING gin (to_tsvector('english', "CONTENT")
K-Gun

1

Tôi đã có vấn đề tương tự. Tôi đã chăm sóc nó bằng cách tính toán ts_rank của mọi thuật ngữ truy vấn văn bản phổ biến đối với một trường: bảng tuple và lưu trữ nó trong bảng tra cứu. Điều này giúp tôi tiết kiệm rất nhiều thời gian (hệ số 40X) trong quá trình tìm kiếm các từ phổ biến trong văn bản nặng.

  1. Nhận các từ phổ biến trong kho văn bản bằng cách quét tài liệu và đếm số lần xuất hiện của nó.
  2. Sắp xếp theo từ phổ biến nhất.
  3. tiền mã hóa ts_rank của các từ phổ biến và lưu trữ nó trong một bảng.

Truy vấn: tra cứu bảng này và nhận id tài liệu được sắp xếp theo thứ hạng tương ứng của chúng. nếu không có, làm theo cách cũ.

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.