Hướng dẫn sử dụng ChromaDB với Python: Xây dựng Vector Database cục bộ siêu nhẹ cho ứng dụng AI

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

2 giờ sáng, chuông PagerDuty réo liên hồi. Mở terminal kiểm tra production server, lệnh top và dmesg hiện ngay dòng chữ quen thuộc: Out of memory: Killed process (python3). Con bot tra cứu tài liệu nội bộ đã sập hoàn toàn. Nguyên nhân? Module vector tự chế ngốn sạch 16GB RAM của node backend.

Sự cố Production: Khi giải pháp lưu vector thô phản chủ

Trước đó, team dựng nhanh tính năng tìm kiếm để kịp tiến độ bàn giao. Mỗi khi người dùng đặt câu hỏi, ứng dụng load toàn bộ mảng NumPy gồm 50.000 vector embedding (kích thước 768 chiều) thẳng vào RAM rồi tính cosine similarity. Mọi thứ vẫn ổn cho đến đợt sync dữ liệu định kỳ đêm qua. Lượng tài liệu tăng gấp đôi lên 100.000 bản ghi. RAM chạm trần 16GB, swap đầy nghẹt và Linux OOM killer lập tức khai tử tiến trình Python.

Khi xây dựng ứng dụng AI quy mô vừa và nhỏ, các đội ngũ kỹ thuật rất dễ rơi vào hai thái cực:

  • Quá thô sơ: Lưu embedding vào file pickle hoặc mảng NumPy trong RAM. Cách này nhanh khi làm prototype nhưng CPU sẽ nghẽn nặng và sập bộ nhớ khi lượng bản ghi chạm mốc vài chục nghìn.
  • Quá cồng kềnh: Dựng cả cụm Milvus hay Elasticsearch độc lập. Bạn sẽ tốn thêm 4–8GB RAM chỉ để nuôi cụm hạ tầng, kèm theo gánh nặng bảo trì mạng và đồng bộ giữa các service.

Bản chất vấn đề: Vì sao tìm kiếm tuyến tính làm sập RAM và CPU?

Để tìm kiếm ngữ nghĩa, mỗi đoạn text được mã hóa thành một vector nhiều chiều. Nếu dùng vòng lặp NumPy để so khớp câu hỏi với $N$ vector, độ phức tạp thuật toán là $O(N)$.

Với $N = 500$, độ trễ gần như bằng 0. Nhưng khi $N = 100.000$, CPU phải gánh hàng triệu phép nhân vô hướng cho mỗi lượt query. Nguy hiểm hơn, việc giữ nguyên ma trận dữ liệu trên RAM mà không có cơ chế phân trang (paging), lập chỉ mục không gian (HNSW) hay lưu trữ xuống đĩa sẽ biến ứng dụng thành quả bom nổ chậm.

Cân nhắc giải pháp: Faiss, Milvus hay ChromaDB?

Ngay trong ca trực đêm đó, 3 phương án được đặt lên bàn cân:

1. Tối ưu lại mảng NumPy hoặc dùng FAISS thuần

FAISS tối ưu tính toán vector trên C++ cực nhanh. Dù vậy, FAISS thuần chỉ là thư viện thuật toán, không phải database. Bạn vẫn phải tự viết code map ID với text gốc, quản lý metadata và tự xử lý ghi file an toàn khi khởi động lại. Rủi ro lỗi logic dữ liệu vẫn còn nguyên.

2. Dựng cụm Vector Database rời (Milvus, Qdrant)

Các giải pháp này sinh ra cho hệ thống enterprise với hàng chục triệu vector. Tuy nhiên, với bot nội bộ hoặc ứng dụng RAG phục vụ dưới 500.000 tài liệu, việc gánh thêm Docker cluster với Zookeeper hay MinIO chỉ làm phức tạp hóa hệ thống.

3. Nhúng ChromaDB trực tiếp vào tiến trình Python

ChromaDB hoạt động như một SQLite trong thế giới vector database. Thư viện chạy trực tiếp trong tiến trình ứng dụng, tự động build index HNSW, map metadata chuẩn xác và lưu bền vững xuống ổ cứng chỉ với vài dòng cấu hình.

Quy trình triển khai ChromaDB PersistentClient chuẩn Production

Chuyển đổi kho vector sang ChromaDB ở chế độ lưu trữ đĩa (PersistentClient) là phương án cứu vãn hệ thống nhanh nhất. Dưới đây là 5 bước triển khai trực tiếp vào mã nguồn.

Bước 1: Cài đặt thư viện

Cài đặt ChromaDB qua pip:

pip install chromadb

Bước 2: Khởi tạo Persistent Client lưu trữ xuống đĩa

Đừng dùng chromadb.Client() mặc định trên production vì dữ liệu sẽ mất trắng khi restart app. Hãy chỉ định rõ đường dẫn lưu trữ local:

import chromadb
from chromadb.config import Settings

# Khởi tạo client lưu trữ dữ liệu bền vững tại thư mục local
client = chromadb.PersistentClient(
    path="./chroma_data",
    settings=Settings(allow_reset=True, anonymized_telemetry=False)
)

# Tạo hoặc load collection đã có
# Đổi thuật toán đo khoảng cách mặc định từ L2 sang Cosine
collection = client.get_or_create_collection(
    name="tech_docs",
    metadata={"hnsw:space": "cosine"}
)

Bước 3: Nạp tài liệu và tự động tạo Embedding

ChromaDB tích hợp sẵn mô hình all-MiniLM-L6-v2 chạy qua onnxruntime. Bạn chỉ cần truyền text thô và metadata đi kèm:

documents = [
    "Hướng dẫn cấu hình Nginx reverse proxy chịu tải cao và cache tĩnh.",
    "Cách khắc phục lỗi Out of Memory (OOM) trên Linux server khi chạy Python.",
    "Thiết lập xác thực SSH key và vô hiệu hóa password login trên Ubuntu 22.04.",
    "Tối ưu hóa truy vấn SQL với Index và phân tích Execution Plan trong PostgreSQL."
]

metadatas = [
    {"category": "devops", "priority": 1},
    {"category": "troubleshooting", "priority": 1},
    {"category": "security", "priority": 2},
    {"category": "database", "priority": 2}
]

ids = ["doc_001", "doc_002", "doc_003", "doc_004"]

# Nạp batch dữ liệu vào collection
collection.add(
    documents=documents,
    metadatas=metadatas,
    ids=ids
)

print(f"Tổng số bản ghi hiện có: {collection.count()}")

Bước 4: Truy vấn ngữ nghĩa kết hợp lọc Metadata

Khi có câu hỏi từ người dùng, ChromaDB tự vector hóa câu query và trả về các đoạn tài liệu phù hợp nhất:

# Tìm kiếm tài liệu liên quan đến lỗi server tràn bộ nhớ
results = collection.query(
    query_texts=["Server bị tràn RAM sập tiến trình thì sửa thế nào?"],
    n_results=2,
    where={"category": "troubleshooting"}  # Lọc chính xác theo metadata
)

for i, doc in enumerate(results["documents"][0]):
    doc_id = results["ids"][0][i]
    distance = results["distances"][0][i]
    print(f"[{doc_id}] Cosine distance: {distance:.4f} -> {doc}")

Bước 5: Thao tác Update và Delete (CRUD) theo ID

Khi nội dung tài liệu thay đổi, bạn có thể cập nhật hoặc xóa trực tiếp qua ID mà không cần rebuild toàn bộ index:

# Cập nhật nội dung và metadata mới
collection.update(
    ids=["doc_002"],
    documents=["Cách xử lý lỗi OOM killer trên Linux và cấu hình Swap file chi tiết."],
    metadatas=[{"category": "troubleshooting", "priority": 1, "updated": "2026-10-02"}]
)

# Xóa tài liệu lỗi thời
collection.delete(ids=["doc_001"])

Xử lý sự cố thực tế khi deploy ChromaDB

Hai vấn đề phổ biến nhất bạn sẽ gặp khi đưa ChromaDB lên môi trường production:

  • Lỗi SQLite version trên Linux (< 3.35.0): Trên các hệ điều hành như CentOS 7 hoặc Ubuntu cũ, bạn sẽ gặp lỗi RuntimeError: Your system has an unsupported version of sqlite3. Cách xử lý gọn nhất là cài pysqlite3-binary và chèn đoạn code override này lên dòng đầu tiên của file chạy chính:
    __import__('pysqlite3')
    import sys
    sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
    
  • Lỗi lệch số chiều vector (Dimension Mismatch): Nếu đổi từ embedding mặc định (384 chiều) sang OpenAI text-embedding-3-small (1536 chiều), không được nạp đè vào collection cũ. Hãy khởi tạo collection mới để tránh xung đột cấu trúc index HNSW.

Kết quả sau khi refactor: RAM của node backend giảm từ 16GB xuống chỉ còn 280MB. Độ trễ truy vấn cho 100.000 bản ghi duy trì ổn định ở mức 8–12ms, giải quyết dứt điểm tình trạng sập tiến trình.

Share: