SSD Nodes Learn Hosting plans →

VPS-ல் Vector Database-ஐ இயக்குவது எப்படி?

உங்கள் VPS-ல் pgvector, Qdrant மற்றும் Chroma ஆகியவற்றை ஒப்பிட்டுப் பாருங்கள். RAM பயன்பாடு மற்றும் index அளவை எவ்வாறு கணக்கிடுவது என்பதை இந்த வழிகாட்டி விளக்குகிறது.

VPS-ல் vector database-ஐ இயக்குவதற்கான உண்மையான செலவு

ஒரு VPS-ல் (virtual private server) vector database-ஐ இயக்குவது, நிர்வகிக்கப்படும் (managed) விற்பனையாளர்கள் தீர்வு என்று விற்கும் சிக்கலை நீக்குகிறது. உங்கள் application மற்றும் index ஆகிய இரண்டும் ஒரே machine-ல் இருப்பதால், search request ஒரு network-க்கு பதிலாக loopback socket வழியாகவே பயணிக்கிறது. எஞ்சியிருப்பது எப்போதும் உண்மையான செலவாக இருந்த ஒன்றுதான்: text-ஐ vector-களாக மாற்றுவது. இதற்குப் பின்னால் மேலும் இரண்டு செலவுகள் உள்ளன: index-ஐ உருவாக்குவதற்கான நேரம் மற்றும் அது இயங்கும் காலம் முழுவதும் தேவைப்படும் RAM.

இது எந்த முடிவுகள் முக்கியம் என்பதை மாற்றுகிறது. Region மற்றும் endpoint round trip ஆகியவை உங்கள் கவலையாக இருக்காது. Vector-களின் எண்ணிக்கை, பரிமாணங்கள் (dimensions) மற்றும் நான்கு bytes ஆகியவற்றின் பெருக்கற்பலனே உங்கள் கவலையாக மாறும், ஏனெனில் இதுதான் நீங்கள் மாதந்தோறும் வாடகைக்கு எடுக்கும் memory-ல் index பொருந்துமா என்பதைத் தீர்மானிக்கிறது.

ஒரு server-ல் மில்லி விநாடிகள் எங்கே செலவாகின்றன

சுயமாக ஹோஸ்ட் செய்யப்பட்ட stack-ல் ஒரு similarity query எவ்வாறு செயல்படுகிறது என்பதைப் பார்ப்போம்.

  1. query உரை, embedding model மூலம் vector-ஆக மாற்றப்படுகிறது. CPU-வில் ஒரு சிறிய string-க்கு இது பத்து முதல் நூறு மில்லி விநாடிகள் வரை எடுக்கும். GPU-வில் இது ஒற்றை இலக்க மில்லி விநாடிகளில் முடிந்துவிடும்.
  2. vector, store-க்கு அனுப்பப்படுகிறது. loopback TCP அல்லது Unix domain socket வழியாக இது ஒரு மில்லி விநாடியின் ஒரு பகுதியிலேயே முடிந்துவிடும்.
  3. store அதன் index-ஐ ஆய்வு செய்து, அருகிலுள்ள வரிசைகளை (nearest rows) திருப்பித் தருகிறது.
  4. உங்கள் code, பொருந்தும் உரையை வாசித்து ஒரு prompt-ஐ உருவாக்குகிறது.

மேலே உள்ள பட்டியலில் படி 1 தான் பொதுவாக அதிக நேரத்தை எடுத்துக்கொள்கிறது. படி 2-ல் தான் ஹோஸ்ட் செய்யப்பட்ட vendors தங்களுக்குள் போட்டியிடுகிறார்கள், ஆனால் ஒரே server-ல் இது மிகக் குறைவான நேரமே எடுக்கும். நேரத்தை ஊகிக்க வேண்டாம். உங்கள் server-ல் இரண்டு முனைகளிலும் நேரத்தை அளவிடுங்கள்.

curl http://127.0.0.1:11434/api/embed -s -o /dev/null \
  -w 'embed: %{time_total}s\n' \
  -d '{"model": "nomic-embed-text", "input": "how do I rotate the api key"}'

பின்பு, search query-க்கு முன்னதாக \timing on-ஐ psql-ல் இயக்கவும். முதல் command embed: 0.184312s என்று அச்சிட்டு, psql-க்கு Time: 4.201 ms என்று பதில் கிடைத்தால், index-ஐ tuning செய்வது தவறான வேலை: உங்கள் latency-க்குக் காரணம் embedding model தான். Ollama மூலம் embedding model-ஐ உள்ளூர் அளவில் இயக்குதல் படி 1-ஐ, படி 2 மற்றும் 3 இயங்கும் அதே CPU-வில் வைக்கிறது, எனவே இரண்டு பகுதிகளும் ஒரே cores மற்றும் ஒரே RAM-க்காகப் போட்டியிடுகின்றன. இந்த store-ஐச் சுற்றியுள்ள ingest மற்றும் retrieval loop, சுயமாக ஹோஸ்ட் செய்யப்பட்ட RAG pipeline வழிகாட்டி-ல் விவரிக்கப்பட்டுள்ளது. RAG என்பது retrieval augmented generation ஆகும்: இதில் நீங்கள் உங்கள் சொந்த ஆவணங்களைத் தேடி, சிறந்த பொருத்தங்களை ஒரு prompt-ல் இணைக்கிறீர்கள்.

சுமார் ஒரு லட்சம் vectors-க்குக் கீழ், அனைத்தையும் scan செய்யுங்கள்

முழுமையான scan என்பது, சேமிக்கப்பட்டுள்ள ஒவ்வொரு vector-உடனும் query-ஐ ஒப்பிடுவதாகும். இதன் மூலம் கிடைக்கும் recall வரையறைப்படி துல்லியமானது. இதற்கு index அல்லது build step தேவையில்லை, மேலும் இது உங்கள் தரவுகளுக்குப் பின்னால் காலாவதியாகிவிடாது.

எந்த நிலையில் இது சரியாக இருக்காது என்பதை கணக்கீடு உங்களுக்கு உணர்த்தும். ஒரு scan, ஒவ்வொரு query-க்கும் n * d * 4 bytes-ஐ வாசிக்கும்; இதில் n என்பது vector எண்ணிக்கை மற்றும் d என்பது பரிமாணம் (dimension). 100,000 vectors மற்றும் 768 பரிமாணங்கள் கொண்ட தரவுகளுக்கு, ஒரு query-க்கு 307 MB தேவைப்படும். நவீன CPU-க்கள் இதனைச் சில பத்து மில்லி விநாடிகளில் (tens of milliseconds) கையாளும். 5 மில்லியன் vectors எனில், ஒரு query-க்கு 15 GB தேவைப்படும்; இது இனி ஒரு சாதாரண query அல்ல.

எனவே, vectors-ஐ SQLite-ல் சேமித்து, NumPy மூலம் ஒப்பீட்டைச் செய்யுங்கள்.

import sqlite3, numpy as np

db = sqlite3.connect("docs.db")
db.execute("CREATE TABLE IF NOT EXISTS docs (id INTEGER PRIMARY KEY, body TEXT, vec BLOB)")

def add(body, vec):
    v = np.asarray(vec, dtype=np.float32)
    v /= np.linalg.norm(v)
    db.execute("INSERT INTO docs (body, vec) VALUES (?, ?)", (body, v.tobytes()))
    db.commit()

def search(query_vec, k=5):
    rows = db.execute("SELECT id, body, vec FROM docs").fetchall()
    mat = np.frombuffer(b"".join(r[2] for r in rows), dtype=np.float32).reshape(len(rows), -1)
    q = np.asarray(query_vec, dtype=np.float32)
    q /= np.linalg.norm(q)
    scores = mat @ q
    return [(rows[i][0], rows[i][1], float(scores[i])) for i in np.argsort(-scores)[:k]]

இரண்டு பக்கங்களும் unit length-க்கு மாற்றப்படுவதால், dot product என்பது cosine similarity ஆகிறது; அதிக மதிப்பெண் என்பது நெருக்கமான பொருத்தத்தைக் குறிக்கும். mat-ஐ ஒவ்வொரு query-க்கும் ஒருமுறை ஏற்றுவதற்குப் பதிலாக, startup-ல் ஒருமுறை ஏற்றுங்கள்; இது SQLite வாசிப்பை hot path-லிருந்து முழுமையாக நீக்கிவிடும்.

நீங்கள் இதை நிராகரிக்கும் முன், உங்கள் சொந்த கணினியில் நேரத்தைச் சோதித்துப் பாருங்கள்.

import time
t = time.perf_counter()
search(q)
print(f"{(time.perf_counter() - t) * 1000:.1f} ms")

உண்மையான வரம்புகள் இவை: இது முழு matrix-ஐயும் RAM-ல் வைத்திருக்கும் ஒரு process ஆகும். இதில் metadata filtering வசதி இல்லை, மேலும் ஒரே நேரத்தில் பல writers-ஐக் கையாளும் வசதியும் இல்லை. இவற்றில் ஏதேனும் ஒன்று உங்களுக்குப் பிரச்சினையாக இருந்தால், வேறு முறைக்கு மாறுங்கள். SQLite என்பது ஒரு தீவிரமான server-side store ஆகும்; இது குறித்து the SQLite in production guide விரிவாக விளக்குகிறது. உங்கள் பணி rows-ஐ வழங்குவதை விட columns-ஐ scan செய்வதாக இருந்தால், the DuckDB and SQLite comparison உங்களுக்குப் பயனுள்ளதாக இருக்கும்.

ஏற்கனவே Postgres இயங்கிக்கொண்டிருக்கும்போது pgvector-ஐப் பயன்படுத்துதல்

உங்கள் application-ல் ஏற்கனவே Postgres database இருந்தால், pgvector மிகக்குறைந்த அளவிலான புதிய மாற்றங்களையே ஏற்படுத்தும். இது ஒரு தனி service அல்ல, ஒரு extension மட்டுமே. Vectors சாதாரண table-ல், அவை விவரிக்கும் row-க்கு அருகிலேயே சேமிக்கப்படும். எனவே, filtered search என்பது ஒரு WHERE clause மட்டுமே; இரண்டாவதாக ஒரு system-ஐ synchronize செய்ய வேண்டிய அவசியமில்லை.

Ubuntu 24.04-ன் universe component-ல் இது கிடைக்கிறது.

sudo apt update
sudo apt install -y postgresql-16-pgvector
sudo -u postgres psql -d yourdb -c 'CREATE EXTENSION vector;'

அக்டோபர் 2026 நிலவரப்படி, அந்த package-ல் pgvector 0.6.0 உள்ளது, இது upstream-ஐ விட மிகவும் பின்தங்கியுள்ளது. குறிப்பாக, iterative index scans-க்கு 0.8 பதிப்பு தேவை, எனவே அவற்றை PostgreSQL project-ன் சொந்த repository-லிருந்து பதிவிறக்கவும்.

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt install -y postgresql-17-pgvector

17-க்கு பதிலாக உங்கள் server-ன் major version-ஐக் குறிப்பிடவும், அதை sudo -u postgres psql -tAc 'SHOW server_version' காட்டும். தவறான major version-க்காக உருவாக்கப்பட்ட extension package-ஐ install செய்தால் CREATE EXTENSION தோல்வியடையும், ஏனெனில் இயங்கிக்கொண்டிருக்கும் version-ன் share directory-ல் மட்டுமே Postgres தேடும்:

ERROR:  could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directory

இதன் schema ஒரு புதிய type-ஐக் கொண்ட சாதாரண SQL ஆகும்.

CREATE TABLE chunks (
  id        bigserial PRIMARY KEY,
  doc_id    bigint NOT NULL,
  body      text   NOT NULL,
  embedding vector(768)
);

SELECT id, body FROM chunks
ORDER BY embedding <=> '[0.013, -0.021, 0.004]'
LIMIT 5;

<=> என்பது cosine distance, <-> என்பது L2 (Euclidean) distance, மற்றும் <#> என்பது negative inner product ஆகும். உங்கள் embedding model எதற்காகப் பயிற்சியளிக்கப்பட்டுள்ளதோ, அதைப் பயன்படுத்தவும். தவறானதைத் தேர்ந்தெடுத்தால் எந்தப் பிழையும் வராது, ஆனால் முடிவுகள் மட்டும் தரமற்றதாக இருக்கும்.

Index இல்லாதபோது, அந்த query ஒவ்வொரு row-விலும் துல்லியமான தேடலை (exact search) மேற்கொள்ளும். இது மேலே குறிப்பிட்ட brute force முறையின் Postgres பதிப்பாகும், மேலும் இது அதே முழுமையான recall-ஐத் தரும். max_parallel_workers_per_gather-ஐ அதிகரிப்பது கூடுதல் cores-ஐப் பயன்படுத்த உதவும். இதை முதலில் செய்துவிட்டு, பிறகு index-ஐ உருவாக்கவும்; அப்போதுதான் index-ன் செயல்திறனை அளவிட உங்களிடம் ஒரு recall baseline இருக்கும்.

Qdrant, index தரவுத்தளத்தின் அளவை விட அதிகரிக்கும் போது

Qdrant என்பது Rust மொழியில் எழுதப்பட்ட ஒரு பிரத்யேக vector store ஆகும். உங்கள் index-ன் அளவு பெரியதாகி, அதன் build செயல்முறை உங்கள் application-ன் Postgres-உடன் போட்டியிடக் கூடாது என்று நீங்கள் நினைக்கும் போது, Qdrant சிறந்த தேர்வாகிறது. அல்லது, உங்களுக்கு payload filtering மற்றும் உள்ளமைக்கப்பட்ட scalar அல்லது product quantisation தேவைப்படும்போது இதைப் பயன்படுத்தலாம்; ஏனெனில் pgvector-ல் half precision மற்றும் binary quantisation மட்டுமே உள்ளன.

docker run -d --name qdrant \
  -p 127.0.0.1:6333:6333 -p 127.0.0.1:6334:6334 \
  -e QDRANT__SERVICE__API_KEY="$(openssl rand -hex 32)" \
  -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
  qdrant/qdrant

Port 6333 ஆனது REST (representational state transfer) API மற்றும் /dashboard-ல் உள்ள dashboard-ஐ வழங்குகிறது, மேலும் 6334 ஆனது gRPC-ஐ வழங்குகிறது. பொது VPS-ல் இரண்டு விவரங்கள் முக்கியமானவை. Qdrant-ன் சொந்த ஆவணங்களின்படி, இந்த service இயல்பாகவே "எந்தவிதமான குறியாக்கமும் (encryption) அல்லது அங்கீகாரமும் (authentication) இன்றி" இயங்குகிறது. மேலும், quickstart-ல் உள்ள -p 6333:6333 அனைத்து interface-களிலும் இணைகிறது (bind). Docker தனது சொந்த forwarding விதிகளை எழுதுவதால், இது ufw விதியையும் மீறி port-ஐத் திறக்கிறது. 127.0.0.1-ல் bind செய்து, ஒரு API key-ஐ அமைக்கவும். API key இன்றி பொது IP-ல் அணுகக்கூடிய Qdrant instance என்பது, உங்கள் ஆவணங்களின் பொதுப்படையான நகலாகும்.

curl -s http://127.0.0.1:6333/collections -H "api-key: $QDRANT_API_KEY"

சரியான பதில் {"result":{"collections":[]},"status":"ok","time":0.00002} போல இருக்கும். {"status":{"error":"Unauthorized"}} என்று பதில் கிடைத்தால், header பெயர் அல்லது key தவறாக உள்ளது என்று அர்த்தம். எதுவுமே கிடைக்கவில்லை என்றால், container இயங்கவில்லை அல்லது வேறு எங்கோ bind செய்யப்பட்டுள்ளது என்று அர்த்தம். அந்த container உங்கள் server-க்கு ஏற்றதா என்பது ஒரு சாதாரண stateful-service சார்ந்த கேள்வி, எனவே Docker மற்றும் host database ஒப்பீடு இதற்கும் அப்படியே பொருந்தும்.

Chroma மற்றும் அதன் பயன்பாடு

Chroma என்பது எவ்வித தயாரிப்பும் இன்றி, ஒரு retrieval demo-வை விரைவாக உருவாக்குவதற்கான எளிய வழியாகும்.

pip install chromadb
chroma run --path /srv/chroma

இது 8000 port-ல் இயங்குகிறது, மேலும் chromadb.HttpClient(host="localhost", port=8000) இதனுடன் இணைகிறது. Chroma ஒரு default embedding function-ஐக் கொண்டுள்ளதால், ஆரம்பக்கட்ட prototype-க்குத் தனி model server தேவையில்லை.

இந்தத் தேர்வின் சாதக பாதகங்களை வெளிப்படையாகப் புரிந்துகொள்ள வேண்டும். Chroma-வைப் பயன்படுத்துவது எளிது, ஏனெனில் இந்த வழிகாட்டியில் விவாதிக்கப்படும் சிக்கலான முடிவுகளை இது மறைத்துவிடுகிறது: எந்த distance metric-ஐப் பயன்படுத்துவது, எவ்வளவு RAM தேவைப்படும் என்பது போன்றவற்றை இதுவே கவனித்துக்கொள்கிறது. ஒரு prototype-க்கு இது சரியாக இருக்கலாம், ஆனால் நீங்கள் பொறுப்பேற்று நிர்வகிக்கும் production சூழலுக்கு இது பொருத்தமற்றதாக இருக்கலாம். உங்கள் தரவு ஏற்கனவே Postgres-ல் இருந்தால், அதை Chroma-விற்கு மாற்றுவது ஒரு கூடுதல் process-ஐ உருவாக்குவதுடன், தரவு ஒத்திசைவு (synchronisation) சிக்கலையும் ஏற்படுத்தும். pgvector-ல் இல்லாத இத்தகைய சிக்கல்களைத் தவிர்க்க, உங்கள் தேவையைச் சரியாக மதிப்பிடுவது அவசியம்.

குறியீட்டிற்கு (index) எவ்வளவு RAM தேவைப்படும்

Raw vectors-ல் இருந்து தொடங்குங்கள், ஏனெனில் அவைதான் அடிப்படைத் தேவை; எந்தவொரு tuning-ம் அவற்றின் அளவைக் குறைக்காது.

bytes = number_of_vectors * dimensions * 4

ஒவ்வொரு dimension-க்கும் ஒரு 32-bit float-க்கு நான்கு bytes தேவைப்படும். RAM-ஐ விட அதிகமாக வளரும் collections குறித்த Qdrant-ன் வழிகாட்டி, metadata, indexes மற்றும் optimization-ன் போது உருவாக்கப்படும் தற்காலிக segments ஆகியவற்றிற்காக 1.5 மடங்கு கூடுதல் இடத்தைப் பரிந்துரைக்கிறது:

memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5

ஒரு மில்லியன் vectors-க்கு, தற்போதைய embedding models உருவாக்கும் dimensions-ஐக் கொண்டு கணக்கிடப்பட்ட சூத்திரம் இதோ:

விளக்கப்படம்RAM for 1 million vectors, by embedding dimension
இந்த விளக்கப்படத்தின் தரவு
[
  {
    "label": "384 dims",
    "raw_gib": 1.43,
    "with_overhead_gib": 2.15
  },
  {
    "label": "768 dims",
    "raw_gib": 2.86,
    "with_overhead_gib": 4.29
  },
  {
    "label": "1024 dims",
    "raw_gib": 3.81,
    "with_overhead_gib": 5.72
  },
  {
    "label": "1536 dims",
    "raw_gib": 5.72,
    "with_overhead_gib": 8.58
  },
  {
    "label": "3072 dims",
    "raw_gib": 11.44,
    "with_overhead_gib": 17.17
  }
]

இவை சூத்திரத்தின் வெளியீடுகள் (GiB-ல்), அளவீடுகள் அல்ல. இவற்றை நினைவகத்தில் நீங்கள் ஒதுக்க வேண்டிய இடமாகப் பார்க்கவும். ஒரு மில்லியன் chunks-க்கு nomic-embed-text போன்ற 768-dimension model-க்கு சுமார் 4.29 GiB தேவைப்படும்; இது 8 GB திட்டத்தில் Postgres-க்கு இடமளித்து பொருந்தும். அதே தரவுத்தொகுப்பு 3072 dimensions-ல் இருந்தால் 17.17 GiB தேவைப்படும், இது பொருந்தாது.

இங்கு முக்கியமானது அட்டவணையின் முதல் நெடுவரிசைதான், கடைசி அல்ல. Dimension-ஐ பாதியாகக் குறைப்பது, அதன் பின் வரும் அனைத்து byte அளவுகளையும் பாதியாகக் குறைக்கும். பொதுவான leaderboard-ல் சற்று குறைவான மதிப்பெண் பெறும் 768-dimension model, ஒரு VPS-ல் சிறந்த பொறியியல் தேர்வாக அமையும். pgvector-ன் halfvec வகை 16-bit floats-ஐச் சேமிக்கும், இது byte அளவை மீண்டும் பாதியாகக் குறைக்கும், மேலும் இது ஒரு expression மூலம் குறியீட்டை (index) உருவாக்கும்:

CREATE INDEX ON chunks USING hnsw ((embedding::halfvec(768)) halfvec_cosine_ops);

ஒரு model-ஐத் தேர்ந்தெடுக்கும் முன் கவனிக்க வேண்டிய வரம்பு இது. pgvector-ன் vector வகை 16,000 dimensions வரை ஏற்கும், ஆனால் அதன் HNSW மற்றும் IVFFlat indexes 2,000 வரை மட்டுமே செயல்படும். ஒரு halfvec cast 4,000 வரை செல்லும். அதற்கு மேல், pgvector binary quantisation-ஐப் பரிந்துரைக்கிறது: bit_hamming_ops மூலம் ஒரு binary_quantize(embedding)::bit(N) expression-ஐ index செய்யவும், இது 64,000 dimensions வரை ஏற்கும், பின்னர் சிறந்த முடிவுகளை அசல் vectors-உடன் ஒப்பிட்டு மறுவரிசைப்படுத்தவும் (re-rank).

