Cài đặt và quản trị Milvus với Attu: Triển khai Vector Database cho RAG quy mô lớn

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

Quick start: Dựng Milvus Standalone và Attu GUI trong 5 phút

Khi kho dữ liệu RAG vượt mốc 5-10 triệu vector, các thư viện in-memory như FAISS hay Chroma bắt đầu ngốn RAM và khó mở rộng theo cụm. Milvus giải quyết triệt để vấn đề này nhờ kiến trúc phân tán. Bạn có thể dựng nhanh toàn bộ môi trường bằng Docker Compose.

Tạo file docker-compose.yml gom đủ 4 thành phần: Milvus server, MinIO (lưu file index), Etcd (lưu metadata) và Attu (giao diện web):

version: '3.5'

services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ./volumes/etcd:/etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    volumes:
      - ./volumes/minio:/minio_data
    command: minio server /minio_data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.3.4
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes:
      - ./volumes/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - "etcd"
      - "minio"

  attu:
    container_name: milvus-attu
    image: zilliz/attu:v2.3.4
    ports:
      - "8000:3000"
    environment:
      MILVUS_URL: milvus-standalone:19530
    depends_on:
      - "standalone"

Khởi động toàn bộ stack bằng một lệnh duy nhất:

docker compose up -d

Đợi khoảng 30 giây cho các service ổn định, sau đó mở trình duyệt tại http://localhost:8000. Điền host milvus-standalone:19530 để đăng nhập dashboard Attu.

Kiến trúc Milvus và vai trò của Attu

1. Cơ chế vận hành của Milvus

Milvus tách biệt hoàn toàn giữa tầng tính toán (Compute) và tầng lưu trữ (Storage). Nhờ thiết kế này, bạn có thể scale-out Query Node độc lập khi traffic tìm kiếm tăng vọt:

  • Etcd: Lưu metadata của collection, schema, thông tin partition và trạng thái cluster.
  • MinIO / S3: Lưu vector thô, file log và các file index. Node compute gặp sự cố khởi động lại cũng không lo mất dữ liệu.
  • Query Node & Data Node: Query Node nạp index vào RAM để phục vụ tìm kiếm mili-giây. Data Node gom dữ liệu ghi (insert) thành các immutable segment.

2. Attu giúp ích gì cho vận hành?

Attu là GUI chính thức do Zilliz phát triển. Giao diện này giúp bạn bớt phải viết script thủ công mỗi khi cần kiểm tra dữ liệu:

  • Tạo, sửa, xóa Collection và Partition trực quan.
  • Kiểm tra trạng thái nạp bộ nhớ (Loaded/Unloaded) của từng collection.
  • Test nhanh câu lệnh Vector Search và lọc Scalar metadata trực tiếp trên web.
  • Theo dõi số lượng entity và kích thước segment theo thời gian thực.

Thao tác dữ liệu với PyMilvus và tối ưu Index

Hai yếu tố quyết định hiệu năng RAG là: thiết kế schema linh hoạt với Dynamic Field và chọn đúng thuật toán index.

1. Khởi tạo Collection cho hệ thống RAG

Dưới đây là script Python tạo collection chứa document chunk và vector 1536 chiều (chuẩn OpenAI text-embedding-3-small):

from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection

# Kết nối tới Milvus
connections.connect("default", host="localhost", port="19530")

collection_name = "rag_enterprise_docs"

# Khai báo Schema
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="doc_id", dtype=DataType.VARCHAR, max_length=64),
    FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536)
]

# Bật enable_dynamic_field để thoải mái lưu thêm metadata JSON tùy ý
schema = CollectionSchema(fields, description="RAG Knowledge Store", enable_dynamic_field=True)
collection = Collection(name=collection_name, schema=schema)

print(f"Collection {collection_name} đã sẵn sàng!")

2. Chọn Index: HNSW hay IVF_FLAT?

Nếu không đánh index, Milvus phải quét toàn bộ bảng (Flat search). Với 1 triệu vector, query sẽ trễ hàng trăm mili-giây. Lựa chọn thực tế:

  • HNSW: Tốc độ tìm kiếm cực nhanh (thường < 5ms trên 1 triệu vector), Recall đạt 98-99%. Điểm trừ: tốn RAM để dựng đồ thị.
  • IVF_FLAT: Gom vector vào các cụm centroid. Index này tốn ít RAM hơn HNSW, thích hợp cho máy chủ cấu hình vừa phải.

Tạo index HNSW với metric khoảng cách Cosine:

index_params = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
    "params": {"M": 16, "efConstruction": 200}
}

collection.create_index(field_name="embedding", index_params=index_params)

# Nạp collection vào RAM sau khi build index
collection.load()
print("Index HNSW build xong, collection đã được load vào RAM.")

3. Hybrid Search: Lọc Metadata kết hợp Vector Similarity

Trong thực tế, bạn thường cần tìm chunk liên quan nhưng phải thuộc về một tài liệu hoặc phòng ban cụ thể. Dùng biểu thức expr để lọc:

query_vector = [0.015] * 1536

search_params = {"metric_type": "COSINE", "params": {"ef": 64}}

results = collection.search(
    data=[query_vector],
    anns_field="embedding",
    param=search_params,
    limit=3,
    expr='doc_id == "finance_q1_2026"', # Lọc metadata trước khi tính tương đồng
    output_fields=["doc_id", "content"]
)

for hits in results:
    for hit in hits:
        print(f"Score: {hit.distance:.4f} | Content: {hit.entity.get('content')}")

Kinh nghiệm thực chiến khi vận hành Milvus

  • Nhớ gọi collection.load(): Milvus chỉ query được trên các segment đã nạp vào RAM. Nếu query trả về rỗng, mở Attu kiểm tra xem collection đã chuyển sang trạng thái Loaded hay chưa.
  • Ước tính dung lượng RAM cho HNSW: Dùng công thức: Số vector * Số chiều * 4 bytes * 1.5. Ví dụ: 5 triệu vector 1536 chiều cần khoảng 5,000,000 * 1536 * 4 * 1.5 ≈ 46 GB RAM. Hãy chuẩn bị máy chủ tối thiểu 64GB RAM.
  • Giới hạn số lượng Partition: Chỉ nên giữ dưới 64 partition cho mỗi collection. Quá nhiều partition nhỏ sẽ làm phân mảnh bộ nhớ của Query Node. Muốn lọc dữ liệu chi tiết, hãy dùng Dynamic Metadata và biểu thức expr.
  • Bật Etcd Compact định kỳ: Không dọn dẹp snapshot sẽ khiến file Etcd phình to vượt quota 2GB/4GB, dẫn đến treo cluster. Luôn giữ biến môi trường ETCD_AUTO_COMPACTION_RETENTION=1000.
  • Monitor qua Prometheus: Milvus mở sẵn endpoint metrics ở port 9091/metrics. Bạn nên kéo về Grafana để theo dõi QPS, Query Latency và số lượng unindexed segment.
Share: