Vector database
Vector database lưu embedding và tìm các vector gần nhất trong vài mili giây. Bài này đi qua độ đo tương đồng, index HNSW và IVF, pgvector so với Qdrant, Milvus, Pinecone, bài toán lọc metadata, RAG và cách tính RAM.
Vector database là gì và mô hình cốt lõi
Một embedding là mảng số thực (vài trăm đến vài nghìn chiều, tùy model) do một model sinh ra từ đoạn văn bản, ảnh hay sản phẩm. Model được huấn luyện sao cho hai thứ có nghĩa gần nhau thì vector cũng gần nhau. "Cách đổi mật khẩu" và "quên password thì làm sao" có vector gần nhau dù không chung từ nào.1
Vector database giải một bài toán hẹp: cho một vector truy vấn q, trả về k vector gần q nhất trong rất nhiều vector đã lưu, trong thời gian đủ nhanh cho API realtime.1 Mỗi bản ghi thường gồm ba phần:
id: khóa để nối về dữ liệu gốc.vector: mảng float32 kích thước cố định.payloadhoặc metadata: tenant_id, ngôn ngữ, ngày tạo, đoạn text gốc. Dùng để lọc và hiển thị.
Tìm chính xác k láng giềng gần nhất (exact kNN) nghĩa là so q với mọi vector. Seven Databases minh họa đúng điểm này với extension cube của Postgres: tính khoảng cách cho mọi dòng rồi sắp xếp là quét toàn bảng và chậm, nên cần thu hẹp ứng viên trước bằng một hình hộp bao quanh điểm truy vấn.2 Với 10 triệu vector 1.536 chiều, mỗi truy vấn phải làm khoảng 15 tỷ phép nhân. Không chấp nhận được cho API realtime. Vì vậy vector database dùng ANN (approximate nearest neighbor): chấp nhận bỏ sót một ít kết quả đúng để nhanh hơn nhiều.3 Chất lượng đo bằng recall@k: trong k kết quả thật sự gần nhất, index trả về được bao nhiêu phần trăm.
Độ đo tương đồng
| Độ đo | Công thức ý tưởng | Khi nào dùng |
|---|---|---|
| Cosine | Góc giữa hai vector, bỏ qua độ dài | Embedding văn bản, mặc định của nhiều model |
| Dot product (inner product) | Tổng tích từng chiều | Vector đã chuẩn hóa độ dài 1 (khi đó bằng cosine, tính nhanh hơn), hoặc model recommendation |
| Euclidean (L2) | Khoảng cách hình học | Embedding ảnh, một số model cũ |
Quy tắc thực tế: dùng đúng độ đo mà model được huấn luyện. Nếu model trả vector đã chuẩn hóa, dot product và cosine cho cùng thứ hạng.4
Kiến trúc và các loại index ANN
HNSW
HNSW (Hierarchical Navigable Small World) xây một đồ thị nhiều tầng. Mỗi vector là một node nối với khoảng M láng giềng gần nó. Tầng trên thưa, chỉ có ít node, dùng để nhảy xa. Tầng dưới cùng chứa mọi node. Truy vấn bắt đầu ở tầng trên, đi tham lam tới node gần q nhất, xuống tầng dưới và lặp lại, cuối cùng duyệt một vùng nhỏ ở tầng đáy.3
Tham số chính:
M: số cạnh mỗi node. Lớn hơn thì recall cao hơn, tốn RAM hơn.ef_construction: độ kỹ khi xây. Lớn hơn thì xây chậm hơn, đồ thị tốt hơn.ef_search: độ rộng tìm khi truy vấn. Đây là núm chỉnh recall và độ trễ lúc chạy.
HNSW cho recall cao và độ trễ thấp, hỗ trợ thêm vector từng cái một. Nhược điểm: toàn bộ đồ thị nên nằm trong RAM, xây index chậm, xóa nhiều làm đồ thị kém dần.
IVF và IVF-PQ
IVF (inverted file) chạy k-means để chia vector thành nlist cụm. Mỗi vector thuộc một cụm. Truy vấn tìm nprobe tâm cụm gần nhất rồi chỉ quét vector trong các cụm đó.
IVF tốn ít RAM hơn HNSW và xây nhanh hơn, nhưng cần dữ liệu mẫu để huấn luyện tâm cụm. Nếu phân bố dữ liệu thay đổi nhiều, phải huấn luyện lại. Ghép thêm PQ (product quantization) để nén mỗi vector xuống nhiều lần, đổi lấy recall thấp hơn. Đây là cách phổ biến khi số vector rất lớn.3
Lượng tử hóa
Ngoài PQ, các vector database hiện đại hỗ trợ lưu vector ở độ chính xác thấp hơn (số byte là phép tính từ kiểu dữ liệu, mức ảnh hưởng recall chỉ là định tính):3
| Kiểu | Byte mỗi chiều | Tiết kiệm | Ảnh hưởng recall |
|---|---|---|---|
| float32 | 4 | Gốc | Không |
| float16 / halfvec | 2 | ×2 | Rất nhỏ |
| int8 (scalar quantization) | 1 | ×4 | Nhỏ, thường bù bằng rescoring |
| Binary | 1/8 | ×32 | Lớn, phải rescoring bằng vector gốc |
Cách làm phổ biến: tìm nhanh trên vector đã nén để lấy 100 ứng viên, rồi tính lại điểm bằng vector float32 gốc (lưu trên đĩa) để chọn 10 kết quả cuối.
Data model và API
pgvector
pgvector là extension của PostgreSQL. Vector là một cột như mọi cột khác, nên bạn có transaction, join, backup và quyền truy cập sẵn có.5 Ví dụ bên dưới chỉ minh họa cú pháp; giá trị tham số là ví dụ, không phải khuyến nghị.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE doc_chunks (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL,
content text NOT NULL,
embedding vector(1536) NOT NULL
);
CREATE INDEX ON doc_chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
CREATE INDEX ON doc_chunks (tenant_id);
SET hnsw.ef_search = 100;
SELECT id, content, 1 - (embedding <=> $1) AS score
FROM doc_chunks
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 10;
Toán tử: <-> là L2, <=> là cosine distance, <#> là inner product âm. Index HNSW có giới hạn số chiều tùy phiên bản; model nhiều chiều có thể phải dùng kiểu độ chính xác thấp hơn (halfvec) hoặc giảm số chiều, cần kiểm tra tài liệu hiện hành.
Qdrant (ví dụ một hệ chuyên dụng)
await qdrant.upsert("doc_chunks", {
points: [{ id: 42, vector: embedding, payload: { tenant_id: 7, lang: "vi" } }],
});
const hits = await qdrant.search("doc_chunks", {
vector: queryEmbedding,
limit: 10,
filter: { must: [{ key: "tenant_id", match: { value: 7 } }] },
});
Mô hình tương tự ở Milvus, Pinecone, Weaviate: collection, point có id, vector, payload, và truy vấn search kèm filter.6
Bài toán lọc metadata
Đây là chỗ khó nhất trong thực tế. Có hai cách ngây thơ:
- Lọc sau (post-filter): tìm top 10 bằng HNSW rồi bỏ những kết quả không đúng tenant. Nếu tenant chỉ chiếm một phần rất nhỏ dữ liệu, gần như cả 10 kết quả bị bỏ và bạn trả về rỗng.7
- Lọc trước (pre-filter): lấy mọi id của tenant rồi so vector từng cái. Chính xác, nhưng với tenant lớn thì quay lại quét toàn bộ.
Các hệ chuyên dụng giải bằng cách lọc ngay trong lúc duyệt đồ thị. pgvector ở các bản mới có iterative index scan: tự quét tiếp khi lọc làm thiếu kết quả.7 Với multi-tenant, cách chắc chắn nhất là tách index theo tenant lớn (partition hoặc collection riêng) và để tenant nhỏ dùng chung.
Sai lầm hay gặp: Thêm WHERE tenant_id = ... vào truy vấn pgvector rồi thấy kết quả ít hơn LIMIT. Nguyên nhân là ef_search giới hạn số ứng viên HNSW trả về trước khi lọc. Bật iterative scan, tăng ef_search, hoặc partition theo tenant.
Con số và sizing
Công thức RAM cho vector thô (phép tính từ giả định kiểu float32; hệ số overhead là ước lượng, cần đo với index thật):8
RAM vector = số vector × số chiều × 4 B
RAM HNSW ≈ RAM vector + số vector × M × 2 × 4 B (danh sách cạnh)
Thực tế ≈ RAM vector × 1,2 đến 1,5 (ước lượng)
| Kịch bản | Tính | Kết quả (ước tính) |
|---|---|---|
| 1 triệu chunk, 1.536 chiều, float32 | 1M × 1.536 × 4 B | 6,1 GB thô, ~8 GB với HNSW |
| 10 triệu chunk, 1.536 chiều | 10M × 1.536 × 4 B | 61 GB thô, ~80 đến 90 GB với HNSW |
| 10 triệu chunk, 768 chiều, int8 | 10M × 768 × 1 B | 7,7 GB, cộng vector gốc trên đĩa |
| 1 tỷ vector, 768 chiều, IVF-PQ 64 B | 1B × 64 B | 64 GB mã nén, cần cluster |
Thông lượng (ước lượng định tính, phụ thuộc phần cứng và ef_search, cần load test):8
- HNSW khi dữ liệu nằm gọn trong RAM: truy vấn ở mức vài mili giây.
- Ghi: HNSW chèn chậm hơn đọc. Nạp hàng loạt nên tạo index sau khi nạp xong.
- Embedding model thường là nút cổ chai: gọi model embedding chậm hơn bước tìm vector. Cache embedding của câu hỏi lặp lại.
Ví dụ nhanh: chatbot hỗ trợ khách hàng cho 500 doanh nghiệp, mỗi doanh nghiệp 20.000 chunk. Tổng 10 triệu chunk, model 1.536 chiều, khoảng 80 GB RAM nếu giữ float32. Chuyển sang halfvec còn khoảng 40 GB, vừa một máy 64 GB. Nếu 100 QPS cao điểm (giả định) thì có thể một node và một replica là đủ, cần load test.
Khi nào dùng và khi nào không
| Lựa chọn | Hợp khi | Không hợp khi |
|---|---|---|
| pgvector | Quy mô vừa, đã có Postgres, cần join với dữ liệu nghiệp vụ, muốn ít hệ thống | Quy mô rất lớn, QPS rất cao, cần scale ngang tự động |
| Qdrant | Cần lọc payload mạnh, tự host được, muốn quantization và rescoring sẵn | Team không muốn vận hành thêm một database |
| Milvus | Quy mô rất lớn, cần nhiều loại index, tách compute và storage | Quy mô nhỏ, vì nhiều thành phần cần vận hành |
| Pinecone | Muốn dịch vụ managed hoàn toàn, serverless, trả theo dùng | Dữ liệu không được rời hạ tầng của bạn, hoặc chi phí ở quy mô lớn |
| Elasticsearch / OpenSearch kNN | Đã có cluster search, cần hybrid search (BM25 + vector) | Chỉ có nhu cầu vector thuần |
| Redis vector search | Dữ liệu nhỏ, cần độ trễ cực thấp, đã có Redis | Dữ liệu lớn hơn RAM có thể trả tiền |
Bảng trên là nhận định tổng hợp của khoá học, không phải số liệu từ một nguồn.6
Không cần vector database khi:
- Tìm kiếm theo từ khóa chính xác (mã đơn hàng, SKU, email). Dùng index thường hoặc Elasticsearch; Grokking Distributed Search mô tả inverted index là cấu trúc nền cho tìm kiếm theo từ khóa.9
- Dữ liệu rất nhỏ (vài chục nghìn vector, ước lượng). Quét toàn bộ trong RAM bằng numpy hoặc ngay trong Postgres không cần index vẫn nhanh.
- Lọc có cấu trúc là chính, ngữ nghĩa là phụ. Hãy lọc trước bằng SQL rồi xếp hạng tập nhỏ.
RAG: use case phổ biến nhất
RAG (retrieval-augmented generation) cho LLM trả lời dựa trên tài liệu riêng của bạn. Có hai luồng:
Nạp dữ liệu (offline). Tách tài liệu thành chunk vừa phải có chồng lấp, gọi model embedding, ghi vector cùng payload (tài liệu gốc, tenant, quyền truy cập). Khi tài liệu đổi, phải tính lại embedding cho các chunk liên quan. Có thể dùng change data capture để bắt thay đổi từ database nguồn và đẩy vào hàng đợi tính embedding.
Trả lời (online). Embedding câu hỏi, tìm top 20 chunk có lọc theo tenant và quyền, có thể chạy thêm reranker để chọn 5 chunk tốt nhất, rồi nhét vào prompt.10
Hai kinh nghiệm thực tế:
- Hybrid search (từ khóa + vector, gộp điểm của hai danh sách) thường tốt hơn vector thuần, nhất là với tên riêng, mã lỗi, số hiệu văn bản pháp luật.10 Phần từ khóa dựa trên inverted index.9
- Quyền truy cập phải nằm trong filter. Nếu lọc quyền sau khi lấy kết quả, bạn vừa có nguy cơ thiếu kết quả, vừa có nguy cơ lọt dữ liệu vào prompt nếu code quên lọc.
Vận hành ở quy mô nhỏ và lớn
Nhỏ (vài triệu vector, ước lượng). pgvector trên Postgres hiện có. Không thêm hệ thống mới. Chi phí gần như bằng không ngoài RAM (ước tính). Theo dõi: kích thước index, thời gian vacuum, recall trên một tập câu hỏi mẫu.11
Vừa (hàng chục triệu, ước lượng). Cân nhắc tách sang Qdrant hoặc dùng Postgres riêng cho vector để không ảnh hưởng tải giao dịch. Bật quantization. Tách collection theo tenant lớn. Dựng bộ đánh giá recall tự động: chạy exact kNN trên mẫu 1.000 câu hỏi, so với kết quả ANN mỗi khi đổi tham số.
Lớn (hàng trăm triệu trở lên, ước lượng). Hệ chuyên dụng phân tán hoặc dịch vụ managed. Shard theo id hoặc theo tenant, replica để chia tải đọc. Dùng index nén (như IVF-PQ) hoặc index lưu trên SSD để giảm RAM.11 Chi phí lúc này chủ yếu là RAM và tiền gọi model embedding khi nạp lại toàn bộ dữ liệu (một lần đổi model nghĩa là embed lại mọi thứ).
Lưu ý: Đổi model embedding làm mọi vector cũ vô nghĩa vì hai model không chung không gian. Lưu tên và phiên bản model cùng vector, và lên kế hoạch chạy song song hai index trong lúc nạp lại.
Điểm nói trong phỏng vấn
- Giải thích vì sao cần ANN. Exact kNN là O(n × d) mỗi truy vấn. ANN đổi một ít recall lấy tốc độ.23
- Nêu HNSW và IVF cùng trade-off. HNSW: recall cao, nhanh, tốn RAM. IVF-PQ: tiết kiệm RAM, recall thấp hơn, cần huấn luyện.
- Tính RAM. Số vector × số chiều × 4 B, nhân hệ số overhead (ước lượng 1,2 đến 1,5) cho index.8 10 triệu × 1.536 chiều ≈ 61 GB thô.
- Nói về lọc metadata và multi-tenant. Đây là câu hỏi follow-up hay gặp nhất.
- Chọn pgvector trước khi đề xuất hệ chuyên dụng. Lý do: ít hệ thống, có transaction, đủ cho quy mô vừa. Chỉ tách khi có con số chứng minh.
- Nêu chi phí ẩn: độ trễ embedding, embed lại khi đổi model, giữ vector đồng bộ với dữ liệu gốc.
Trong phỏng vấn: Bài ChatGPT thường hỏi phần nhớ hội thoại hoặc tra cứu tài liệu, đó là RAG với vector database. Bài Post search có thể mở rộng sang tìm theo ngữ nghĩa bằng hybrid search. Tinder và News feed dùng embedding người dùng và nội dung cho bước lấy ứng viên trước khi xếp hạng.
Nguồn trích dẫn
-
Chưa có nguồn trong thư viện: thư viện không có sách về embedding hay vector database; phần này chỉ nêu khái niệm cơ bản, không đưa số liệu sản phẩm. ↩ ↩2
-
Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.2 PostgreSQL · Day 3 · mục "Genres as a Multidimensional Hypercube" (vector n chiều, nearest-neighbor, bounding cube thay cho full-table scan). ↩ ↩2
-
Chưa có nguồn trong thư viện: thư viện không có sách về ANN, HNSW, IVF hay lượng tử hóa; chỉ nêu khái niệm cơ bản và đánh đổi định tính. ↩ ↩2 ↩3 ↩4 ↩5
-
Chưa có nguồn trong thư viện: thư viện không có sách về độ đo tương đồng cho embedding; chỉ nêu khái niệm cơ bản. ↩
-
Chưa có nguồn trong thư viện: thư viện không có sách về pgvector; ví dụ chỉ minh họa cú pháp, tham số mang tính ví dụ. ↩
-
Chưa có nguồn trong thư viện: thư viện không có sách về Qdrant, Milvus, Pinecone, Weaviate hay Elasticsearch kNN; chỉ nêu mô hình dữ liệu chung và nhận định tổng hợp. ↩ ↩2
-
Chưa có nguồn trong thư viện: thư viện không có sách về lọc metadata trong tìm kiếm vector; chỉ nêu hai cách tiếp cận ở mức khái niệm. ↩ ↩2
-
Lý thuyết · RAM tối thiểu của vector: số vector × số chiều × 4 byte (float32). Ví dụ 10 triệu vector × 768 chiều × 4 B = 30,72 GB. Chỉ mục (ví dụ đồ thị HNSW) cần thêm bộ nhớ, nên nhân thêm hệ số overhead; hệ số 1,5 đến 2 là ước lượng của khoá học, cần đo lại với dữ liệu thật. ↩ ↩2 ↩3
-
Grokking Modern System Design · ch.21 Distributed Search · mục "Inverted index". ↩ ↩2
-
Chưa có nguồn trong thư viện: thư viện không có sách về RAG, reranker hay hybrid search; chỉ nêu khái niệm cơ bản. ↩ ↩2
-
Chưa có nguồn trong thư viện: các mốc quy mô và cách vận hành là nhận định của khoá học, không phải số liệu từ sách. ↩ ↩2