build time-ல் m மற்றும் ef_construction-ன் தாக்கம்

HNSW (hierarchical navigable small world) என்பது pgvector மற்றும் Qdrant ஆகிய இரண்டிலும் பயன்படுத்தப்படும் index ஆகும். இது அடுக்குகளைக் கொண்ட ஒரு வரைபடம் (layered graph). ஒவ்வொரு vector-ம் ஒரு node ஆகும்; அது அருகிலுள்ள node-களுடன் இணைக்கப்பட்டிருக்கும். ஒரு தேடலின் போது, அனைத்தையும் வாசிப்பதற்குப் பதிலாக, இந்த இணைப்புகளைப் பயன்படுத்தி query-ஐ நோக்கித் தாவும்.

m என்பது ஒவ்வொரு node-ம் எத்தனை இணைப்புகளைத் தக்கவைத்துக் கொள்ளும் என்பதைக் குறிக்கிறது. Faiss ஆவணங்களின்படி, HNSW-ன் நினைவகத் தேவை ஒரு vector-க்கு (d * 4 + m * 2 * 4) bytes ஆகும்; m மதிப்பை 4 முதல் 64-க்குள் வைத்திருக்கப் பரிந்துரைக்கப்படுகிறது. இதை 768 பரிமாணங்கள் (dimensions) மற்றும் ஒரு மில்லியன் vector-களுக்குப் பயன்படுத்தவும்.

விளக்கப்படம்Cost of raising m at 768 dimensions, 1 million vectors
இந்த விளக்கப்படத்தின் தரவு
[
  {
    "label": "m = 8",
    "link_bytes_per_vector": 64,
    "total_gib": 2.92
  },
  {
    "label": "m = 16 (default)",
    "link_bytes_per_vector": 128,
    "total_gib": 2.98
  },
  {
    "label": "m = 32",
    "link_bytes_per_vector": 256,
    "total_gib": 3.1
  },
  {
    "label": "m = 64",
    "link_bytes_per_vector": 512,
    "total_gib": 3.34
  }
]

இதன் அளவு வேறுபாடு ஆச்சரியமளிக்கும் வகையில் இருக்கும். இயல்புநிலை m = 16-லிருந்து m = 64-க்கு மாறும்போது, இணைப்புகளின் அளவு 128-லிருந்து 512 bytes ஆக உயர்கிறது. இது 3072 bytes vector தரவுகளுடன் கூடுதலாக 384 bytes-ஐச் சேர்க்கிறது. இதனால் மொத்த அளவு 2.98 GiB-லிருந்து 3.34 GiB ஆக உயர்கிறது. இது சுமார் 12 சதவீத அதிகரிப்பு மட்டுமே. இந்த பரிமாணங்களில், உங்கள் நினைவகம் m-ஆல் செலவாவதில்லை; vector-களாலேயே செலவாகிறது.

m-ன் உண்மையான விலை அதன் build time மற்றும் insert time ஆகும். ஏனெனில், ஒரு node-ஐ இணைப்பது என்பது அத்தனை அண்டை node-களைக் கண்டறிந்து இணைப்பதைக் குறிக்கும். ef_construction என்பது ஒவ்வொரு node-ஐயும் அமைக்கும்போது builder கருத்தில் கொள்ளும் candidate list-ன் அளவு ஆகும். இதை அதிகரிப்பது சிறந்த வரைபடத்தை உருவாக்கும், ஆனால் build நேரத்தை மெதுவாக்கும். இது இறுதி index-ன் அளவை மாற்றாது.

SET maintenance_work_mem = '4GB';
SET max_parallel_maintenance_workers = 7;
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

maintenance_work_mem என்பது build செயல்முறை நிமிடங்களில் முடியுமா அல்லது மணிநேரங்கள் ஆகுமா என்பதைத் தீர்மானிக்கும் அமைப்பாகும். ஏனெனில், நினைவகத்தில் இடமிருக்கும் வரை pgvector வரைபடத்தை அங்கேயே தொகுக்கும். நினைவகம் போதவில்லை என்றால், pgvector பின்வருமாறு தெரிவிக்கும்:

NOTICE:  hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
DETAIL:  Building will take significantly more time.
HINT:  Increase maintenance_work_mem to speed up builds.

இந்த அறிவிப்பு pgvector வெளியிடும் மிக முக்கியமான வரியாகும். build செயல்முறை மிக மெதுவான பாதைக்கு மாறிவிட்டது என்று அர்த்தம். எனவே, அதை நிறுத்திவிட்டு, மேலே நீங்கள் கணக்கிட்ட RAM அளவை விட இந்த அமைப்பை அதிகரித்துவிட்டு மீண்டும் தொடங்கவும். மற்றொரு session-லிருந்து முன்னேற்றத்தைக் கண்காணிக்கவும்:

SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS pct
FROM pg_stat_progress_create_index;

HNSW initializing மற்றும் loading tuples ஆகியவற்றைத் தெரிவிக்கும். ஒரு build நீண்ட நேரம் குறைந்த சதவீதத்திலேயே நின்று, disk பயன்பாடு குறைவாக இருந்தால், அது maintenance_work_mem சிக்கலே தவிர, query சிக்கல் அல்ல.

