· pgvector / postgres / vector-search
Tinh chỉnh index pgvector: HNSW, IVFFlat và khi nên chuyển
Cấu hình mặc định không đủ khi scale lên. Những tham số cần thay đổi, cách đo recall, và khi nào nên chuyển sang database vector chuyên dụng.
Bởi Ethan
2.225 từ · 12 phút đọc
Nếu câu truy vấn vector của bạn chậm, hoặc pipeline RAG đang trả về kết quả lạc đề sau khi bảng vượt qua vài trăm nghìn dòng, thủ phạm hầu như chắc chắn là index — hoặc thiếu tinh chỉnh. pgvector đi kèm với các giá trị mặc định thận trọng. An toàn, nhưng không nhanh.
Tóm gọn: dùng HNSW, đặt m = 32 và ef_construction = 200 khi build index, rồi tinh chỉnh ef_search ở query time theo một recall target mà bạn tự đo. Nếu đang dùng IVFFlat, lấy lists = rows / 1000 và probes = sqrt(lists) làm điểm xuất phát. Phần còn lại của bài giải thích lý do đằng sau những con số đó, phải làm gì nếu chúng không phù hợp với workload của bạn, và điểm mà pgvector bắt đầu không còn đủ.
Bài viết này dành cho ai
Backend engineer đang chạy một pipeline RAG hoặc tính năng tìm kiếm ngữ nghĩa trên nền tảng Postgres hiện có. Bài viết giả định bạn đã quyết định dùng pgvector. Nếu bạn đang scale đến hàng trăm triệu vector, đọc phần “Khi nào nên chuyển sang giải pháp khác” trước.
Nếu bạn đang quản lý pgvector trong môi trường multi-tenant, bài Multi-tenant Postgres 2026 đề cập các chiến lược isolation ảnh hưởng đến cách bạn phân vùng vector query.
pgvector là gì (và điều gì thay đổi trong v0.5.0)
pgvector bổ sung kiểu cột vector và hai loại index — IVFFlat và HNSW — vào PostgreSQL tiêu chuẩn. Extension này yêu cầu PostgreSQL 13 trở lên; hỗ trợ PostgreSQL 12 đã bị bỏ từ v0.8.0, phát hành ngày 2024-10-30.
Lịch sử phiên bản quan trọng ở đây. HNSW được giới thiệu trong v0.5.0 ngày 2023-08-28. Trước đó, IVFFlat là lựa chọn index duy nhất. Rất nhiều bài hướng dẫn viết trước ngày đó vẫn đang đứng top tìm kiếm — họ khuyên dùng IVFFlat vì HNSW chưa tồn tại. Hãy kiểm tra ngày đăng trước khi làm theo bất kỳ lời khuyên tinh chỉnh pgvector nào.
HNSW so với IVFFlat
Sự đánh đổi giữa hai loại được trình bày trực tiếp từ pgvector README:
“HNSW has better query performance than IVFFlat (in terms of speed-recall tradeoff), but has slower build times and uses more memory.”
“IVFFlat has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff).”
Đó là toàn bộ thông tin từ nguồn gốc. Bất kỳ bài blog nào đưa ra con số chính xác — “nhanh gấp 6×”, “thông lượng tốt hơn 30×” — đều đang suy diễn từ các benchmark không phản ánh phần cứng, kích thước embedding, hay phân phối truy vấn của bạn. Quá trình kiểm chứng adversarial cho thấy không có con số nào trong số đó trụ vững khi đối chiếu với nguồn gốc.
Trong thực tế:
Dùng HNSW khi latency truy vấn là SLA của bạn. HNSW duy trì recall tốt hơn khi scale, vì cấu trúc đồ thị xử lý không gian nhiều chiều hiệu quả hơn cách tiếp cận dựa trên cluster.
Dùng IVFFlat khi bạn cần rebuild index thường xuyên — batch embedding mới, chu kỳ bảo trì định kỳ — hoặc khi RAM là hạn chế cứng. IVFFlat build nhanh hơn và tốn ít bộ nhớ hơn.
Một lưu ý vận hành: IVFFlat cần đủ dữ liệu để phân vùng thành các cluster có nghĩa. Kinh nghiệm chung là chỉ build index sau khi bảng đã có một lượng dữ liệu nhất định — danh sách thưa làm giảm recall. HNSW không có ràng buộc khởi động lạnh.
Các tham số HNSW
Ba tham số cần điều chỉnh:
| Tham số | Mặc định | Kiểm soát gì | Điểm xuất phát cho production |
|---|---|---|---|
m | 16 | Số kết nối tối đa mỗi layer trong đồ thị | 32 |
ef_construction | 64 | Kích thước danh sách ứng viên khi build index | 200 |
ef_search | 40 | Kích thước danh sách ứng viên lúc query | Tinh chỉnh theo workload |
m và ef_construction được đặt tại thời điểm CREATE INDEX và cần rebuild hoàn toàn để thay đổi. ef_search là một GUC cấp session — thay đổi cho từng truy vấn bằng SET hnsw.ef_search = 100.
m = 32 thay vì mặc định 16: nhiều cạnh hơn mỗi node có nghĩa đồ thị tìm đến ứng viên tốt trong ít bước hơn. Các benchmark SIGMOD 2026 trên tập dữ liệu 5–10 triệu vector (Brown University, Google, Université Paris Cité và ETH Zurich) đã dùng m=32. Tăng gấp đôi m làm tăng thời gian build và kích thước index, nhưng recall cải thiện ở quy mô lớn.
ef_construction = 200 thay vì mặc định 64: pool ứng viên lớn hơn trong quá trình build tạo ra đồ thị kết nối tốt hơn. Các benchmark SIGMOD 2026 đó cũng dùng 200. Giá trị cao hơn tốn nhiều thời gian build hơn; chúng không ảnh hưởng đến latency truy vấn.
ef_search là núm điều chỉnh recall-latency tại query time. Giá trị cao hơn cho recall tốt hơn nhưng đánh đổi bằng latency. Giá trị phù hợp phụ thuộc vào SLA của bạn. Chạy benchmark ở phần tiếp theo để tìm điểm cân bằng phù hợp.
-- Build với tham số production
CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops)
WITH (m = 32, ef_construction = 200);
-- Tinh chỉnh ef_search tại query time
SET hnsw.ef_search = 100;
Các tham số IVFFlat
Hai tham số cần điều chỉnh:
| Tham số | Mặc định | Điểm xuất phát khuyến nghị |
|---|---|---|
lists | — | rows / 1000 cho ≤1M dòng; sqrt(rows) cho >1M dòng |
probes | 1 | sqrt(lists) |
lists là số cluster mà index phân vùng các vector vào. Nhiều lists hơn có nghĩa phân vùng chi tiết hơn — mỗi list bao phủ ít vector hơn, và chi phí probe tỷ lệ thuận. Các công thức trên lấy từ pgvector README.
probes là số list mà truy vấn quét qua. Mặc định là 1 — một cluster, rất nhanh nhưng recall kém khi scale. Đặt probes = lists buộc quét toàn bộ cluster; query planner nhận ra đây tương đương với sequential scan và bỏ qua index hoàn toàn. Bạn nhận được kết quả chính xác, không phải xấp xỉ.
Điểm xuất phát sqrt(lists) cân bằng giữa recall và latency. Nếu recall@10 chưa đạt mục tiêu, tăng dần probes. Mỗi lần tăng tốn thêm thời gian truy vấn tỷ lệ tương ứng.
-- Build index cho bảng 500k dòng
CREATE INDEX ON embeddings USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 500); -- rows / 1000 = 500k / 1000
-- Đặt probes cho query session
SET ivfflat.probes = 23; -- sqrt(500) ≈ 22.4, làm tròn lên
Khi nào exact scan chiếm ưu thế
pgvector luôn hỗ trợ tìm kiếm nearest-neighbor chính xác — không cần dùng index. Đây là hành vi đúng, không phải lỗi.
Exact scan là lựa chọn phù hợp trong ba trường hợp:
Không có index: đây là trạng thái mặc định. Nếu bạn chạy SELECT ... ORDER BY embedding <-> query LIMIT 10 không có index, bạn nhận được recall hoàn hảo. Hiệu năng giảm sau vài trăm nghìn dòng, nhưng với dataset nhỏ chi phí này không đáng kể.
Bảng nhỏ: với dataset mà chi phí hệ thống của index lớn hơn chi phí quét bảng trực tiếp. Một preprint SIGMOD 2026 phát hiện rằng heap tuple ID resolution tiêu tốn 60–75% chu kỳ CPU trong các workload vector PostgreSQL — quản lý buffer, TID indirection và page locking, chứ không phải tính toán khoảng cách. Với số dòng nhỏ, exact scan bỏ qua tất cả chi phí đó.
IVFFlat với probes = lists: đặt probes bằng lists báo cho planner quét mọi cluster. Nó nhận ra đây là sequential scan và bỏ qua index. Bạn nhận được recall hoàn hảo mà không có chi phí index. Dùng cách này khi cần recall 100% và thời gian build quan trọng hơn latency truy vấn.
Tự benchmark
Mọi con số benchmark từ nguồn thứ cấp — bài blog, talk ở hội nghị, so sánh của vendor — đều đã được kiểm chứng adversarial cho bài viết này. Không có hệ số nhân thông dụng nào trụ vững khi đối chiếu với nguồn gốc. Benchmark duy nhất áp dụng được cho workload của bạn là benchmark bạn tự chạy trên phần cứng của mình, với kích thước embedding của mình, với phân phối truy vấn của mình.
Đây là phương pháp. ANN-Benchmarks dùng đúng framework này — recall@10 so với số truy vấn mỗi giây — để so sánh pgvector với các database vector chuyên dụng. Tự chạy cho bạn những con số có thể thực sự dùng được.
-- 1. Tạo bảng test
CREATE TABLE bench_vectors (id bigserial PRIMARY KEY, embedding vector(1536));
-- 2. Chèn dữ liệu tổng hợp (thay bằng embedding thực của bạn)
INSERT INTO bench_vectors (embedding)
SELECT array_fill(random(), ARRAY[1536])::vector
FROM generate_series(1, 100000);
-- 3. Build HNSW với tham số production
CREATE INDEX ON bench_vectors USING hnsw (embedding vector_cosine_ops)
WITH (m = 32, ef_construction = 200);
-- 4. Đặt ef_search
SET hnsw.ef_search = 100;
-- 5a. Ground truth: exact scan
SET enable_indexscan = off;
-- Chạy truy vấn → lưu top-10 ID là tập A
-- 5b. Kết quả ANN: HNSW index
SET enable_indexscan = on;
-- Chạy cùng truy vấn → lưu top-10 ID là tập B
-- recall@10 = |A ∩ B| / 10
-- 6. Profile index path
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, embedding <=> '[0.1, 0.2, ...]'::vector AS distance
FROM bench_vectors
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;
Tương đương với IVFFlat:
CREATE INDEX ON bench_vectors USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
SET ivfflat.probes = 10;
Chạy với nhiều giá trị ef_search (hoặc probes) khác nhau. Vẽ đồ thị recall@10 theo wall-clock latency. Tìm điểm giao cắt đáp ứng SLA của bạn. Nếu muốn hiểu pgvector đứng ở đâu so với Qdrant, Weaviate hay Pinecone, ANN-Benchmarks có biểu đồ recall@10 vs QPS trên nhiều dataset và cấu hình phần cứng.
Khi nào nên chuyển sang giải pháp khác
pgvector có giới hạn. Không có ngưỡng số dòng cụ thể nào qua được kiểm chứng adversarial — “hàng triệu vector” là định hướng đúng nhưng giới hạn thực sự phụ thuộc vào hình dạng workload của bạn, không phải kích thước thô.
Các dấu hiệu cho thấy đã đến lúc đánh giá các database chuyên dụng:
- Filtered query (kết hợp vector similarity với metadata filter) liên tục chậm hơn SLA của bạn
- Bạn cần cô lập cấp tenant với tinh chỉnh index riêng từng tenant
- Latency dưới millisecond là yêu cầu bắt buộc ở quy mô của bạn
- Đường cong recall@QPS trên ANN-Benchmarks của pgvector không còn đạt mục tiêu của bạn
Nếu bạn đã dùng Postgres và muốn managed pgvector với chi phí vận hành tối thiểu, Supabase đã hỗ trợ HNSW từ pgvector v0.5.0 (tháng 9/2023) và xử lý tinh chỉnh cấp infrastructure. Đây là lộ trình tự nhiên trước khi chuyển sang database vector chuyên dụng. Xem so sánh Neon vs Supabase nếu bạn đang cân nhắc giữa hai managed pgvector phổ biến nhất.
Nếu lộ trình scale của bạn cần extension pgvectorscale — streaming insert với tốc độ ingest cao, indexing dựa trên DiskANN — Timescale Cloud cung cấp nó như một managed service.
Hướng dẫn ra quyết định
Xem xét theo thứ tự:
- Bảng dưới 50k dòng, không có SLA latency? — Không cần index. Exact scan. Xong.
- Index ít khi rebuild, latency truy vấn là SLA? — HNSW, m=32, ef_construction=200. Tinh chỉnh ef_search theo recall target của bạn.
- Rebuild index thường xuyên, hoặc bị hạn chế RAM? — IVFFlat. lists = rows/1000 (hoặc sqrt(rows) nếu trên 1 triệu dòng). probes = sqrt(lists).
- Cần recall 100%? — Exact scan: không dùng index, hoặc IVFFlat với probes=lists.
- Đã tinh chỉnh hết mức nhưng vẫn không đạt SLA? — Chạy ANN-Benchmarks cho dataset của bạn và đánh giá Qdrant hoặc Weaviate.
Đo recall@10 trước và sau mỗi lần thay đổi tham số. Trực giác về điều gì “nên” nhanh hơn thường sai trong không gian nhiều chiều.
Lưu ý
Paper SIGMOD 2026 (Brown University, Google, Université Paris Cité và ETH Zurich) được đăng trên PACMMOD vol. 4, no. 3, article 134, tháng 6 năm 2026 (DOI 10.1145/3802011). Bài viết này trích dẫn arXiv preprint (arXiv:2603.23710) làm nguồn; paper được công bố là tài liệu tham chiếu chuẩn. Điểm dữ liệu về TID resolution được hỗ trợ về mặt định hướng nhưng hãy coi nó như vậy.
Không có hệ số nhân QPS nào xuất hiện trong bài viết này vì không có hệ số nào qua được kiểm chứng adversarial so với nguồn gốc.