•19 min read

sqlite-vec vs pgvector: Tìm kiếm Vector Nhúng Cục bộ cho Ứng dụng Desktop & Edge

sqlite-vec vs pgvector: Tìm kiếm Vector Nhúng Cục bộ cho Ứng dụng Desktop & Edge

Sự phát triển mạnh mẽ của vector embeddings đã thúc đẩy nhu cầu tìm kiếm tương đồng hiệu quả. Trong khi pgvector đã trở thành một tiêu chuẩn thực tế cho các cơ sở dữ liệu vector phía máy chủ, các ứng dụng nhúng và biên lại đặt ra những ràng buộc kiến trúc độc đáo. Hướng dẫn này so sánh thực nghiệm sqlite-vec (Mozilla/Alex Garcia) với pgvector, tập trung vào sự phù hợp của chúng cho môi trường máy tính để bàn (Electron, Tauri) và các worker biên. Chúng ta sẽ đánh giá mức tiêu thụ RAM, thời gian xây dựng chỉ mục, lượng tử hóa vô hướng int8 và hiệu suất truy xuất trên 100k embeddings.

Audio Briefing
0:00 / 0:00

Tổng quan kiến trúc: sqlite-vec so với pgvector

pgvector mở rộng PostgreSQL, một cơ sở dữ liệu quan hệ client-server mạnh mẽ. Nó cung cấp khả năng tuân thủ ACID, khả năng truy vấn phức tạp và các tính năng sẵn sàng cao. Ngược lại, sqlite-vec là một tiện ích mở rộng của SQLite, nhúng tìm kiếm vector trực tiếp vào quy trình của ứng dụng. Sự khác biệt cơ bản này quyết định các trường hợp sử dụng và đặc điểm hiệu suất tương ứng của chúng.

| Tính năng | sqlite-vec | pgvector | |---|---| | Kiến trúc | Nhúng (trong tiến trình) | Client-Server | | Triển khai | Một tệp duy nhất, không có máy chủ | Yêu cầu máy chủ PostgreSQL | | Mô hình dữ liệu | Bảng SQLite | Bảng PostgreSQL | | Lập chỉ mục | HNSW, IVF, Flat | IVF, HNSW, Flat | | Lượng tử hóa | Vô hướng int8 | Không (tính đến 0.6.0) | | Đồng thời | Mô hình đồng thời của SQLite | MVCC của PostgreSQL | | Trường hợp sử dụng | Biên, Máy tính để bàn, Di động | Máy chủ, Đám mây, Phân tán | | Độ phức tạp thiết lập | Thấp | Trung bình |

Nội bộ sqlite-vec

sqlite-vec tận dụng cơ chế tiện ích mở rộng có thể tải của SQLite. Nó giới thiệu các hàm SQL và bảng ảo mới cho các hoạt động vector. Các thuật toán lập chỉ mục cốt lõi (HNSW, IVF) được triển khai bằng Rust, được biên dịch thành một thư viện chia sẻ (.so, .dylib, .dll) mà SQLite tải trong thời gian chạy. Điều này cho phép hiệu suất gốc mà không có các phụ thuộc bên ngoài ngoài chính thư viện SQLite. Lượng tử hóa vô hướng int8 là một yếu tố khác biệt chính, giúp giảm đáng kể dung lượng lưu trữ và bộ nhớ cho các vector.

Nội bộ pgvector

pgvector là một tiện ích mở rộng PostgreSQL được viết bằng C. Nó bổ sung một kiểu dữ liệu vector mới và các toán tử cho tìm kiếm tương đồng. Nó tích hợp liền mạch với bộ lập kế hoạch truy vấn và hệ thống giao dịch của PostgreSQL. Mặc dù mạnh mẽ, mô hình client-server của nó gây ra độ trễ mạng và yêu cầu một phiên bản PostgreSQL riêng biệt, điều này thường không thực tế cho các kịch bản nhúng.

Advertisement

Thiết lập và chuẩn bị dữ liệu