திட்டமிடும்போது கவனிக்க வேண்டிய இரண்டு உண்மைகள்: pgvector-ன் README-ன்படி, IVFFlat-ஐ விட HNSW "மெதுவான build நேரத்தைக் கொண்டது மற்றும் அதிக நினைவகத்தைப் பயன்படுத்துகிறது", ஆனால் இது வேகத்திற்கும் recall-க்கும் இடையிலான சிறந்த சமநிலையை வழங்குகிறது. HNSW-ஐ காலியான அட்டவணையில் (empty table) உருவாக்க முடியும். ஆனால் IVFFlat-க்கு முதலில் தரவுகளைக் கொண்டு k-means இயக்க வேண்டும், எனவே காலியான அட்டவணையில் அதை உருவாக்கினால் recall குறைவாக இருக்கும். புதிய schema-வில் HNSW-ஐ நீங்கள் ஆரம்பத்திலேயே உருவாக்கலாம்.

ef_search: build-க்கு பிறகு நீங்கள் மாற்றக்கூடிய அளவுரு

m மற்றும் ef_construction ஆகியவை index-ல் முடக்கப்பட்டவை (frozen). ef_search அவ்வாறு இல்லை. இது graph-ல் தேடும்போது எத்தனை வேட்பாளர்களை (candidates) வைத்திருக்க வேண்டும் என்பதைத் தீர்மானிக்கிறது; இதை ஒவ்வொரு session-க்கும் அல்லது query-க்கும் ஏற்ப நீங்கள் மாற்றிக்கொள்ளலாம்.

SET hnsw.ef_search = 100;
SELECT id, body FROM chunks ORDER BY embedding <=> $1 LIMIT 5;

இதன் default மதிப்பு 40 ஆகும். இதை அதிகரித்தால் recall உயரும், அதனுடன் latency-யும் உயரும். இதைக் குறைத்தால் இரண்டுமே குறையும். index-ஐ மீண்டும் உருவாக்காமலேயே (rebuild) நீங்கள் மாற்றக்கூடிய ஒரே recall கட்டுப்பாடு இதுதான். எனவே, சரியான விடைகள் உங்களுக்குத் தெரிந்த ஒரு குறிப்பிட்ட query தொகுப்பைக் கொண்டு இதைச் சோதித்து, recall மேம்படுவது நிற்கும் வரை சரிசெய்யவும்.

ஒரு சிக்கல் உள்ளது. ef_search ஒரு selective WHERE clause-உடன் சரியாகச் செயல்படாது. ஏனெனில், index ஒரு குறிப்பிட்ட எண்ணிக்கையிலான வேட்பாளர்களை மட்டுமே வழங்குகிறது, அதன் பிறகுதான் filter பயன்படுத்தப்படுகிறது. பெரும்பாலான வரிசைகளை (rows) நிராகரிக்கும் ஒரு filter-ஐப் பயன்படுத்தும்போது, அட்டவணையில் பொருந்தக்கூடிய வரிசைகள் இருந்தபோதிலும், உங்களுக்கு LIMIT-க்கும் குறைவான முடிவுகளே கிடைக்கலாம். pgvector 0.8 இதற்கு iterative scans மூலம் தீர்வு வழங்குகிறது:

SET hnsw.iterative_scan = relaxed_order;

limit பூர்த்தியாகும் வரை index மீண்டும் மீண்டும் scan செய்யப்படும். இது hnsw.max_scan_tuples வரை செல்லும், இதன் default மதிப்பு 20000 ஆகும். strict_order துல்லியமான distance வரிசையைத் தக்கவைக்கிறது, ஆனால் இதற்கு அதிக செலவாகும். Ubuntu 0.6.0 தொகுப்பில் இந்த வசதி இல்லை; filter பயன்படுத்தும்போது முடிவுகள் விடுபடுவதுதான் இந்த வசதி இல்லை என்பதற்கான அறிகுறியாகும்.

Index ஏன் RAM-ல் இருக்க வேண்டும்

HNSW தேடல் என்பது ஒரு வரைபடத்தின் (graph) வழியே செல்லும் பயணமாகும். ஒவ்வொரு தாவலும் (hop) முந்தைய இடத்திலிருந்து தொடர்பில்லாத ஒரு இடத்தில் சேமிக்கப்பட்டுள்ள node-ஐ வாசிக்கிறது. எனவே, இந்த அணுகல் முறை (access pattern) சீரற்றதாக (random) இருக்கும், மேலும் read-ahead நுட்பம் இதில் உதவாது. வரைபடம் RAM-ல் இருக்கும் வரை, ஒவ்வொரு தாவலும் ஒரு memory reference-ஆக இருக்கும். அது RAM-ல் இல்லாதபோது, ஒரு தாவல் disk read-ஆக மாறக்கூடும். சில நூறு node-களைத் தொடும் ஒரு தேடல், சில நூறு disk read-களாக மாறிவிடும்.

Qdrant ஆவணங்கள் இதைத் தெளிவாக விளக்குகின்றன: "நீங்கள் பாதியளவு vectors-ஐ மட்டுமே RAM-ல் சேமித்தால், தேடல் தாமதம் (search latency) தோராயமாக இருமடங்காகும்." இந்த உண்மையை மனதில் கொண்டு திட்டமிடுங்கள்.

