Một cơ sở dữ liệu chuyên dụng không chỉ chạy thuật toán tìm kiếm trên bộ nhớ RAM, mà còn phải giải quyết bài toán lưu trữ bền vững (persistence) trên đĩa cứng sao cho hiệu năng đọc/ghi không bị suy giảm.

Bài học này sẽ hướng dẫn bạn thiết kế kiến trúc Lưu trữ Hybrid (Hybrid Storage) - một mô hình chia tách vật lý tối ưu giúp tách biệt các mảng số thực tính toán và dữ liệu siêu dữ liệu (metadata) để vượt qua giới hạn I/O của phần cứng.

4.1 Thách thức của việc lưu trữ Vector đa chiều

Trong các cơ sở dữ liệu truyền thống, dữ liệu được tổ chức theo 2 hướng chính:

  • Lưu trữ dạng hàng (Row-store): Toàn bộ các cột của một bản ghi được xếp liên tiếp nhau trên đĩa. Kiểu này tối ưu cho việc đọc/ghi trọn vẹn 1 bản ghi nhưng cực kỳ tệ khi ta chỉ muốn quét 1 thuộc tính trên toàn bộ cơ sở dữ liệu.
  • Lưu trữ dạng cột (Column-store): Dữ liệu của từng cột được gom lại xếp liên tiếp nhau. Phù hợp cho tính toán báo cáo phân tích nhưng tốn chi phí khi tái cấu trúc lại bản ghi đầy đủ.

Khi ta lưu trữ các véc-tơ lớn (ví dụ 1536 số thực Float32 tốn 6KB bộ nhớ), cả hai cấu trúc trên đều gặp điểm nghẽn. Nếu quét tuyến tính để tính khoảng cách, hệ thống sẽ phải đọc kèm theo cả metadata (như chuỗi text lớn, ID, ngày tháng) nạp vào RAM, gây ra hiện tượng nghẽn băng thông RAM/Disk và làm chậm tiến trình nhân ma trận của CPU.

traditional_vs_hybrid_schema.sql
-- [❌ Kém hiệu quả] Cấu trúc truyền thống gộp chung vector vào bảng hàng
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255),
    content TEXT,
    embedding FLOAT8[] -- Quét tuyến tính bảng này sẽ bắt buộc nạp TEXT vào bộ đệm RAM
);

-- [✅ Đúng chuẩn Hybrid] Phân tách bảng lưu trữ
CREATE TABLE article_metadata (
    id VARCHAR(64) PRIMARY KEY,
    payload JSONB -- Chứa title, content, author
);
-- Vector nhị phân Float32 thực tế sẽ được nạp vào file nhị phân phẳng tuần tự riêng biệt

4.2 Kiến trúc lưu trữ Hybrid (Hybrid Storage Architecture)

Để đạt được hiệu năng tối đa, các CSDL Vector chuyên dụng (như Pinecone, Milvus, Qdrant) sử dụng Kiến trúc lưu trữ Hybrid (Hybrid Storage):

  1. Không gian Vector nhị phân (Binary Vector Space): Lưu trữ toàn bộ mảng véc-tơ số thực dưới dạng một mảng byte nhị phân phẳng liên tiếp (flat binary block) trên RAM hoặc tệp ánh xạ bộ nhớ (memory-mapped file). Nhờ đó, CPU có thể đọc mảng nhị phân này liên tiếp vào bộ đệm L1/L2 Cache của chip và tính toán song song bằng tập lệnh vector hóa (SIMD) mà không bị gián đoạn.
  2. Cơ sở dữ liệu Metadata (Key-Value/Document Store): Lưu trữ các dữ liệu phi cấu trúc đi kèm (như chuỗi văn bản gốc, JSON metadata) trong một cơ sở dữ liệu Key-Value gọn nhẹ (như RocksDB, LMDB) hoặc NoSQL.

Bảng so sánh hiệu quả quét dữ liệu giữa các mô hình tổ chức bộ nhớ:

Đặc tính cấu trúc Row-Store (Lưu dạng hàng) Column-Store (Lưu dạng cột) Hybrid Storage (CSDL Vector)
Bố cục bộ nhớ của Vector Bị phân mảnh bởi các cột metadata khác xen kẽ. Liên tiếp theo cột nhưng tốn overhead giải nén. Mảng byte nhị phân phẳng liên tiếp hoàn toàn.
Hiệu năng quét Vector Kém (Do nạp dữ liệu thừa vào RAM cache). Trung bình. Cực kỳ cao (Khớp cấu trúc SIMD của CPU/GPU).
Overhead truy xuất Metadata Thấp (Đã nằm sẵn trong hàng). Cao (Cần ghép nhiều cột lại). Trung bình (Chỉ đọc khi đã có kết quả Top K).

4.3 Quản lý ID và liên kết dữ liệu

Trong kiến trúc Hybrid, làm thế nào để liên kết véc-tơ nhị phân đang quét với thông tin metadata tương ứng? Chúng ta sử dụng một Bảng ánh xạ ID hai chiều (ID Mapping Table).

Bảng ánh xạ ghi nhận vị trí offset của véc-tơ nhị phân trong file phẳng:

\[\text{Offset}(i) = i \times d \times 4\]

Ý nghĩa ký hiệu:

  • $i$: Chỉ số index tuyến tính của vector trong file nhị phân (từ $0$ đến $N-1$).
  • $d$: Số chiều của vector (ví dụ 1536).
  • $4$: Số bytes cần để biểu diễn 1 số thực Float32 dấu phẩy động.

Dưới đây là mô phỏng động cơ lưu trữ Hybrid tối giản bằng JavaScript:

hybrid_storage_engine.js
class HybridDBEngine {
  constructor(dimension = 1536) {
    this.dimension = dimension;
    this.vectorSizeBytes = dimension * 4;
    this.vectorBuffer = Buffer.alloc(1000 * this.vectorSizeBytes); // Pre-allocate 1000 vectors
    this.idToOffset = new Map();
    this.metadataStore = new Map();
    this.currentIndex = 0;
  }
  
  insert(id, vector, metadata) {
    const offset = this.currentIndex * this.vectorSizeBytes;
    
    // 1. Ghi vector nhị phân vào Buffer
    for (let i = 0; i < this.dimension; i++) {
      this.vectorBuffer.writeFloatLE(vector[i], offset + i * 4);
    }
    
    // 2. Thiết lập bảng ánh xạ
    this.idToOffset.set(id, offset);
    this.metadataStore.set(id, metadata);
    this.currentIndex++;
  }
}

4.4 Cạm bẫy: Phân mảnh và Overhead truy xuất ngẫu nhiên metadata

Một sai lầm thiết kế phổ biến là truy xuất metadata ngay trong quá trình tính toán khoảng cách vector.

Ví dụ: Trong lúc đang duyệt đồ thị tìm kiếm lân cận xấp xỉ, thuật toán cần kiểm tra xem node hiện tại có thỏa mãn bộ lọc metadata hay không, và quyết định truy cập ổ đĩa (Disk I/O) để đọc JSON metadata. Việc đọc đĩa ngẫu nhiên (Random Read) liên tục này sẽ phá hủy hoàn toàn bộ đệm cache, đẩy thời gian truy vấn từ vài mili-giây lên hàng trăm mili-giây.

🕳️ Cạm bẫy: Truy xuất metadata ngẫu nhiên

Hãy thiết kế thuật toán tìm kiếm sao cho quá trình duyệt chỉ mục véc-tơ hoàn toàn diễn ra trên mảng byte nhị phân phẳng trong bộ nhớ. Sau khi đã xác định được chính xác Top K ID gần nhất, lúc đó mới thực hiện 1 lượt truy vấn Key-Value duy nhất để lấy Metadata hiển thị cho người dùng.

4.5 Kiến trúc phân tầng lưu trữ (Multi-tiered storage) trong các hệ thống Vector DB hiện đại

Khi quy mô dữ liệu vượt qua hàng tỷ véc-tơ, việc giữ toàn bộ véc-tơ trên RAM trở nên vô cùng đắt đỏ về chi phí phần cứng. Các CSDL Vector hiện đại giải quyết bài toán này bằng Kiến trúc phân tầng lưu trữ (Multi-tiered storage):

  • Tầng Nóng (Hot Tier - RAM): Lưu trữ các véc-tơ thường xuyên được truy vấn và cấu trúc đồ thị duyệt nhanh (như tầng cao của HNSW).
  • Tầng Ấm (Warm Tier - SSD/NVMe): Lưu trữ mảng véc-tơ nhị phân phẳng dưới dạng file ánh xạ bộ nhớ (Memory-mapped files - mmap). Hệ điều hành sẽ tự động nạp các block dữ liệu từ SSD vào RAM khi cần thiết và giải phóng bộ đệm khi thiếu bộ nhớ.
  • Tầng Lạnh (Cold Tier - Object Storage/S3): Lưu trữ bản sao lưu chỉ mục cũ hoặc metadata ít khi truy xuất để tối ưu hóa chi phí lưu trữ dài hạn.
💡 Mẹo cấu hình mmap

Sử dụng cơ chế mmap của Linux giúp CSDL hoạt động ổn định mà không lo bị tràn bộ nhớ hệ thống (Out Of Memory - OOM), vì hệ điều hành sẽ tự động quản lý việc phân trang và ghi dữ liệu đệm từ RAM xuống đĩa cứng một cách tối ưu.

multi_tiered_caching.js
// Giả lập quản lý bộ nhớ phân tầng
class MultiTieredCache {
  constructor(ramLimit = 10) {
    this.ramLimit = ramLimit;
    this.ramTier = new Map(); // Hot Tier
    this.ssdFileDescriptor = null; // Warm Tier (mô phỏng đọc file SSD)
  }
  
  async getVector(id) {
    // 1. Kiểm tra Hot Tier (RAM)
    if (this.ramTier.has(id)) {
      return this.ramTier.get(id);
    }
    
    // 2. Fallback đọc từ Warm Tier (SSD)
    console.log(`[Warm Tier] Tìm nạp vector từ SSD cho ID: ${id}`);
    const vector = await this.readFromSsd(id);
    
    // Tự động đưa lên Hot Tier và giải phóng LRU nếu RAM đầy
    if (this.ramTier.size >= this.ramLimit) {
      const firstKey = this.ramTier.keys().next().value;
      this.ramTier.delete(firstKey);
    }
    this.ramTier.set(id, vector);
    return vector;
  }
  
  readFromSsd(id) {
    return new Promise(resolve => setTimeout(() => resolve(new Float32Array(1536)), 10)); // Giả lập đọc SSD tốn 10ms
  }
}

4.6 Thực hành: Trực quan hóa lưu trữ Hybrid

Demo dưới đây dùng đúng FlatIndex của VDBJS — mỗi bản ghi là 1 object { id, vector, metadata } nằm trong CÙNG MỘT mảng this.vectors. Trực quan hóa chỉ vẽ tách nó thành 2 khung hình để minh họa kiến trúc Hybrid: khung trái là "kho vector" (bố cục nhị phân phẳng), khung phải là "kho metadata" (Key-Value), và đường kẻ nối hai bên theo ID chung cho thấy dù tách vật lý, cả hai vẫn thuộc về 1 bản ghi logic duy nhất:

🗄️ Hybrid Storage — FlatIndex thật (VDBJS)
Đã nạp 5 bản ghi vào FlatIndex — mỗi bản ghi gồm 1 vector 4 chiều (kho trái) và 1 metadata (kho phải), liên kết với nhau bằng ID chung.
hybrid_storage_visual.js (đúng logic đang chạy ở tab Xem trước)
import { FlatIndex } from './vdb-engine.js';

// 1 FlatIndex duy nhat luu CA vector LAN metadata trong CUNG 1 mang this.vectors -
// demo chi ve tach lam 2 khung hinh de minh hoa kien truc Hybrid Storage
const flatIndex = new FlatIndex('cosine');
seedRecords.forEach(({ id, vector, metadata }) => flatIndex.insert(id, vector, metadata));

// Chen ban ghi moi - CHI 1 lan insert() ghi vao CA HAI "kho" cung luc
function insertRandomRecord() {
  const id = 'doc_' + String(flatIndex.vectors.length + 1).padStart(3, '0');
  const vector = randVector(4); // vd: [0.12, -0.44, 0.81, -0.03]
  const metadata = { title: pick(TITLE_POOL), category: pick(CATEGORY_POOL) };
  flatIndex.insert(id, vector, metadata); // ghi vao this.vectors[] - gom ca vector + metadata
  render(id); // highlight ban ghi vua chen o CA HAI panel + duong noi
}

// Truy van theo ID - doc truc tiep tu this.vectors, khong can 2 lan truy van rieng biet
function queryById(id) {
  const record = flatIndex.vectors.find((r) => r.id === id);
  render(id); // highlight cell trai (vector) + cell phai (metadata) + duong noi cua CUNG 1 id
  return record;
}

📖 Tài liệu tham khảo

Tải file code thực hành minh họa bài học

Tải tệp tin code mẫu JavaScript mô phỏng động cơ lưu trữ Hybrid, đóng gói Buffer nhị phân và chạy kiểm thử benchmark hiệu năng đọc đệm RAM:

Tải về vectordb_storage_demo.js

Bài viết liên quan trong series

Bài 5: Chỉ mục IVF (Inverted File Index) & Phân hoạch Voronoi Bài 3: Bản Chất Toán Học Các Độ Đo Khoảng Cách & So Khớp Tương Đồng Quay lại Lộ trình Vector DB Series

Bình luận