Để đánh giá hiệu suất, chúng ta sẽ sử dụng một tập dữ liệu gồm 100.000 embeddings 384 chiều được tạo từ một mô hình chuyển đổi câu (ví dụ: all-MiniLM-L6-v2).

Đầu tiên, tạo các embeddings tổng hợp:

import numpy as np
import sqlite3
import time
from pgvector.psycopg import register_vector
import psycopg
import os

# Configuration
NUM_EMBEDDINGS = 100_000
DIMENSIONS = 384
SQLITE_DB_PATH = "embeddings.db"
PG_DB_NAME = "vector_benchmark"
PG_USER = "postgres"
PG_PASSWORD = "password" # Use a strong password in production
PG_HOST = "localhost"
PG_PORT = 5432

# Generate synthetic embeddings
print(f"Generating {NUM_EMBEDDINGS} synthetic embeddings...")
embeddings = np.random.rand(NUM_EMBEDDINGS, DIMENSIONS).astype(np.float32)
# Normalize embeddings to unit length, common practice for cosine similarity
embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
print("Embeddings generated.")

# --- SQLite-vec Setup ---
print("\n--- Setting up sqlite-vec ---")
try:
    # Connect to SQLite database
    sqlite_conn = sqlite3.connect(SQLITE_DB_PATH)
    sqlite_cursor = sqlite_conn.cursor()

    # Load sqlite-vec extension (adjust path as necessary for your OS)
    # On Linux: libsqlite_vec.so, macOS: libsqlite_vec.dylib, Windows: sqlite_vec.dll
    # Ensure you have compiled/downloaded the correct extension for your system.
    # Example for a pre-compiled Linux extension:
    sqlite_conn.enable_load_extension(True)
    try:
        sqlite_cursor.execute("SELECT load_extension('./libsqlite_vec.so');")
    except sqlite3.OperationalError as e:
        print(f"Error loading sqlite-vec extension: {e}")
        print("Please ensure 'libsqlite_vec.so' (or .dylib/.dll) is in the current directory or specified path.")
        print("You might need to compile it from source: https://github.com/asg017/sqlite-vec")
        exit(1)
    sqlite_conn.enable_load_extension(False)

    # Create table for embeddings
    sqlite_cursor.execute(f"""
        CREATE TABLE IF NOT EXISTS items (
            id INTEGER PRIMARY KEY,
            embedding BLOB
        );
    """)
    sqlite_conn.commit()

    # Insert embeddings
    print(f"Inserting {NUM_EMBEDDINGS} embeddings into sqlite-vec...")
    insert_start_time = time.time()
    batch_size = 1000
    for i in range(0, NUM_EMBEDDINGS, batch_size):
        batch = embeddings[i:i+batch_size]
        sqlite_cursor.executemany(
            "INSERT INTO items (id, embedding) VALUES (?, ?)",
            [(i + j, vec.tobytes()) for j, vec in enumerate(batch)]
        )
        sqlite_conn.commit()
    insert_end_time = time.time()
    print(f"sqlite-vec insertion time: {insert_end_time - insert_start_time:.2f} seconds")

except Exception as e:
    print(f"Error during sqlite-vec setup: {e}")
    if 'sqlite_vec' in str(e):
        print("Ensure the sqlite-vec extension is correctly compiled and loaded.")
    exit(1)