Index முழுமையாக RAM-ல் பொருந்தாதபோது, நீங்கள் எடுக்கும் ஒவ்வொரு முடிவும் ஒரு சமரசமே (trade-off). அதைத் திட்டமிட்டுச் செய்யுங்கள்:

  • Vectors-ஐ memory-map செய்யுங்கள். இதன் மூலம் operating system அடிக்கடி பயன்படுத்தப்படும் (hot) பக்கங்களை cache-ல் வைக்கும், பயன்படுத்தப்படாத (cold) பக்கங்களை disk-ல் விட்டுவிடும். இது தாங்கக்கூடிய வேகத்தில் இருக்க, கீழே வேகமான NVMe (non-volatile memory express) சேமிப்பகம் தேவை.
  • Quantise செய்யுங்கள். ஒவ்வொரு பரிமாணத்தையும் (dimension) நான்கு bytes-க்கு பதிலாக ஒரு byte-ஆகச் சேமிக்கவும். இது vector-ன் அளவை நான்கு மடங்காகக் குறைக்கும், ஆனால் தேடல் துல்லியத்தில் (recall) சிறிய, அளவிடக்கூடிய இழப்பு ஏற்படும்.
  • pgvector-ல் halfvec-க்கு cast செய்யுங்கள். இது ஒரு-byte quantisation-ஐ விடக் குறைவான துல்லிய இழப்புடன், bytes-ன் அளவை பாதியாகக் குறைக்கும்.
  • சிறிய model-ஐப் பயன்படுத்தி embed செய்யுங்கள். இதுவே மிகவும் எளிதான தீர்வு, ஆனால் மக்கள் இதைத் தவிர்க்கிறார்கள். ஏனெனில், இது முழு தரவுத்தொகுப்பையும் (corpus) மீண்டும் embed செய்ய வேண்டிய கட்டாயத்தை உருவாக்குகிறது.

தோல்வி முறைகளும் நீங்கள் காணும் சரங்களும்

could not open extension control file. இயங்கும் Postgres முதன்மைப் பதிப்பிற்கான pgvector தொகுப்பு நிறுவப்படவில்லை. sudo -u postgres psql -tAc 'SHOW server_version' மூலம் பதிப்பைக் கண்டறிந்து, அதற்குப் பொருத்தமான postgresql-NN-pgvector-ஐ நிறுவவும்.

ERROR: expected 768 dimensions, not 1536. நெடுவரிசை வகை (column type) மற்றும் மாதிரி (model) ஆகியவற்றுக்கு இடையே முரண்பாடு உள்ளது. நீங்கள் embedding மாதிரிகளை மாற்றியுள்ளீர்கள், ஆனால் தரவுகளை மீண்டும் உட்பொதிக்கவில்லை (re-embed). இதற்குப் பகுதித் தீர்வு ஏதுமில்லை, ஏனெனில் வெவ்வேறு மாதிரிகளிலிருந்து பெறப்பட்ட வெக்டர்களை ஒப்பிட முடியாது. எனவே, ஒவ்வொரு வரிசையையும் மீண்டும் உருவாக்க வேண்டும்.

வினவல் மெதுவாக உள்ளது மற்றும் EXPLAIN ஒரு sequential scan-ஐக் காட்டுகிறது. குறியீட்டு ஆபரேட்டர் வகுப்பு (index operator class) மற்றும் வினவல் ஆபரேட்டர் ஆகியவை பொருந்தவில்லை. vector_cosine_ops ஆனது <=>-க்கு மட்டுமே உதவும். வினவலில் EXPLAIN ANALYZE-ஐ இயக்கி, Index Scan using ... on chunks உள்ளதா என்று பார்க்கவும். அதற்குப் பதிலாக Seq Scan on chunks-ஐக் கண்டால், நீங்கள் பயன்படுத்தும் வினவல் ஆபரேட்டருக்குப் பொருந்தும் வகையில் குறியீட்டு ஆபரேட்டர் வகுப்பைக் கொண்டு குறியீட்டை மீண்டும் உருவாக்கவும்.

LIMIT-ஐ விடக் குறைவான வரிசைகள் மற்றும் WHERE நிபந்தனை உள்ளது. இது மேலே குறிப்பிடப்பட்ட வடிகட்டுதல் சிக்கலாகும் (filtering trap). hnsw.ef_search-ஐ அதிகரிக்கவும் அல்லது pgvector 0.8-க்கு மாறி hnsw.iterative_scan-ஐ அமைக்கவும்.

குறியீட்டு உருவாக்கம் செயல்முறை முடிந்து, psql-ல் எந்தப் பிழையும் இல்லை. maintenance_work_mem கணினியின் பெரும்பகுதி நினைவகத்திற்கு அமைக்கப்பட்டுள்ளது, ஆனால் shared_buffers மற்றும் உங்கள் பயன்பாட்டிற்கும் நினைவகம் தேவைப்படுவதால், கர்னலின் out-of-memory killer அதை நிறுத்துகிறது. sudo dmesg -T | grep -i 'killed process'-ல் postgres-ஐக் குறிப்பிடும் வரியைக் காணலாம். இந்த அமைப்பைக் குறைக்கவும் அல்லது அதிக நினைவகம் கொண்ட திட்டத்தில் குறியீட்டை உருவாக்கி, தரவுத்தளத்தை மீட்டெடுக்கவும் (restore).

ஒன்றைத் தேர்ந்தெடுத்தல்

நீங்கள் ஏற்கனவே Postgres-ஐப் பயன்படுத்தி, சில மில்லியன் வெக்டார்களுக்கும் குறைவாக வைத்திருந்தால், pgvector-ஐப் பயன்படுத்தவும். இதன் index தரவுகளுக்கு அருகிலேயே அமையும், filtering என்பது ஒரு WHERE clause ஆகும், மேலும் உங்கள் தற்போதைய backup முறையே இதையும் உள்ளடக்கும். ஒருவேளை index-க்குத் தனி மெமரி தேவைப்பட்டாலோ அல்லது அதிகப்படியான payload filtering தேவைப்பட்டாலோ, Qdrant-ஐப் பயன்படுத்தவும்; ஆனால் இதற்காக இரண்டாவது service-ஐ நிர்வகிக்க வேண்டியிருக்கும்.

சுமார் நூறாயிரம் வெக்டார்களுக்குக் கீழ் இருந்தால், எதையும் நிறுவும் முன் brute-force scan-ஐச் சோதித்துப் பார்க்கவும். அந்த அளவில், துல்லியமான முடிவுகளைத் தரும் exhaustive search முறையே சிறந்தது; இதற்கு build step தேவையில்லை. இதுவே சரியான அணுகுமுறை. மாறாக, approximate index-ஐத் தேர்ந்தெடுப்பது என்பது, உங்களுக்குத் தேவைப்படாத மில்லி விநாடி வேகத்திற்காக, RAM பயன்பாடு மற்றும் tuning சிக்கல்களை நீங்களே உருவாக்கிக்கொள்வதாகும்.

FAQ

எனக்கு ஒரு பிரத்யேக vector database தேவையா, அல்லது Postgres போதுமானதா?

உங்கள் தரவு ஏற்கனவே Postgres-ல் இருந்தால், பெரும்பாலான ஒப்பீடுகள் கூறுவதை விட pgvector நீண்ட காலத்திற்குப் போதுமானதாக இருக்கும். இது vectors-ஐ ஒரு சாதாரண column-ல் சேமிக்கிறது, எனவே filtered search என்பது ஒரு WHERE clause மட்டுமே; உங்கள் தற்போதைய backup முறையே index-க்கும் பொருந்தும். Vector பணிக்குத் தனி memory தேவைப்படும்போதோ, அல்லது pgvector-ல் இல்லாத payload filtering மற்றும் scalar அல்லது product quantisation தேவைப்படும்போதோ Qdrant போன்ற பிரத்யேக store-க்கு மாறுங்கள்.

ஒரு VPS-ல் எத்தனை vectors-ஐச் சேமிக்க முடியும்?

ஊகிப்பதற்குப் பதிலாக, number_of_vectors * dimensions * 4 bytes * 1.5-ஐப் பயன்படுத்தி கணக்கிடுங்கள். 768-பரிமாணங்கள் கொண்ட ஒரு மில்லியன் vectors தோராயமாக 4.3 GiB ஆகும், எனவே 8 GB plan-ல் Postgres-க்கு இடமளித்து இதையும் சேமிக்க முடியும். 3072-பரிமாணங்கள் கொண்ட ஒரு மில்லியன் vectors தோராயமாக 17 GiB ஆகும், இதற்கு மிகப் பெரிய plan தேவைப்படும். உங்கள் embedding model-ன் பரிமாணமே memory பயன்பாட்டைத் தீர்மானிக்கும் முக்கிய காரணி, எனவே memory செலவைக் கருத்தில் கொண்டு model-ஐத் தேர்வு செய்யுங்கள்.

ஒரே machine-ல் இருக்கும்போது network உங்கள் பிரச்சினை அல்ல, எனவே மற்ற இரண்டு காரணங்களைச் சோதியுங்கள். முதலாவதாக, embedding call-ஐ மட்டும் தனியாகச் சோதியுங்கள், ஏனெனில் query vector-ஐ CPU-ல் உருவாக்குவது search-ஐ விட அதிக நேரம் எடுக்கலாம். இரண்டாவதாக, index RAM-ல் உள்ளதா என்று பாருங்கள். HNSW search ஒரு graph-ல் சீரற்ற முறையில் தாவுகிறது, எனவே graph disk-க்குச் சென்றால், ஒவ்வொரு தாவலும் disk read ஆக மாறக்கூடும். RAM-ல் உள்ள vectors-ன் எண்ணிக்கையை பாதியாகக் குறைத்தால், search latency தோராயமாக இரண்டு மடங்காகும் என்பதே Qdrant-ன் அறிவுரை.

நான் HNSW index-ஐ உருவாக்க வேண்டுமா?

சுமார் ஒரு லட்சம் vectors-க்குக் கீழ் தேவையில்லை. ஒரு முழுமையான scan ஒவ்வொரு query-க்கும் n * d * 4 bytes-ஐ வாசிக்கும்; 100,000 vectors மற்றும் 768 பரிமாணங்களுக்கு இது 307 MB ஆகும். நவீன CPU-க்கள் இதைச் சில பத்து மில்லி விநாடிகளில், எந்த build step-ம் இன்றி, துல்லியமான recall-உடன் கையாளும். முதலில் உங்கள் சொந்த hardware-ல் scan நேரத்தைச் சோதியுங்கள். ஒரு benchmark கட்டுரை கூறியது என்பதற்காக அல்லாமல், scan நேரம் மிக மெதுவாக இருப்பதாக நீங்கள் உணர்ந்தால் மட்டும் index-ஐ உருவாக்குங்கள்.

m-ஐ அதிகரிப்பதால் எனக்கு என்ன செலவு?

இது memory-ஐ விட, build time மற்றும் insert time-ஐ அதிகம் பாதிக்கும். 768 பரிமாணங்களில், default m = 16-லிருந்து m = 64-க்கு மாறும்போது, graph links ஒரு vector-க்கு 128 bytes-லிருந்து 512 bytes ஆக உயர்கிறது. இது 3072 bytes vector தரவுக்கு எதிராகக் கூடுதலாக 384 bytes ஆகும், எனவே மொத்த memory சுமார் 12 சதவீதம் உயரும். இருப்பினும், ஒவ்வொரு insert-ம் நான்கு மடங்கு அதிகமான அண்டை உறவுகளைக் கண்டறிந்து இணைக்க வேண்டும். முதலில் ef_search-ஐச் சரிசெய்யுங்கள், ஏனெனில் அதை மாற்றுவதற்குச் செலவு இல்லை மற்றும் rebuild தேவையில்லை.

இந்தக் கட்டுரை பயனுள்ளதாக இருந்ததா?

#vector-database#rag#pgvector#qdrant#self-hosting