7.6 Optimizer: ba loại, và vì sao latency nhấp nháy
Optimizer là tiến trình nền dọn dẹp segment. Nó là nguyên nhân số một của hiện tượng “Qdrant lúc nhanh lúc chậm mà không hiểu vì sao”.
Lý do rất trực tiếp: optimizer và truy vấn tranh nhau đúng một bộ tài nguyên — CPU, băng thông bộ nhớ, I/O. Qdrant nói thẳng điều này trong docs. Bạn không tắt được sự tranh chấp đó; bạn chỉ chọn được nó xảy ra khi nào và ở mức nào.
Ba optimizer
Phần tiêu đề “Ba optimizer”| Optimizer | Kích hoạt khi | Việc nó làm |
|---|---|---|
| Vacuum | tỷ lệ điểm đã xoá trong một segment ≥ deleted_threshold và segment có ≥ vacuum_min_vector_number vector | Dựng lại segment, thu hồi chỗ của điểm đã xoá |
| Merge | số segment nhiều hơn default_segment_number mong muốn | Gộp các segment nhỏ nhất lại |
| Indexing | lượng dữ liệu trong segment vượt indexing_threshold_kb (hoặc memmap_threshold) | Xây HNSW, chuyển sang mmap, áp quantization |
Mặc định, lấy từ config.yaml v1.19.0:
optimizers: deleted_threshold: 0.2 # 20% điểm đã xoá thì mới vacuum vacuum_min_vector_number: 1000 # segment nhỏ hơn thế thì kệ nó default_segment_number: 0 # 0 = tự chọn theo số CPU max_segment_size_kb: null # null = tự chọn theo số CPU indexing_threshold_kb: 10000 # 0 = tắt hẳn việc xây index flush_interval_sec: 5 max_optimization_threads: null # null = tự do; 0 = TẮT optimizerNhớ lại 7.4: các ngưỡng _kb đo bằng kilobyte, quy ước
1 KB ≈ 1 vector 256 chiều. indexing_threshold_kb: 10000 trên embedding 1536 chiều là
khoảng 1.700 điểm, không phải 10.000.
Proxy segment — vì sao tối ưu không làm rớt truy vấn
Phần tiêu đề “Proxy segment — vì sao tối ưu không làm rớt truy vấn”Câu hỏi tự nhiên: nếu optimizer đang dựng lại một segment, truy vấn chạm vào segment đó thì sao?
Câu trả lời là proxy segment. Trong lúc dựng lại, Qdrant bọc segment cũ bằng một lớp proxy: đọc vẫn phục vụ được từ bản cũ, ghi mới được ghi vào cả nơi cần thiết, và khi bản mới xong thì tráo. Không có cửa sổ nào mà dữ liệu biến mất.
Đây là điểm khác biệt thật giữa một vector database và một thư viện ANN: với hnswlib, “dựng lại index” nghĩa là bạn tự lo phần phục vụ trong lúc dựng.
Vì sao latency nhấp nháy: cơ chế
Phần tiêu đề “Vì sao latency nhấp nháy: cơ chế”Tải ghi cao │ ├──► điểm mới dồn vào segment appendable │ │ │ ▼ │ segment này CHƯA CÓ HNSW │ │ ▼ ▼optimizer cố xây mọi truy vấn phải QUÉT TUẦN TỰ segment đóindex │ │ ▼ └──► tranh CPU/IO ◄─── truy vấn chậm đi │ ▼ optimizer càng chậm → segment chưa index càng lớn → truy vấn càng chậmĐây là một vòng lặp dương. Khi nó bắt đầu, hệ không tự thoát ra — nó chỉ thoát khi tải ghi giảm.
Bài đo của chính Qdrant về hiện tượng này cho các con số sau (số dẫn từ nguồn ngoài,
xem Đọc thêm):
| Tình huống | Median latency | Ghi chú |
|---|---|---|
| Index liên tục, ổn định | ~4 ms | mốc so sánh — suy ra từ tỷ lệ 60× mà bài nêu, không phải số bài ghi trực tiếp |
| Tắt hẳn indexing | 256,6 ms | ~60× chậm hơn — đây là chi phí thật của quét tuần tự |
Bật lại index sau khi hoãn, không có prevent_unoptimized | 2,7 s (đuôi tới 12,1 s) | truy vấn quét đi quét lại đúng cái backlog đang phình |
Cùng tình huống, có prevent_unoptimized | 10,2 ms (từ 780 ms) | cải thiện ~76× ở giai đoạn tiêu backlog |
Đọc bảng này cho đúng: hoãn indexing để “nạp cho nhanh” là một sai lầm đắt. Trực giác “tắt index lúc nạp, bật lại sau” nghe hợp lý nhưng bài đo bác bỏ nó — giai đoạn phục hồi sau đó tệ hơn nhiều so với việc cứ để index chạy liên tục.
prevent_unoptimized — cách chữa gọn nhất
Phần tiêu đề “prevent_unoptimized — cách chữa gọn nhất”Có từ v1.17.1, vẫn được đánh dấu là thử nghiệm. Ý tưởng một dòng:
Điểm vừa ghi vẫn được lưu bền vững, nhưng không cho truy vấn nhìn thấy cho tới khi segment chứa nó tối ưu xong.
Nghĩa là truy vấn không bao giờ phải quét segment chưa index. Vòng lặp dương ở trên bị cắt.
Cái giá — và phải nói rõ vì nó không nhỏ:
- Điểm mới không tìm thấy được ngay. Bạn đổi độ tươi lấy độ ổn định. Chỉ dùng khi trễ vài giây tới vài chục giây trước lúc dữ liệu tìm thấy được là chấp nhận được.
- Bắt buộc
wait=falsetrên mọi request ghi. Vớiwait=true, client ngồi chờ tới lúc index xong, gây timeout và tắc nghẽn đầu hàng (7.5).
Thứ tự xử lý khi truy vấn chậm dưới tải ghi
Phần tiêu đề “Thứ tự xử lý khi truy vấn chậm dưới tải ghi”Qdrant đưa ra một danh sách chín bước theo thứ tự. Đây là bản rút gọn có chú giải, và thứ tự quan trọng — đừng nhảy xuống bước 8 trước:
| # | Việc | Vì sao ở vị trí này |
|---|---|---|
| 1 | Bật prevent_unoptimized (kèm wait=false) | Rẻ nhất, tác dụng lớn nhất, hoàn tác dễ |
| 2 | Giảm kích thước batch ghi | Batch lớn tạo cú sốc I/O; đổi throughput lấy latency |
| 3 | Giảm optimizer_cpu_budget | Cho optimizer ít CPU hơn — nó chậm lại, truy vấn nhanh lên |
| 4 | Chỉnh max_optimization_threads, max_indexing_threads | Tinh chỉnh của bước 3 |
| 5 | Dùng delayed fan-out khi có replica | Không phải replica nào cũng bị hỏi cùng lúc |
| 6 | Bật I/O bất đồng bộ (io_uring, chỉ Linux) | Giảm chờ I/O của rescore/đọc nguội |
| 7 | Giảm max_segment_size | Segment nhỏ dựng nhanh hơn; đổi latency ổn định lấy tốc độ phục hồi |
| 8 | Thêm replica (scale ngang) | Tốn tiền |
| 9 | Máy to hơn (scale dọc) | Tốn tiền hơn |
Về optimizer_cpu_budget: mặc định 0 nghĩa là tự chọn và luôn chừa lại ít nhất một
CPU. Số âm nghĩa là “trừ đi từng ấy CPU khỏi số CPU khả dụng” — đây thường là cách diễn
đạt đúng ý bạn hơn là đặt một con số tuyệt đối.
Về max_optimization_threads: đặt 0 sẽ tắt hoàn toàn optimizer. Đừng làm vậy trừ khi
bạn hiểu rõ hậu quả ở bảng đo phía trên.
Qdrant còn khuyên một điều dễ bỏ qua: nếu đĩa cho phép, hãy đặt deleted_threshold cao
hơn mặc định 0,2 — để vacuum không chen vào giữa lúc đang phục vụ truy vấn.
Cách quan sát
Phần tiêu đề “Cách quan sát”Ba nơi để nhìn, theo thứ tự tăng dần độ chi tiết:
- Màu collection — vàng mãi = optimizer chưa đuổi kịp (7.2).
/collections/{name}— sopoints_countvớiindexed_vectors_count. Khoảng cách giữa hai số này chính là phần đang bị quét tuần tự./metricsvà/telemetry— số tác vụ tối ưu, latency theo endpoint (7.20).
Một metric duy nhất nếu chỉ được chọn một: khoảng cách giữa
points_countvàindexed_vectors_count. Nó đi trước triệu chứng latency, nên nó là cảnh báo sớm chứ không phải khám nghiệm tử thi.