Vector DB 4 — pgvector: vector trong PostgreSQL
pgvector là gì?
pgvector là một extension của PostgreSQL thêm vào cơ sở dữ liệu quan hệ hai thứ mà Postgres gốc không có: một kiểu dữ liệu vector và các toán tử tìm kiếm tương đồng (similarity search) trên vector đó. Nói cách khác, thay vì phải dựng một hệ vector database riêng biệt, bạn cài thêm một extension và Postgres của bạn biết lưu embedding, đánh chỉ mục ANN, và trả về "k đoạn văn bản gần nghĩa nhất" ngay trong một câu SELECT.
Ở Vector DB 1 và Vector DB 2 ta đã thấy: mỗi đoạn tài liệu được biến thành một vector nhiều chiều, và "gần nhau về ý nghĩa" tương đương "gần nhau về khoảng cách". Vector DB 3 — ANN & chỉ mục mổ xẻ các thuật toán tìm lân cận gần đúng (IVF, HNSW). pgvector là hiện thân cụ thể của những khái niệm đó ngay bên trong Postgres — cùng một cơ sở dữ liệu đang giữ bảng khách hàng, giao dịch, tài khoản của bạn.
Vì pgvector cắm vào cơ chế extension của Postgres, nó tận dụng được cả hệ sinh thái sẵn có: transaction (ACID), replication, backup, quyền truy cập theo row/column, chỉ mục GIN/GiST trên JSONB, planner tối ưu truy vấn... Vector chỉ là "một kiểu cột mới", không phải một thế giới tách biệt.
Vì sao pgvector hấp dẫn nhiều tổ chức?
Trước khi có pgvector, kiến trúc điển hình cho tìm kiếm ngữ nghĩa là: dữ liệu nghiệp vụ nằm trong Postgres, còn embedding phải đẩy sang một hệ riêng (Qdrant, Milvus, Pinecone, Weaviate — xem Vector DB 5). Điều đó kéo theo hàng loạt cái giá vận hành:
- Đồng bộ hai chiều: khi một tài liệu bị xoá/sửa trong Postgres, phải nhớ xoá/sửa vector tương ứng ở hệ kia — dễ lệch, khó đảm bảo nhất quán.
- Không có transaction xuyên hệ: chèn tài liệu + chèn vector không thể nằm trong một transaction ACID → có cửa sổ dữ liệu "nửa vời".
- Thêm một hệ để bảo trì, giám sát, backup, phân quyền, kiểm toán — với ngân hàng, mỗi hệ mới là thêm một vòng thẩm định bảo mật và tuân thủ.
pgvector xoá bỏ phần lớn những cái giá này bằng cách nhốt giao dịch + vector + metadata trong một DB duy nhất:
| Tiêu chí | pgvector (trong Postgres) | Vector DB chuyên dụng |
|---|---|---|
| Nhất quán dữ liệu | Cùng transaction ACID với dữ liệu nghiệp vụ | Phải tự đồng bộ, dễ lệch |
| Filter metadata + vector | Một câu SQL, dùng planner sẵn có | Cần API filter riêng, khả năng hạn chế |
| Vận hành | Dùng lại backup/replication/monitoring Postgres | Thêm một hệ để bảo trì |
| Quy mô cực lớn (>hàng trăm triệu) | Có giới hạn hiệu năng | Tối ưu chuyên biệt, mạnh hơn |
| Độ chín tính năng vector | Đủ tốt, đang tiến nhanh | Phong phú (quantization, sharding, sparse...) |
Kết luận thực dụng: pgvector đặc biệt hợp quy mô vừa (vài triệu tới vài chục triệu vector) và những tổ chức đã dùng Postgres sẵn — mà ngân hàng Việt Nam thì Postgres cực kỳ phổ biến. Giảm được một hệ thống là giảm một khối phức tạp về bảo mật, tuân thủ và chi phí con người. Khi kho vượt quy mô hoặc cần tính năng chuyên sâu, mới tính tới vector DB riêng.
Cài đặt và kiểu vector
pgvector cài như một extension bình thường (giả định bản nhị phân đã được deploy vào Postgres). Kích hoạt cho một database:
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox (extension chưa cài)
CREATE EXTENSION IF NOT EXISTS vector;
Sau đó có thể khai báo cột kiểu vector(n) với n là số chiều (dimension) — phải khớp đúng số chiều mà mô hình embedding sinh ra:
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
CREATE TABLE policy_chunks (
id bigserial PRIMARY KEY,
doc_id bigint NOT NULL, -- tài liệu gốc
department text NOT NULL, -- metadata: phòng ban
effective boolean NOT NULL, -- còn hiệu lực?
content text NOT NULL, -- đoạn văn bản
embedding vector(1024) -- vector 1024 chiều
);
Chèn một vector bằng cách viết mảng số thực (embedding do mô hình sinh ra được nạp vào ứng dụng rồi ghi xuống):
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
INSERT INTO policy_chunks (doc_id, department, effective, content, embedding)
VALUES (42, 'Tín dụng', true, 'Hạn mức cho vay tín chấp...', '[0.021, -0.083, ...]');
Lưu ý số chiều phải cố định và khớp mô hình: nếu mô hình embedding trả về 1024 chiều thì cột phải là vector(1024); nạp vector 768 chiều vào sẽ báo lỗi. Đổi mô hình embedding (sang số chiều khác) đồng nghĩa phải tạo cột/bảng mới và re-embed toàn bộ.
Toán tử khoảng cách
Trái tim của pgvector là ba toán tử khoảng cách giữa hai vector. Chọn đúng toán tử phải khớp với metric mà mô hình embedding được huấn luyện (Vector DB 2 giải thích kỹ):
| Toán tử | Ý nghĩa | Metric | Khi nào dùng |
|---|---|---|---|
<-> | Khoảng cách L2 (Euclid) | Càng nhỏ càng gần | Khi mô hình dùng L2 |
<=> | Khoảng cách cosine (1 − cosine similarity) | Càng nhỏ càng gần | Phổ biến nhất cho text embedding |
<#> | Inner product âm (negative inner product) | Càng nhỏ (âm sâu) càng gần | Khi vector đã chuẩn hoá và tối ưu dot product |
Điểm dễ sai: cả ba đều là "càng nhỏ càng gần" để ORDER BY ... LIMIT k lấy đúng láng giềng gần nhất. Vì thế <#> trả về inner product có dấu âm (nghịch đảo), đừng nhầm là "âm nghĩa là xa".
Truy vấn tìm k đoạn gần nghĩa nhất với một vector câu hỏi q (embedding của câu hỏi người dùng, được ứng dụng tính rồi truyền vào dạng tham số):
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
SELECT id, doc_id, content
FROM policy_chunks
ORDER BY embedding <=> '[... vector câu hỏi q ...]' -- cosine
LIMIT 5;
Đây chính là semantic search: không match từ khoá, mà xếp hạng theo độ gần về ý nghĩa. Muốn trả kèm điểm tương đồng, đưa biểu thức khoảng cách vào SELECT:
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
SELECT id, content,
1 - (embedding <=> '[...q...]') AS cosine_similarity
FROM policy_chunks
ORDER BY embedding <=> '[...q...]'
LIMIT 5;
Chỉ mục: IVFFlat vs HNSW
Không có chỉ mục, mỗi truy vấn ORDER BY embedding <=> q là exact KNN — quét toàn bộ bảng, O(N·d). Ổn với vài nghìn dòng nhưng sập ở triệu dòng (Vector DB 3 giải thích vì sao). pgvector cung cấp hai loại chỉ mục ANN.
IVFFlat
IVFFlat hiện thực hoá ý tưởng IVF (inverted file): k-means chia kho thành lists cụm, truy vấn chỉ quét probes cụm gần nhất.
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
CREATE INDEX ON policy_chunks
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 1000);
ANALYZE policy_chunks; -- cập nhật thống kê cho planner
SET ivfflat.probes = 10; -- núm chỉnh recall–tốc độ lúc truy vấn
Đặc điểm và bẫy của IVFFlat:
- Phải xây SAU khi đã có dữ liệu: k-means cần dữ liệu để học các centroid. Xây IVFFlat trên bảng rỗng (rồi mới nạp) → các cụm vô nghĩa → recall tệ. Quy trình đúng là nạp đủ dữ liệu đại diện rồi mới
CREATE INDEX. lists(số cụm): khuyến nghị~sqrt(N)cho tới vài triệu dòng,N/1000cho kho lớn hơn.probes(số cụm xét khi truy vấn): tăng → recall lên, latency lên. Đây là núm chỉnh chính, đặt bằngSET ivfflat.probes.- Cần
ANALYZEsau khi build để planner ước lượng đúng. vector_cosine_opsphải khớp toán tử: cosine →vector_cosine_ops, L2 →vector_l2_ops, inner product →vector_ip_ops.
HNSW
HNSW hiện thực đồ thị "thế giới nhỏ phân tầng" (Vector DB 3 mô tả cơ chế). Trong pgvector, HNSW thường cho đường cong recall–tốc độ tốt hơn IVFFlat:
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
CREATE INDEX ON policy_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
SET hnsw.ef_search = 80; -- núm chỉnh recall–tốc độ lúc truy vấn
Ưu điểm quan trọng so với IVFFlat: HNSW xây được ngay cả khi bảng rỗng (đồ thị dựng tăng dần khi chèn), nên hợp với dữ liệu chảy vào liên tục — không cần "chờ đủ dữ liệu rồi mới build" như IVFFlat.
Ba tham số (giống Vector DB 3):
m: số cạnh tối đa mỗi đỉnh. Lớn → recall cao, tốn RAM và build lâu hơn (điển hình 16).ef_construction: kích thước danh sách ứng viên lúc build. Lớn → đồ thị chất lượng cao, build chậm hơn (điển hình 64–200).ef_search: kích thước danh sách ứng viên lúc truy vấn — núm chỉnh recall–tốc độ chính, phải>= k.
So sánh nhanh
| IVFFlat | HNSW | |
|---|---|---|
| Cơ chế | Phân cụm k-means (IVF) | Đồ thị phân tầng |
| Xây khi bảng rỗng | Không (cần dữ liệu train) | Có |
| Build | Nhanh hơn | Chậm hơn |
| RAM | Thấp hơn | Cao hơn (đồ thị trong RAM) |
| Recall/latency | Tốt | Thường tốt hơn |
| Núm truy vấn | probes | ef_search |
Mặc định thực tế cho hầu hết use case ngân hàng quy mô vừa: HNSW. Chỉ chọn IVFFlat khi RAM rất eo hẹp hoặc kho quá lớn muốn build nhanh và chấp nhận recall thấp hơn chút.
Sức mạnh thật: WHERE metadata + vector trong một câu
Đây là điểm ăn tiền nhất của pgvector so với hệ vector riêng: bạn có thể trộn filter SQL (trên metadata quan hệ) với similarity search trong cùng một câu, dùng chính planner của Postgres:
-- Minh hoạ pgvector — KHÔNG chạy được trên sandbox
SELECT id, content
FROM policy_chunks
WHERE department = 'Tín dụng' -- filter metadata (SQL thường)
AND effective = true
ORDER BY embedding <=> '[...q...]' -- similarity
LIMIT 5;
Với vector DB chuyên dụng, filter metadata thường bị giới hạn (chỉ vài kiểu điều kiện) hoặc phải học API riêng. Với pgvector, bạn có toàn bộ sức mạnh SQL: JOIN sang bảng khác, subquery, điều kiện phức tạp, truy vấn JSONB... rồi mới xếp hạng theo vector.
Nhưng có đánh đổi filter vs recall cần hiểu (đã bàn ở Vector DB 3): chỉ mục HNSW/IVFFlat được xây trên toàn bộ vector, không biết trước filter. Khi filter loại phần lớn dữ liệu, quá trình duyệt đồ thị có thể "đi lạc" vào vùng bị filter bỏ, phải duyệt nhiều hơn để gom đủ k kết quả thoả điều kiện → recall giảm hoặc chậm. pgvector 0.8+ cải thiện đáng kể bằng iterative index scan — tự động duyệt thêm khi filter khắt khe làm thiếu kết quả. Với filter cực kỳ chọn lọc (số dòng sau lọc rất nhỏ), planner có thể tự chuyển sang lọc bằng B-tree rồi brute-force trên tập nhỏ — vừa nhanh vừa recall = 1,0.
Đối chiếu: lexical search chạy được trên Postgres thường
Để cảm nhận khác biệt giữa semantic (pgvector, cần embedding) và lexical/từ khoá (LIKE, full-text — chạy được trên Postgres thường), đây là một truy vấn "tìm theo từ khoá" thuần SQL. Nó không hiểu ngữ nghĩa (không biết "vay tiêu dùng" gần "tín chấp"), chỉ khớp chuỗi ký tự — nhưng chạy được ngay trên sandbox để đối chiếu. Truy vấn liệt kê các giao dịch có ghi chú kiểu "loan" của khách ở Hà Nội, kèm tên khách và phòng ban của nhân viên phụ trách (minh hoạ JOIN 5 bảng):
-- ▶ Chạy được
SELECT c.full_name,
a.account_no,
t.amount,
t.kind,
d.name AS department
FROM transactions t
JOIN accounts a ON a.id = t.account_id
JOIN customers c ON c.id = a.customer_id
JOIN employees e ON e.department_id = (SELECT id FROM departments WHERE name = 'Credit')
JOIN departments d ON d.id = e.department_id
WHERE c.city = 'Hanoi'
AND t.kind LIKE '%loan%'
ORDER BY t.created_at DESC
LIMIT 20;
Đây là lexical: LIKE '%loan%' chỉ bắt đúng chữ "loan" xuất hiện trong cột kind, bỏ sót mọi cách diễn đạt khác. Semantic search của pgvector thì ngược lại — nắm được ý nghĩa gần nhau dù chữ khác. Trong thực tế hai hướng này bổ sung cho nhau, đó là ý tưởng Hybrid Search (Vector DB 6).
Kiến trúc: Postgres làm cả OLTP + vector + metadata
Điểm mấu chốt của sơ đồ: một Postgres vừa là OLTP (giao dịch nghiệp vụ), vừa giữ metadata, vừa giữ vector + chỉ mục — thay vì tách thành hai ba hệ. Backup, replication, phân quyền (access control), kiểm toán áp dụng đồng nhất.
Giới hạn của pgvector
pgvector không phải "viên đạn bạc". Cần biết rõ ranh giới:
- Số chiều: pgvector giới hạn cột được đánh chỉ mục ở tối đa 2000 chiều (kiểu
vector; các biến thểhalfvecnửa độ chính xác cho phép nhiều hơn nhưng đánh đổi độ chính xác). Đa số mô hình embedding (768, 1024, 1536) nằm dưới ngưỡng này. - Quy mô rất lớn: từ hàng trăm triệu vector trở lên, HNSW ngốn RAM và build rất lâu; hiệu năng và tính năng phân mảnh (sharding) thua các vector DB chuyên dụng vốn tối ưu riêng cho việc này (Vector DB 5).
- Tính năng chuyên sâu: quantization nâng cao, sparse vector, multi-vector, tối ưu GPU... vector DB chuyên dụng đi trước. pgvector đang tiến nhanh nhưng chưa phủ hết.
- Cạnh tranh tài nguyên: vector search ngốn RAM/CPU, nếu chạy chung instance với OLTP nặng thì có thể ảnh hưởng lẫn nhau — thường tách read replica riêng cho tải vector.
pgvectorscale (của Timescale) là một extension bổ sung xây trên pgvector, thêm chỉ mục StreamingDiskANN (dựa trên DiskANN — cho phép đẩy một phần index xuống đĩa để phục vụ kho lớn với ít RAM hơn) và lượng tử hoá SBQ, nhằm thu hẹp khoảng cách hiệu năng với vector DB chuyên dụng khi quy mô lớn. Đây là một hướng đáng cân nhắc trước khi quyết định rời bỏ Postgres.
Thực hành tốt
- Chuẩn hoá (normalize) vector nếu dùng cosine/inner product: chuẩn hoá về độ dài 1 giúp cosine và inner product tương đương, và ổn định số học. Nhiều mô hình đã trả vector chuẩn hoá sẵn — kiểm tra kỹ.
- Chọn metric khớp mô hình embedding: nếu mô hình huấn luyện theo cosine thì dùng
<=>vàvector_cosine_ops; dùng sai metric làm hỏng chất lượng xếp hạng dù index vẫn "chạy". - Ops class của index phải khớp toán tử truy vấn: index
vector_cosine_opschỉ tăng tốc truy vấn dùng<=>. Truy vấn dùng<->trên index cosine sẽ không dùng được index → quét toàn bảng. - HNSW cho hầu hết trường hợp, IVFFlat khi RAM eo hẹp/kho lớn và chấp nhận recall thấp hơn.
- Đặt số chiều đúng ngay từ đầu và cố định mô hình embedding; đổi mô hình = re-embed toàn bộ.
- Với dữ liệu chảy vào liên tục, ưu tiên HNSW vì xây được khi rỗng; nếu buộc dùng IVFFlat, nhớ rebuild định kỳ khi phân bố dữ liệu đổi nhiều.
Use case thực tế
Bối cảnh: Team dữ liệu NCB xây trợ lý RAG tra cứu chính sách, thông tư, quy trình nghiệp vụ nội bộ. Yêu cầu bảo mật: dữ liệu không rời hạ tầng nội bộ, và tổ chức đã vận hành PostgreSQL cho nhiều hệ nghiệp vụ. Đây là kịch bản "vàng" cho pgvector — không phải dựng thêm hệ vector riêng để qua vòng thẩm định bảo mật.
Quy mô: sau chunking có 4,8 triệu chunk, mỗi chunk là vector 1024 chiều (từ mô hình embedding chạy on-prem), kèm metadata department, effective, doc_id.
Bước 1 — Thiết kế bảng: một bảng policy_chunks(id, doc_id, department, effective, content, embedding vector(1024)). Vector và metadata cùng một bảng, cùng transaction — khi thu hồi một thông tư, UPDATE effective = false và vector "biến mất khỏi kết quả" ngay trong cùng giao dịch, không có cửa sổ lệch dữ liệu.
Bước 2 — Chỉ mục HNSW: nạp dữ liệu rồi build HNSW (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64). Chọn HNSW vì tài liệu mới bổ sung hằng ngày (xây được khi rỗng, cập nhật tăng dần). Build ~30 phút, index ~18 GB nằm trong RAM của node 64 GB.
Bước 3 — Tune ef_search: dựng ground-truth bằng exact KNN trên 2.000 query mẫu, quét ef_search:
| ef_search | recall@10 | p95 latency |
|---|---|---|
| 40 | 0,94 | 7 ms |
| 80 | 0,975 | 12 ms |
| 120 | 0,987 | 18 ms |
→ Chọn ef_search = 80: recall 0,975, p95 12ms.
Bước 4 — Filter + similarity một câu: mỗi phòng ban chỉ được thấy tài liệu phòng mình + tài liệu chung, và chỉ bản còn hiệu lực. Điều kiện WHERE department IN (...) AND effective = true ghép thẳng vào câu ANN, dùng iterative index scan của pgvector 0.8. Với truy vấn khoá vào một tài liệu cụ thể (<3.000 chunk), planner tự chuyển sang B-tree + brute-force, recall = 1,0.
Kết quả: toàn bộ RAG chạy trên một Postgres nội bộ — không thêm hệ vector riêng, không đồng bộ hai chiều, dùng lại backup/replication/phân quyền sẵn có. p95 12ms, recall@10 = 0,975, phục vụ ~120 QPS trên một node. Vận hành nâng cao ở Vector DB 8 — Production Ops.
Ghi nhớ
- pgvector là extension Postgres thêm kiểu
vector(n)và toán tử similarity — biến Postgres thành nơi lưu và tìm embedding, không cần hệ vector riêng. - Hấp dẫn vì gộp giao dịch + vector + metadata trong một DB: nhất quán ACID, dùng lại backup/phân quyền/monitoring, giảm phức tạp — hợp quy mô vừa và các tổ chức (như ngân hàng) đã dùng Postgres sẵn.
- Ba toán tử khoảng cách:
<->(L2),<=>(cosine),<#>(inner product âm); đều "càng nhỏ càng gần". Chọn metric khớp mô hình embedding. - Truy vấn:
ORDER BY embedding <=> q LIMIT k. Không có index → exact KNN quét toàn bảng. - Hai chỉ mục: IVFFlat (cần dữ liệu để train, xây SAU khi có dữ liệu, cần
ANALYZE, chỉnhprobes) vs HNSW (m,ef_construction,ef_search; xây được khi rỗng, thường tốt hơn). Mặc định: HNSW. - Điểm mạnh riêng: WHERE metadata + similarity trong một câu SQL; đánh đổi filter khắt khe vs recall (pgvector 0.8 iterative scan; filter cực chọn lọc → brute-force recall 1,0).
- Giới hạn: số chiều (≤2000 cho index), quy mô rất lớn và tính năng chuyên sâu thua vector DB riêng; pgvectorscale (StreamingDiskANN) thu hẹp khoảng cách khi kho lớn.
- pgvector là extension chưa cài trên sandbox → mọi câu vector trong bài là minh hoạ, không chạy được; chỉ block lexical
LIKE/JOIN là SQL Postgres thường chạy được để đối chiếu semantic vs từ khoá.
Bài viết liên quan
Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.
Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.
Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.
Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.