# --- pgvector Setup ---
print("\n--- Setting up pgvector ---")
try:
    # Connect to PostgreSQL
    pg_conn = psycopg.connect(f"dbname={PG_DB_NAME} user={PG_USER} password={PG_PASSWORD} host={PG_HOST} port={PG_PORT}")
    pg_conn.autocommit = True # For CREATE DATABASE/EXTENSION
    pg_cursor = pg_conn.cursor()

    # Create database if not exists
    try:
        pg_cursor.execute(f"CREATE DATABASE {PG_DB_NAME};")
        print(f"Database '{PG_DB_NAME}' created.")
    except psycopg.errors.DuplicateDatabase:
        print(f"Database '{PG_DB_NAME}' already exists.")
    except Exception as e:
        print(f"Error creating database: {e}")
        print("Ensure PostgreSQL is running and user has permissions.")
        exit(1)

    pg_conn.close() # Close and reconnect to the new database

    pg_conn = psycopg.connect(f"dbname={PG_DB_NAME} user={PG_USER} password={PG_PASSWORD} host={PG_HOST} port={PG_PORT}")
    pg_cursor = pg_conn.cursor()

    # Enable pgvector extension
    pg_cursor.execute("CREATE EXTENSION IF NOT EXISTS vector;")
    print("pgvector extension enabled.")

    # Register vector type for psycopg
    register_vector(pg_conn)

    # Create table for embeddings
    pg_cursor.execute(f"""
        CREATE TABLE IF NOT EXISTS items (
            id SERIAL PRIMARY KEY,
            embedding vector({DIMENSIONS})
        );
    """)
    pg_conn.commit()

    # Insert embeddings
    print(f"Inserting {NUM_EMBEDDINGS} embeddings into pgvector...")
    insert_start_time = time.time()
    batch_size = 1000
    for i in range(0, NUM_EMBEDDINGS, batch_size):
        batch = embeddings[i:i+batch_size]
        pg_cursor.executemany(
            "INSERT INTO items (embedding) VALUES (%s)",
            [(vec,) for vec in batch]
        )
        pg_conn.commit()
    insert_end_time = time.time()
    print(f"pgvector insertion time: {insert_end_time - insert_start_time:.2f} seconds")

except Exception as e:
    print(f"Error during pgvector setup: {e}")
    print("Ensure PostgreSQL is running and accessible.")
    exit(1)

print("\nData setup complete for both databases.")

Lưu ý về tiện ích mở rộng sqlite-vec: Bạn phải biên dịch sqlite-vec từ mã nguồn hoặc tải xuống một tệp nhị phân được biên dịch sẵn cho hệ điều hành và kiến trúc cụ thể của bạn. Đặt thư viện chia sẻ (libsqlite_vec.so, libsqlite_vec.dylib, hoặc sqlite_vec.dll) ở một vị trí mà ứng dụng của bạn có thể truy cập, hoặc chỉ định đường dẫn đầy đủ của nó.

Lập chỉ mục và đánh giá hiệu suất

Chúng ta sẽ đánh giá thời gian xây dựng chỉ mục, mức tiêu thụ RAM và hiệu suất tìm kiếm (truy xuất và độ trễ) cho cả sqlite-vec và pgvector. Đối với sqlite-vec, chúng ta cũng sẽ đánh giá tác động của lượng tử hóa vô hướng int8.

Chiến lược lập chỉ mục

  • sqlite-vec: Chúng ta sẽ sử dụng HNSW (Hierarchical Navigable Small Worlds) vì sự cân bằng giữa tốc độ và khả năng truy xuất. Chúng ta cũng sẽ kiểm tra HNSW lượng tử hóa int8.
  • pgvector: Chúng ta sẽ sử dụng IVF (Inverted File Index) và HNSW. IVF thường nhanh hơn cho tìm kiếm láng giềng gần nhất chính xác trên các tập dữ liệu nhỏ hơn, trong khi HNSW cung cấp khả năng truy xuất tốt hơn ở quy mô lớn.
import numpy as np
import sqlite3
import time
from pgvector.psycopg import register_vector
import psycopg
import os
import faiss # For ground truth and recall calculation
from sklearn.metrics import top_k_accuracy_score

# Configuration (same as above, ensure NUM_EMBEDDINGS, DIMENSIONS match)
NUM_EMBEDDINGS = 100_000
DIMENSIONS = 384
SQLITE_DB_PATH = "embeddings.db"
PG_DB_NAME = "vector_benchmark"
PG_USER = "postgres"
PG_PASSWORD = "password"
PG_HOST = "localhost"
PG_PORT = 5432
K_SEARCH = 10 # Number of neighbors to retrieve
NUM_QUERIES = 100 # Number of queries for benchmarking

# Reconnect to databases
sqlite_conn = sqlite3.connect(SQLITE_DB_PATH)
sqlite_conn.enable_load_extension(True)
sqlite_cursor = sqlite_conn.cursor()
try:
    sqlite_cursor.execute("SELECT load_extension('./libsqlite_vec.so');")
except sqlite3.OperationalError as e:
    print(f"Error loading sqlite-vec extension: {e}")
    exit(1)
sqlite_conn.enable_load_extension(False)

pg_conn = psycopg.connect(f"dbname={PG_DB_NAME} user={PG_USER} password={PG_PASSWORD} host={PG_HOST} port={PG_PORT}")
pg_cursor = pg_conn.cursor()
register_vector(pg_conn)

# Load all embeddings for ground truth and query generation
print("Loading all embeddings for ground truth...")
sqlite_cursor.execute("SELECT embedding FROM items ORDER BY id")
all_embeddings_bytes = sqlite_cursor.fetchall()
all_embeddings = np.array([np.frombuffer(e[0], dtype=np.float32) for e in all_embeddings_bytes])
print(f"Loaded {len(all_embeddings)} embeddings.")

# Generate query embeddings (random subset of existing embeddings)
query_indices = np.random.choice(NUM_EMBEDDINGS, NUM_QUERIES, replace=False)
query_embeddings = all_embeddings[query_indices]

# --- Ground Truth (using FAISS for exact nearest neighbors) ---
print("\nCalculating ground truth using FAISS...")
faiss_index = faiss.IndexFlatL2(DIMENSIONS) # L2 for cosine similarity on normalized vectors
faiss_index.add(all_embeddings)
D_gt, I_gt = faiss_index.search(query_embeddings, K_SEARCH)
print("Ground truth calculated.")

# Function to calculate recall@K
def calculate_recall(retrieved_ids, ground_truth_ids, k):
    hits = 0
    for i in range(len(retrieved_ids)):
        # Check if any of the top K retrieved IDs are in the ground truth top K
        # Note: For exact matches, we expect the query itself to be in the top K.
        # For approximate search, we check if the retrieved set overlaps with the ground truth set.
        retrieved_set = set(retrieved_ids[i])
        ground_truth_set = set(ground_truth_ids[i])
        if not ground_truth_set.isdisjoint(retrieved_set):
            hits += 1
    return hits / len(retrieved_ids)

