Trang chủ Bài viết Cơ sở dữ liệu Tìm kiếm vector PostgreSQL với pgvector và hybrid search trong Next.js
Cơ sở dữ liệu 12/11/2026 0 lượt xem

Tìm kiếm vector PostgreSQL với pgvector và hybrid search trong Next.js

Tìm kiếm vector PostgreSQL với pgvector và hybrid search trong Next.js

Bài viết trình bày cách xây dựng một lab tìm kiếm ticket hỗ trợ nhỏ, kết hợp tìm kiếm từ khóa, vector và hybrid trên PostgreSQL với pgvector 0.8 trở lên, sau đó nối truy vấn vào Next.js Route Handler. Lab dùng vector ba chiều cố định để có thể tái tạo hành vi xếp hạng và lọc mà không cần API key embedding; các con số này chỉ thay thế cho embedding, không đo khả năng hiểu ticket của mô hình.

Thiết lập schema và dữ liệu mẫu

Cần một database PostgreSQL dùng một lần, có extension vector. Phần thiết lập extension và schema dùng role migration; route tìm kiếm dùng một login riêng bị giới hạn quyền. Bảng support_ticket gồm tenant_id, error_code, title, body, embedding vector(3) và cột search_text tsvector sinh tự động, có trọng số: title mức A, body mức B.

Các chỉ mục gồm B-tree trên tenant_id, chỉ mục kết hợp (tenant_id, error_code) và GIN trên search_text. Dữ liệu mẫu có bốn ticket của hai tenant acme và other; một ticket của tenant other chứa cùng mã AUTH-4017 nhưng tuyệt đối không được trả về cho Acme. Tác giả lưu ý mã lỗi hay SKU nên nằm ở trường riêng có chỉ mục, vì full-text search chuẩn hóa ngôn ngữ và không đảm bảo khớp chính xác các ký tự đặc biệt.

So sánh tìm kiếm từ khóa và vector

Tìm kiếm từ khóa là baseline tốt cho từ đặc trưng, tên sản phẩm và mã định danh. Tìm kiếm vector sắp xếp theo khoảng cách tới vector truy vấn, có thể lấy được văn bản ít trùng từ với câu hỏi. Cả hai truy vấn đều lọc tenant trước khi trả ứng viên.

Với bộ dữ liệu mẫu, truy vấn từ khóa cho Acme và AUTH-4017 trả về ticket 1, còn tìm kiếm vector xếp ticket 2 lên trên ticket 1. Ticket riêng tư có vector gần hơn nhưng bị loại bởi bộ lọc tenant. Khi thử câu diễn đạt lại “sign-in works until I close the browser”, tìm kiếm từ khóa không tìm thấy dòng nào vì thiếu từ khóa bắt buộc, trong khi vector trả về ticket về session cookie trước. Trong hệ thống thật, hai chuỗi này sẽ tạo ra các vector truy vấn khác nhau; ở đây cố ý dùng chung một vector để giữ nguyên phía vector.

Kết hợp bằng Reciprocal Rank Fusion

Điểm xếp hạng văn bản và khoảng cách cosine khác đơn vị và ý nghĩa. Reciprocal rank fusion (RRF) dùng vị trí của ứng viên trong từng danh sách thay vì cộng điểm thô: mỗi ứng viên nhận 1 chia cho hằng số cộng thứ hạng của nó ở mỗi danh sách mà nó xuất hiện, ứng viên vắng mặt đóng góp 0. Đây là baseline có thể tinh chỉnh bằng các truy vấn đã được đánh giá.

Ví dụ hybrid ưu tiên khớp mã chính xác trước khi fusion, để mã định danh vẫn thắng khi có một kết quả ngữ nghĩa gần. Với Acme, AUTH-4017 và vector [1,0,0], truy vấn hybrid trả ticket 1 đầu tiên; với câu diễn đạt lại, danh sách từ khóa rỗng và danh sách vector cung cấp kết quả. Không nên coi điểm fusion là phần trăm độ tin cậy; kết quả đầu vẫn có thể sai, đặc biệt khi cả hai nhánh đều không có bằng chứng mạnh. Với agent dùng retrieval-augmented, cần giữ ID nguồn cho mỗi mục và quyết định khi nào trả “không có kết quả đáng tin” dựa trên các truy vấn đã đánh giá thay vì một ngưỡng điểm cố định.

Áp dụng Row Level Security theo tenant

Cần lọc cả hai danh sách ứng viên, không chỉ lọc sau khi lấy top 20 trên toàn bộ tenant, vì như vậy vừa lãng phí ứng viên vừa dễ lộ dữ liệu workspace khác qua một join sau này. Thêm Row Level Security (RLS) làm lớp thứ hai; ứng dụng lấy tenant từ kiểm tra membership đã xác thực, không bao giờ lấy từ tham số tìm kiếm do client kiểm soát.

Role support_search chỉ có quyền SELECT, không sở hữu bảng, không có BYPASSRLS hay quyền superuser. Policy dùng tenant_id = nullif(current_setting('app.tenant_id', true), ''); nếu không đặt tenant trong transaction, policy trả về không dòng nào. Cần kiểm thử qua chính role runtime, vì truy vấn thành công với tư cách chủ bảng không nói lên điều gì về RLS. Mô hình này giả định ứng dụng sở hữu kết nối database và chạy SQL tham số hóa cố định, không phù hợp làm sandbox cho SQL tùy ý từ agent.

Kết nối Next.js Route Handler

Route dùng gói pg với pool giới hạn kết nối, SQL lưu dạng hằng số phía server. Mỗi request xác thực tenant, kiểm tra độ dài câu hỏi (1–400 ký tự), tạo embedding và kiểm tra hình dạng vector. Trong transaction, route đặt statement_timeout 1500ms, đặt tenant bằng set_config với tham số thứ ba là true để cấu hình chỉ có hiệu lực trong transaction, chạy truy vấn hybrid rồi commit; lỗi sẽ rollback. Khi dùng pool, mọi câu lệnh trong transaction phải dùng cùng một client đã checkout.

Đo ảnh hưởng của bộ lọc tenant tới recall HNSW

Chỉ mục HNSW tăng tốc tra cứu vector nhưng chỉ trả về ứng viên gần đúng. Một predicate lọc tenant có độ chọn lọc thấp có thể loại bỏ ứng viên sau khi quét chỉ mục, khiến trả về ít dòng hơn yêu cầu dù tenant có đủ dữ liệu. Lab thêm 500 vector của tenant other gần vector truy vấn, giữ nguyên vector Acme, rồi tạo chỉ mục HNSW với operator class cosine để khớp toán tử khoảng cách đã dùng.

Cần lưu ba ID từ baseline chính xác trước khi tạo chỉ mục, sau đó xem EXPLAIN (ANALYZE, BUFFERS) cho cùng truy vấn vector. Với tenant nhỏ và chọn lọc cao, việc dùng bộ lọc B-tree rồi sắp xếp các dòng phù hợp là kết quả hợp lý trong production nhưng không kiểm tra HNSW; trong lab cần tạm tắt các đường truy cập cạnh tranh để kế hoạch thực thi nêu rõ chỉ mục HNSW.

Nguồn: Planet PostgreSQL

Trích nguồn từ: Planet PostgreSQL

Chia sẻ:

Bài viết liên quan

Zalo