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

3.12 Dựng, cập nhật và xoá index

Mọi ví dụ ở các mục trước đều giả định index đã có sẵn và không đổi. Trong sản phẩm thật, tài liệu được thêm, sửa, xoá liên tục — và cách một index xử lý cập nhật quyết định phần lớn kiến trúc vận hành của nó.

Cách ngây thơ (một hash map term → postings trong RAM) chết ở corpus lớn. Hai thuật toán cổ điển:

CáchÝ tưởngĐánh đổi
BSBI (blocked sort-based)đọc từng khối tài liệu → sinh cặp (termID, docID)sắp → ghi ra đĩa → trộn nhiều đườngCần bảng term→termID toàn cục
SPIMI (single-pass in-memory)mỗi khối tự dựng dictionary riêng, đầy RAM thì ghi ra một index con → trộn sauMột lượt, không cần bảng toàn cục. Cách hiện đại dùng

Cả hai đều dẫn tới cùng một cấu trúc vận hành: nhiều index con, được trộn dần.

Đây là mô hình đáng hiểu nhất, vì gần như mọi công cụ tìm kiếm hiện đại đều làm giống:

tài liệu mới → buffer trong RAM
→ (refresh, mặc định 1 s) → segment mới, BẤT BIẾN, tìm được ngay
→ (merge, ngầm) → nhiều segment nhỏ gộp thành segment lớn

Hệ quả trực tiếp, cần biết trước khi gặp:

Sự thậtHệ quả vận hành
Segment bất biếnKhông có “sửa tài liệu”. Sửa = xoá + thêm mới
Xoá là cờ đánh dấu (tombstone)Tài liệu đã xoá vẫn nằm trên đĩa, vẫn tính vào df, cho tới lúc merge
df lệch tạm thờiĐiểm BM25 có thể xê dịch nhẹ sau xoá hàng loạt, tự đúng lại sau merge
Merge tốn I/O đột biếnp99 latency có gai. Đây là nguồn “chậm ngẫu nhiên” hay bị quy oan cho truy vấn
Cập nhật nhiều = rác nhiềuTài liệu bị sửa 10 lần để lại 9 bản chết chờ merge

refresh_interval là nút xoay quan trọng nhất mà ít người xoay. Mặc định 1 giây nghĩa là “gần thời gian thực” — và mỗi lần refresh sinh một segment nhỏ, kéo theo merge. Nếu sản phẩm chịu được độ trễ dữ liệu 30 giây, đặt 30s thường giảm đáng kể tải ghi và làm p99 phẳng hơn. Với lần nạp lớn (bulk import): tắt refresh, nạp, rồi bật lại.

tsvector + GIN index nằm trong transaction. Không có segment, không có refresh — commit là thấy. Đổi lại, cập nhật GIN đắt hơn, và Postgres bù bằng fastupdate (danh sách chờ chèn) — thứ tự đến lượt nó tạo ra gai latency khi danh sách chờ được xả.

Một dòng để chọn: cần nhất quán đọc-sau-ghi tức thì và corpus vừa phải → Postgres. Cần throughput ghi cao, corpus lớn, chịu được độ trễ dữ liệu vài giây → Lucene/Elasticsearch.

Reindex: việc sẽ phải làm, nên chuẩn bị trước

Phần tiêu đề “Reindex: việc sẽ phải làm, nên chuẩn bị trước”

Bạn sẽ phải index lại toàn bộ. Chắc chắn. Vì:

  • đổi tokenizer (và 3.1 cho thấy bạn sẽ đổi),
  • đổi chiến lược chunking (Tầng 4),
  • đổi model embedding — vector cũ và mới không so sánh được với nhau.

Nên thiết kế cho việc đó từ đầu:

ViệcVì sao
Ghi alias, không ghi thẳng tên indexĐổi index mới = trỏ lại alias, tức thời, có đường lùi
Giữ được nguồn sự thật để dựng lạiIndex là dữ liệu dẫn xuất. Nếu không dựng lại được từ nguồn, bạn không có backup mà có một điểm chết
Đo lại golden set trước khi đổi aliasReindex là lúc dễ làm tụt chất lượng nhất — và cũng là lúc dễ không nhận ra nhất (Phần 2)
Ghi phiên bản cấu hình vào index“Index này dùng tokenizer nào, model nào” — sáu tháng sau không ai nhớ
Phần 3 — Nền tảng kỹ thuật