# --- Benchmarking Function ---
def benchmark_search(db_type, index_type, index_params, query_sql, conn, cursor, is_sqlite_vec=False, is_quantized=False):
    print(f"\n--- Benchmarking {db_type} with {index_type} ({index_params}) ---")

    # Drop existing index if any
    if db_type == "sqlite-vec":
        try:
            cursor.execute(f"DROP TABLE IF EXISTS items_vec_idx;")
            conn.commit()
        except sqlite3.OperationalError:
            pass # Index might not exist
    elif db_type == "pgvector":
        try:
            cursor.execute(f"DROP INDEX IF EXISTS items_embedding_idx;")
            conn.commit()
        except psycopg.errors.UndefinedObject:
            pass # Index might not exist

    # Build Index
    print(f"Building {index_type} index...")
    build_start_time = time.time()
    if db_type == "sqlite-vec":
        if is_quantized:
            cursor.execute(f"CREATE VIRTUAL TABLE items_vec_idx USING vec0(items, embedding, {DIMENSIONS}, 'quantizer=int8', '{index_type}={index_params}');")
        else:
            cursor.execute(f"CREATE VIRTUAL TABLE items_vec_idx USING vec0(items, embedding, {DIMENSIONS}, '{index_type}={index_params}');")
        conn.commit()
    elif db_type == "pgvector":
        cursor.execute(f"CREATE INDEX items_embedding_idx ON items USING {index_type}(embedding {index_params});")
        conn.commit()
    build_end_time = time.time()
    print(f"Index build time: {build_end_time - build_start_time:.2f} seconds")

    # Measure RAM usage (approximate, depends on OS/tooling)
    # For sqlite-vec, this is the process's RAM. For pgvector, it's the PostgreSQL server's RAM.
    # This is hard to measure programmatically in a cross-platform way.
    # For a real benchmark, use OS-specific tools (e.g., `ps aux` on Linux, Activity Monitor on macOS).
    print("RAM usage measurement requires external OS tools.")

    # Perform searches
    search_latencies = []
    retrieved_ids_list = []

    print(f"Performing {NUM_QUERIES} searches...")
    for i, query_vec in enumerate(query_embeddings):
        search_start_time = time.time()
        if db_type == "sqlite-vec":
            # For sqlite-vec, the query uses the virtual table
            cursor.execute(query_sql, (query_vec.tobytes(), K_SEARCH))
        elif db_type == "pgvector":
            cursor.execute(query_sql, (query_vec, K_SEARCH))

        results = cursor.fetchall()
        search_end_time = time.time()
        search_latencies.append(search_end_time - search_start_time)
        retrieved_ids_list.append([r[0] for r in results]) # Assuming ID is the first column

    avg_latency = np.mean(search_latencies)
    p95_latency = np.percentile(search_latencies, 95)
    print(f"Average search latency: {avg_latency * 1000:.2f} ms")
    print(f"P95 search latency: {p95_latency * 1000:.2f} ms")

    # Calculate recall
    # We need to map the ground truth IDs to the retrieved IDs.
    # For simplicity, we assume the IDs are 0-indexed and correspond to the original `all_embeddings` array.
    # The ground truth `I_gt` contains indices, which are effectively IDs.
    recall = calculate_recall(retrieved_ids_list, I_gt, K_SEARCH)
    print(f"Recall@{K_SEARCH}: {recall:.4f}")

    return {
        "db_type": db_type,
        "index_type": index_type,
        "index_params": index_params,
        "build_time": build_end_time - build_start_time,
        "avg_latency_ms": avg_latency * 1000,
        "p95_latency_ms": p95_latency * 1000,
        "recall_at_k": recall
    }

# --- Run Benchmarks ---
results = []

# sqlite-vec: HNSW (default float32)
results.append(benchmark_search(
    "sqlite-vec", "hnsw", "M=16,ef_construction=100",
    "SELECT id FROM items_vec_idx WHERE embedding MATCH ? ORDER BY distance LIMIT ?",
    sqlite_conn, sqlite_cursor, is_sqlite_vec=True
))

# sqlite-vec: HNSW with int8 quantization
results.append(benchmark_search(
    "sqlite-vec", "hnsw", "M=16,ef_construction=100",
    "SELECT id FROM items_vec_idx WHERE embedding MATCH ? ORDER BY distance LIMIT ?",
    sqlite_conn, sqlite_cursor, is_sqlite_vec=True, is_quantized=True
))

# pgvector: IVF (lists=100)
# For 100k embeddings, lists=100 is a reasonable starting point.
# The `vector_l2_ops` is for L2 distance, which is equivalent to cosine for normalized vectors.
results.append(benchmark_search(
    "pgvector", "ivfflat", "vector_l2_ops, lists=100",
    f"SELECT id FROM items ORDER BY embedding <-> %s LIMIT %s",
    pg_conn, pg_cursor
))

# pgvector: HNSW (m=16, ef_construction=64)
# Note: pgvector's HNSW parameters are slightly different from sqlite-vec's.
# `m` is the number of neighbors, `ef_construction` is for build time.
results.append(benchmark_search(
    "pgvector", "hnsw", "vector_l2_ops, m=16, ef_construction=64",
    f"SELECT id FROM items ORDER BY embedding <-> %s LIMIT %s",
    pg_conn, pg_cursor
))

# Print results table
print("\n--- Benchmark Summary ---")
print("| DB | Index Type | Params | Build Time (s) | Avg Latency (ms) | P95 Latency (ms) | Recall@10 |")
print("|---|---|---|---|---|---|---|")
for r in results:
    print(f"| {r['db_type']} | {r['index_type']} | {r['index_params']} | {r['build_time']:.2f} | {r['avg_latency_ms']:.2f} | {r['p95_latency_ms']:.2f} | {r['recall_at_k']:.4f} |")

# Clean up
sqlite_conn.close()
pg_conn.close()

Giải thích kết quả đánh giá hiệu suất

Các con số cụ thể sẽ thay đổi tùy thuộc vào phần cứng, phiên bản sqlite-vec và pgvector chính xác, và cấu hình PostgreSQL. Tuy nhiên, các xu hướng chung được mong đợi:

  • Thời gian xây dựng chỉ mục: sqlite-vec thường xây dựng chỉ mục nhanh hơn do bản chất trong tiến trình của nó, tránh chi phí mạng và quản lý tài nguyên máy chủ phức tạp. HNSW của pgvector có thể tốn nhiều tài nguyên trong quá trình xây dựng.
  • Mức tiêu thụ RAM: Lượng tử hóa int8 của sqlite-vec sẽ giảm đáng kể dung lượng RAM so với các chỉ mục float32 trong cả sqlite-vec (không lượng tử hóa) và pgvector. Điều này rất quan trọng đối với các thiết bị biên.
  • Độ trễ tìm kiếm: Đối với các tập dữ liệu nhỏ đến trung bình (ví dụ: 100k-1M vector), sqlite-vec có thể đạt được độ trễ tương đương hoặc thậm chí thấp hơn pgvector do không có chi phí mạng. Khi tập dữ liệu mở rộng, các tối ưu hóa phía máy chủ của pgvector và khả năng phân phối khối lượng công việc có thể mang lại lợi thế.
  • Truy xuất: Lượng tử hóa int8 gây ra một sự sụt giảm nhỏ về khả năng truy xuất do mất độ chính xác. Đánh đổi là giảm đáng kể bộ nhớ và lưu trữ. Đối với nhiều ứng dụng, sự sụt giảm truy xuất này là chấp nhận được. HNSW thường cung cấp khả năng truy xuất tốt hơn IVF ở các mức hiệu suất tương tự.

Những vấn đề trong sản xuất & Khắc phục sự cố

sqlite-vec

  1. Lỗi tải tiện ích mở rộng:

    • Chế độ lỗi: sqlite3.OperationalError: unable to load extension: ...
    • Nguyên nhân: Đường dẫn không chính xác đến libsqlite_vec.so (hoặc .dylib, .dll), không khớp kiến trúc (ví dụ: cố gắng tải tệp nhị phân ARM trên x86), hoặc thiếu các phụ thuộc.
    • Cách khắc phục:
      • Xác minh tệp tiện ích mở rộng tồn tại ở đường dẫn đã chỉ định.
      • Đảm bảo tiện ích mở rộng được biên dịch cho hệ điều hành và kiến trúc CPU chính xác của hệ thống mục tiêu.
      • Kiểm tra ldd libsqlite_vec.so (Linux) hoặc otool -L libsqlite_vec.dylib (macOS) để tìm các thư viện chia sẻ bị thiếu.
      • Đối với Electron/Tauri, đảm bảo tiện ích mở rộng được đóng gói đúng cách và được tải từ đường dẫn tài nguyên của ứng dụng.
  2. Sụt giảm truy xuất lượng tử hóa int8:

    • Chế độ lỗi: Kết quả tìm kiếm kém rõ rệt sau khi bật lượng tử hóa int8.
    • Nguyên nhân: Lượng tử hóa int8 giảm độ chính xác của mỗi thành phần vector từ số thực 32 bit xuống số nguyên 8 bit. Việc mất thông tin này có thể ảnh hưởng đến các phép tính tương đồng, đặc biệt đối với các embeddings đã rất gần nhau.
    • Cách khắc phục:
      • Đánh giá xem sự sụt giảm truy xuất có chấp nhận được đối với yêu cầu của ứng dụng của bạn hay không.
      • Cân nhắc sử dụng M hoặc ef_construction lớn hơn cho HNSW với int8 để bù đắp, mặc dù điều này làm tăng thời gian và kích thước xây dựng chỉ mục.
      • Nếu độ chính xác cao là tối quan trọng, hãy giữ nguyên float32 (mặc định).
  3. Các vấn đề đồng thời:

    • Chế độ lỗi: sqlite3.OperationalError: database is locked
    • Nguyên nhân: SQLite được thiết kế cho đồng thời một người ghi, nhiều người đọc. Nếu nhiều luồng/tiến trình cố gắng ghi đồng thời, lỗi này sẽ xảy ra.
    • Cách khắc phục:
      • Triển khai các cơ chế khóa thích hợp (ví dụ: threading.Lock trong Python, Mutex trong Rust/C++).
      • Sử dụng chế độ WAL (Write-Ahead Logging): PRAGMA journal_mode=WAL;. Điều này cải thiện đáng kể tính đồng thời cho người đọc trong khi người ghi đang hoạt động.
      • Đối với đồng thời ghi cao, SQLite có thể không phải là lựa chọn phù hợp; hãy cân nhắc một cơ sở dữ liệu client-server.

