Bỏ qua để đến nội dung
Search & RAG

7.2 Collection, point, vector, payload

Bốn khái niệm. Học xong bốn cái này là bạn đọc được mọi request của Qdrant.

Collection "tai_lieu_ho_tro" ← đơn vị cấu hình: số chiều, khoảng cách, shard
└── Point id = 42 ← đơn vị dữ liệu
├── vector(s) [0.12, -0.03, …] ← thứ dùng để đo "giống nhau"
└── payload {"phong_ban": "ktoan", "cap_nhat": "2026-08-01", "text": "…"}
← thứ dùng để lọc, và để trả về cho ứng dụng

Một câu để nhớ: vector quyết định thứ tự, payload quyết định ai được vào cuộc chơi.

Collection — và bốn quyết định không sửa được về sau

Phần tiêu đề “Collection — và bốn quyết định không sửa được về sau”

Collection là đơn vị bạn tạo, cấu hình, sharding và backup. Vấn đề: một vài lựa chọn khi tạo collection đóng đinh vĩnh viễn hoặc cực đắt để đổi. Liệt kê ra đây vì đây là chỗ người mới trả giá nhiều nhất:

Quyết địnhĐổi được sau không?Hậu quả nếu chọn sai
Số chiều của mỗi named vectorKhôngĐổi model embedding sang số chiều khác ⇒ tạo collection mới, nạp lại toàn bộ
Khoảng cách (Cosine, Dot, Euclid, Manhattan)KhôngĐiểm số vô nghĩa, hoặc lệch hẳn thứ hạng
shard_numberKhông (bản tự host); Qdrant Cloud có resharding từ v1.13Muốn thêm node mà không chia nhỏ được ⇒ nạp lại
sharding_method (auto / custom)KhôngMất khả năng gom một tenant vào một shard

Ngược lại, những thứ đổi được lúc chạy: hnsw_config, optimizers_config, quantization_config, memory tier, replication_factor, strict_mode_config, và việc thêm/xoá named vector (từ v1.18). Nghĩa là: đừng tối ưu hiệu năng lúc tạo collection. Hãy chọn đúng bốn thứ ở bảng trên, rồi tinh chỉnh phần còn lại sau khi đã đo.

Chọn khoảng cách nào. Nếu embedding model của bạn được huấn luyện với cosine similarity (đa số model câu/đoạn hiện nay), dùng Cosine. Nếu vector đã được chuẩn hoá về độ dài 1 thì Dot cho cùng thứ hạng mà rẻ hơn một phép chia. EuclidManhattan chỉ dùng khi bạn biết chắc mình cần — và lưu ý Manhattan (L1) làm hỏng phần lớn lựa chọn quantization (7.10).

Alias — cách thay index mà không rớt dịch vụ

Phần tiêu đề “Alias — cách thay index mà không rớt dịch vụ”

Collection có thể có alias: một tên khác trỏ tới nó, và việc đổi alias là nguyên tử. Đây là công cụ triển khai quan trọng hơn vẻ ngoài của nó:

ứng dụng luôn hỏi: "tai_lieu" (alias)
hôm nay ───────┴──► tai_lieu_v3 (model embedding cũ)
sau khi nạp xong ──► tai_lieu_v4 (model mới, số chiều khác)

Vì số chiều không đổi được, đổi model embedding luôn có nghĩa là tạo collection mới. Alias là thứ biến việc đó thành một thao tác đổi tên thay vì một lần downtime. Ghi chú: alias không nằm trong snapshot cấp collection (7.19).

Bốn màu, và mỗi màu có nghĩa vận hành rõ ràng:

MàuNghĩaBạn nên làm gì
🟢 greenSẵn sàngKhông gì cả
🟡 yellowĐang tối ưu (optimizer chạy)Bình thường sau khi nạp dữ liệu. Nhưng nếu vàng mãi thì xem 7.6
⚫ greyTối ưu đang chờ, cần một thao tác cập nhật để tiếp tụcGửi một update bất kỳ
🔴 redLỗi không phục hồi đượcĐọc log; nghĩ tới snapshot

Point là một bản ghi: một ID, một hoặc nhiều vector, và một payload.

ID chỉ nhận hai kiểu: số nguyên không dấu 64 bit, hoặc UUID. Không có chuỗi tự do. Đây là chỗ vấp đầu tiên của gần như mọi người: "doc-123#chunk-4" không phải một ID hợp lệ.

Cách xử lý đúng, và cũng là cách nên làm ngay từ đầu:

Băm khoá nghiệp vụ của bạn thành UUID, rồi cất khoá gốc vào payload. ID để Qdrant dùng; payload để bạn đọc. Dùng UUIDv5 (băm xác định từ một namespace) thì cùng một chunk luôn ra cùng một ID — nghĩa là nạp lại tài liệu sẽ ghi đè đúng điểm cũ thay vì sinh trùng lặp.

Điều này quan trọng hơn nó có vẻ: upsert trong Qdrant thay thế toàn bộ point. Nếu ID không xác định được từ nội dung, mỗi lần bạn nạp lại tài liệu là một lần corpus phình lên với các bản sao ma — và recall@k của bạn tụt vì top-k bị chiếm bởi các bản sao.

Xoá, và vì sao dữ liệu đã xoá vẫn còn tốn chỗ

Phần tiêu đề “Xoá, và vì sao dữ liệu đã xoá vẫn còn tốn chỗ”

Xoá trong Qdrant là xoá mềm: điểm bị đánh dấu, còn chỗ trống chỉ được thu hồi khi vacuum optimizer chạy — theo mặc định là khi tỷ lệ điểm đã xoá trong một segment vượt deleted_threshold: 0.2 và segment có ít nhất vacuum_min_vector_number: 1000 vector (config.yaml v1.19.0).

Hệ quả thực tế: sau một đợt xoá lớn, dung lượng không giảm ngay, và các điểm đã xoá vẫn nằm trong đồ thị HNSW làm chậm việc đi đồ thị cho tới khi segment được xây lại. Chi tiết ở 7.6.

Payload — JSON tuỳ ý, nhưng đừng đối xử với nó như JSON tuỳ ý

Phần tiêu đề “Payload — JSON tuỳ ý, nhưng đừng đối xử với nó như JSON tuỳ ý”

Payload là một object JSON bất kỳ gắn vào point. Không cần khai báo schema. Điều đó tiện, và cũng là cái bẫy: payload không được index thì filter trên nó vẫn chạy, chỉ là chạy chậm và làm hỏng cơ chế filterable HNSW (7.9).

Ba nhóm trường trong payload, nên tách rõ trong đầu:

NhómVí dụCó cần index không?Có nên để RAM không?
Trường lọcphong_ban, tenant_id, cap_nhat, ngon_nguBắt buộcCó — Qdrant giữ trường đã index trong RAM bất kể payload storage nằm đâu
Trường trả vềtext, tieu_de, urlKhôngKhông — đây là phần nặng, để trên đĩa
Trường vô dụngtoàn bộ JSON gốc “cho chắc”KhôngXoá nó đi

Nhóm thứ ba là nguồn tốn RAM lặng lẽ hay gặp nhất. Mặc định của Qdrant là on_disk_payload: true (nay được thay bằng payload.memory: cold), nghĩa là payload đọc từ đĩa mỗi lần cần — đúng với trường trả về, và không ảnh hưởng tới trường đã index vì những trường đó vẫn nằm trong RAM.

Qdrant hiểu đường dẫn kiểu chấm và phép chiếu mảng: "quoc_gia.ten", "thanh_pho[].dan_so". Nhưng lọc trên mảng object có một cái bẫy nghiêm trọng đủ để dành riêng một mục — xem 7.13, phần nested.

Ba câu hỏi để tự kiểm mô hình dữ liệu

Phần tiêu đề “Ba câu hỏi để tự kiểm mô hình dữ liệu”

Trước khi nạp một triệu điểm, trả lời ba câu này. Trả lời sai thì phải nạp lại:

  1. Số chiều và khoảng cách có khớp với model embedding không? Kiểm bằng cách tính cosine của một cặp câu bạn biết là giống nhau, so với điểm Qdrant trả về.
  2. ID có sinh ra được từ nội dung không? Nếu không, bạn không có cách nào nạp lại mà không sinh bản sao.
  3. Những trường nào sẽ xuất hiện trong filter? Đó chính xác là danh sách payload index bạn phải tạo trước khi nạp dữ liệu (7.9).
Phần 7 — Qdrant