Vector DB 4 — pgvector: vector trong PostgreSQL

13 thg 7, 2026 3 lượt xem
#ai
#vector-search
#pgvector
#hnsw
#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 1Vector 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ệuCùng transaction ACID với dữ liệu nghiệp vụPhải tự đồng bộ, dễ lệch
Filter metadata + vectorMộ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ànhDùng lại backup/replication/monitoring PostgresThê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ăngTối ưu chuyên biệt, mạnh hơn
Độ chín tính năng vectorĐủ tốt, đang tiến nhanhPhong 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 nsố 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ĩaMetricKhi nào dùng
<->Khoảng cách L2 (Euclid)Càng nhỏ càng gầnKhi mô hình dùng L2
<=>Khoảng cách cosine (1 − cosine similarity)Càng nhỏ càng gầnPhổ biến nhất cho text embedding
<#>Inner product âm (negative inner product)Càng nhỏ (âm sâu) càng gầnKhi 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 <=> qexact 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/1000 cho 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ằng SET ivfflat.probes.
  • Cần ANALYZE sau khi build để planner ước lượng đúng.
  • vector_cosine_ops phả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

IVFFlatHNSW
Cơ chếPhân cụm k-means (IVF)Đồ thị phân tầng
Xây khi bảng rỗngKhông (cần dữ liệu train)
BuildNhanh hơnChậm hơn
RAMThấp hơnCao hơn (đồ thị trong RAM)
Recall/latencyTốtThường tốt hơn
Núm truy vấnprobesef_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ể halfvec nử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 <=>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_ops chỉ 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_searchrecall@10p95 latency
400,947 ms
800,97512 ms
1200,98718 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ớ

  • pgvectorextension 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ỉnh probes) 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.

13 thg 7, 2026 15

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.

13 thg 7, 2026 13

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.

13 thg 7, 2026 13

Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.

13 thg 7, 2026 11