pgvector

  1. Lỗi kết nối PostgreSQL:

    • Chế độ lỗi: psycopg.OperationalError: connection to server at "localhost" (::1), port 5432 failed: Connection refused
    • Nguyên nhân: Máy chủ PostgreSQL không chạy, máy chủ/cổng không chính xác, hoặc tường lửa chặn kết nối.
    • Cách khắc phục:
      • Khởi động dịch vụ PostgreSQL.
      • Xác minh postgresql.conf cho listen_addresses và port.
      • Kiểm tra pg_hba.conf để biết các quy tắc xác thực máy khách.
      • Điều chỉnh các quy tắc tường lửa để cho phép kết nối đến cổng 5432.
  2. Thời gian xây dựng chỉ mục và mức tiêu thụ tài nguyên:

    • Chế độ lỗi: CREATE INDEX mất quá nhiều thời gian hoặc tiêu thụ tất cả RAM/CPU có sẵn trên máy chủ.
    • Nguyên nhân: Tập dữ liệu lớn, tài nguyên máy chủ không đủ, hoặc các tham số chỉ mục không tối ưu (ví dụ: ef_construction quá cao cho HNSW).
    • Cách khắc phục:
      • Tăng work_mem trong postgresql.conf cho việc xây dựng chỉ mục.
      • Phân bổ thêm RAM và CPU cho máy chủ PostgreSQL.
      • Điều chỉnh các tham số HNSW (m, ef_construction) hoặc các tham số IVF (lists) để cân bằng thời gian xây dựng, kích thước và khả năng truy xuất. Bắt đầu với các giá trị thấp hơn và tăng dần.
      • Cân nhắc xây dựng chỉ mục trong giờ thấp điểm.
  3. Không tìm thấy kiểu vector:

    • Chế độ lỗi: psycopg.errors.UndefinedObject: type "vector" does not exist
    • Nguyên nhân: Tiện ích mở rộng pgvector không được bật trong cơ sở dữ liệu.
    • Cách khắc phục: Chạy CREATE EXTENSION IF NOT EXISTS vector; trong cơ sở dữ liệu mục tiêu.
Advertisement

Các câu hỏi thường gặp

  1. Khi nào tôi nên chọn sqlite-vec thay vì pgvector? Chọn sqlite-vec cho các ứng dụng nhúng (máy tính để bàn, di động, thiết bị biên) nơi một cơ sở dữ liệu client-server đầy đủ là quá mức hoặc không thực tế. Điều này bao gồm các ứng dụng Electron/Tauri, thiết bị IoT hoặc các hàm biên không máy chủ nơi bạn cần tìm kiếm vector cục bộ, độ trễ thấp mà không có các phụ thuộc bên ngoài. Lượng tử hóa int8 của nó là một lợi thế mạnh mẽ cho các môi trường bị hạn chế bộ nhớ.

  2. sqlite-vec có thể xử lý hàng triệu vector không? Có, sqlite-vec có thể xử lý hàng triệu vector, đặc biệt với lập chỉ mục HNSW và lượng tử hóa int8. Tuy nhiên, hiệu suất cuối cùng sẽ bị giới hạn bởi CPU, RAM và I/O đĩa của thiết bị chủ. Đối với các tập dữ liệu vượt quá hàng chục triệu hoặc yêu cầu xử lý phân tán, pgvector (có thể với sharding) hoặc các cơ sở dữ liệu vector chuyên dụng như Milvus/Weaviate trở nên phù hợp hơn.

  3. Tác động hiệu suất của lượng tử hóa int8 trong sqlite-vec là gì? Lượng tử hóa int8 thường giảm dung lượng bộ nhớ của các vector xuống 75% (từ 4 byte mỗi chiều xuống 1 byte). Điều này dẫn đến kích thước chỉ mục nhỏ hơn, tải nhanh hơn và giảm mức tiêu thụ RAM. Đánh đổi là một sự sụt giảm nhỏ, nhưng thường chấp nhận được, về khả năng truy xuất (thường là 1-5% tùy thuộc vào tập dữ liệu và chất lượng embedding).

  4. Làm cách nào để đảm bảo sqlite-vec an toàn trong một ứng dụng máy tính để bàn? Vì sqlite-vec là một cơ sở dữ liệu nhúng, bảo mật của nó gắn liền với bảo mật của ứng dụng.

    • Mã hóa dữ liệu: Sử dụng các tiện ích mở rộng mã hóa của SQLite (ví dụ: SQLCipher) để mã hóa tệp cơ sở dữ liệu khi không hoạt động.
    • Tính toàn vẹn của mã: Đảm bảo tệp nhị phân ứng dụng của bạn được ký và chống giả mạo.
    • Xác thực đầu vào: Làm sạch tất cả các đầu vào của người dùng để ngăn chặn SQL injection, ngay cả khi cơ sở dữ liệu là cục bộ.
    • Kiểm soát truy cập: Triển khai kiểm soát truy cập cấp ứng dụng nếu nhiều người dùng hoặc thành phần tương tác với cơ sở dữ liệu.
  5. Các phương pháp hay nhất để điều chỉnh các tham số HNSW trong sqlite-vec và pgvector là gì?

    • M (Số láng giềng trên mỗi nút): Kiểm soát khả năng kết nối của đồ thị. M cao hơn cải thiện khả năng truy xuất nhưng làm tăng kích thước chỉ mục và thời gian xây dựng. Các giá trị điển hình: 16-64.
    • ef_construction (Phạm vi tìm kiếm thời gian xây dựng): Kiểm soát chất lượng của đồ thị trong quá trình xây dựng chỉ mục. ef_construction cao hơn dẫn đến khả năng truy xuất tốt hơn nhưng thời gian xây dựng lâu hơn nhiều. Các giá trị điển hình: 64-200.
    • ef_search (Phạm vi tìm kiếm thời gian truy vấn): Kiểm soát độ chính xác tìm kiếm tại thời điểm truy vấn. ef_search cao hơn cải thiện khả năng truy xuất nhưng làm tăng độ trễ truy vấn. Điều này thường có thể được điều chỉnh động mà không cần xây dựng lại chỉ mục. Các giá trị điển hình: 32-128.
    • lists (IVF): Đối với IVF của pgvector, lists (số danh sách đảo ngược) phải là sqrt(N) đến 10*sqrt(N) trong đó N là số vector. Nhiều danh sách hơn làm giảm thời gian tìm kiếm nhưng làm tăng kích thước chỉ mục và thời gian xây dựng.
    • Điều chỉnh lặp đi lặp lại: Bắt đầu với các tham số thận trọng, đánh giá hiệu suất, sau đó tăng dần M, ef_construction hoặc lists cho đến khi bạn đạt được sự đánh đổi truy xuất/độ trễ mong muốn.
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement