SSD Nodes Learn Hosting plans →
मार्गदर्शक Matt Connorद्वारे Matt Connor

VPS वर vector database चालवणे: pgvector की Qdrant?

तुमचे application आणि index एकाच VPS वर असल्याने latency मुख्य अडचण नाही. pgvector, Qdrant, Chroma आणि brute force तुलना करून RAM योग्य मोजा.

VPS वर vector database चालवण्याचा प्रत्यक्ष खर्च

VPS (virtual private server) वर vector database चालवल्यामुळे managed vendor ज्या समस्येचे निराकरण विकतो, ती समस्या दूर होते. तुमचे application आणि index एकाच मशीनवर असतात. त्यामुळे search request नेटवर्कऐवजी loopback socket मधून जाते. उरतो तो नेहमीचाच खरा खर्च: मजकुराचे vectors मध्ये रूपांतर करणे. यासोबत आणखी दोन खर्च असतात: index तयार करण्यासाठी लागणारा वेळ आणि तो सेवा देत असेपर्यंत index ठेवण्यासाठी लागणारी RAM.

यामुळे कोणते निर्णय महत्त्वाचे आहेत हे बदलते. Region आणि endpoint round trip यांची चिंता उरत नाही. त्याऐवजी vector count, dimensions आणि four bytes यांचा गुणाकार महत्त्वाचा ठरतो, कारण त्यावर तुम्ही दरमहा भाड्याने घेतलेल्या memory मध्ये index बसेल की नाही हे ठरते.

एका बॉक्सवर मिलीसेकंद प्रत्यक्षात कुठे खर्च होतात

self-hosted stack मधील एका similarity query चा संपूर्ण प्रवास पाहूया.

  1. Embedding model query text चे vector मध्ये रूपांतर करते. CPU वर लहान string साठी याला दहा ते काहीशे मिलीसेकंद लागतात. GPU वर हा वेळ single-digit मिलीसेकंद असतो.
  2. Vector store कडे पाठवला जातो. Loopback TCP किंवा Unix domain socket वर यासाठी मिलीसेकंदाच्या अंशाइतका वेळ लागतो.
  3. Store आपला index तपासतो आणि सर्वात जवळच्या rows परत करतो.
  4. तुमचा code जुळणारा text वाचतो आणि prompt तयार करतो.

या यादीत Step 1 ची वेळ साधारणपणे सर्वाधिक असते. Hosted vendors ज्या step वर स्पर्धा करतात तो Step 2 आहे; मात्र एकाच बॉक्सवर तो जवळजवळ अस्तित्वातच नसतो. हा विभाग अंदाजाने ठरवू नका. तुमच्या स्वतःच्या 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 स्थानिक पातळीवर चालवणे यामुळे Step 1 हा Step 2 आणि Step 3 ज्या CPU वर चालतात त्याच CPU वर चालतो. त्यामुळे दोन्ही भाग समान cores आणि समान RAM साठी स्पर्धा करतात. या store भोवतीचा ingest आणि retrieval loop self-hosted RAG pipeline मार्गदर्शिकेत स्पष्ट केला आहे. RAG म्हणजे retrieval augmented generation: तुम्ही तुमचे स्वतःचे documents शोधता आणि सर्वोत्तम matches prompt मध्ये जोडता.

शंभर हजारांपेक्षा कमी vectors असतील, तर ते सर्व scan करा

Exhaustive scan प्रत्येक stored vector ची query सोबत तुलना करतो. व्याख्येनुसार recall परिपूर्ण असतो. यासाठी index किंवा build step आवश्यक नसतो. तसेच तुमच्या data च्या तुलनेत तो out of date होण्याची शक्यताही नसते.

ही पद्धत कधी अव्यवहार्य होते हे arithmetic स्पष्ट करते. प्रत्येक query साठी scan n * d * 4 bytes वाचतो. येथे n ही vector count आणि d ही dimension आहे. 768 dimensions असलेल्या 100,000 vectors साठी हे प्रत्येक query मागे 307 MB होते. आधुनिक CPU हे काही दहा milliseconds मध्ये stream करू शकतो. 5 million vectors असतील, तर प्रत्येक query मागे 15 GB वाचावे लागते. त्या टप्प्यावर ते query राहात नाही.

म्हणून vectors SQLite मध्ये साठवा आणि comparison 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 वर scale केल्या आहेत. त्यामुळे dot product cosine similarity असतो आणि जास्त score म्हणजे अधिक जवळचा match. mat प्रत्येक query वेळी load करण्याऐवजी startup वेळी एकदाच load करा. त्यामुळे SQLite read पूर्णपणे hot path मधून बाहेर पडतो.

ही पद्धत नाकारण्यापूर्वी ती तुमच्या स्वतःच्या box वर time करा.

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

प्रामाणिक मर्यादा अशा आहेत: संपूर्ण matrix RAM मध्ये ठेवणारा हा एक process आहे. तसेच यात metadata filtering नाही आणि concurrent writer साठी स्पष्ट व्यवस्था नाही. यापैकी एखादी बाब तुमच्या अडचणीचे कारण असेल, तर पुढील पद्धतीकडे जा. SQLite स्वतः server-side store म्हणून सक्षम आहे. याची सविस्तर माहिती production मधील SQLite मार्गदर्शक मध्ये आहे. तुमचा खरा workload rows serve करण्याऐवजी columns scan करत असेल, तर DuckDB आणि SQLite तुलना अधिक उपयुक्त ठरेल.

Postgres आधीपासून चालू असल्यास pgvector

तुमच्या अॅप्लिकेशनकडे आधीपासून Postgres database असल्यास, pgvector मुळे नवीन पायाभूत बदल सर्वात कमी होतात. हे service नसून extension आहे. Vectors त्यांचे वर्णन करणाऱ्या row च्या शेजारी सामान्य table मध्ये ठेवता येतात. त्यामुळे filtered search साठी समक्रमित ठेवायची दुसरी system न वापरता WHERE clause वापरता येतो.

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;'

October 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 साठी build केलेले extension package install केल्यास CREATE EXTENSION अयशस्वी होते, कारण Postgres चालू असलेल्या version च्या share directory मध्येच शोध घेतो:

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

Schema सामान्य SQL आहे आणि त्यात फक्त एक नवीन type आहे.

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 ज्या प्रकारासाठी train केले आहे तो प्रकार वापरा. चुकीचा प्रकार निवडल्यास error येत नाही; मात्र तुमचे results शांतपणे कमी अचूक होतात.

Index नसताना ही query प्रत्येक row वर exact search करते. हा Postgres मधील वरील brute force प्रकार आहे आणि त्याचा recall देखील perfect असतो. max_parallel_workers_per_gather वाढवल्यास अधिक cores या कामासाठी वापरले जातात. हे प्रथम करा आणि index नंतर तयार करा. त्यामुळे index ची तुलना करण्यासाठी recall baseline उपलब्ध राहतो.

Qdrant, index database पेक्षा मोठा झाल्यावर

Qdrant हे Rust मध्ये लिहिलेले स्वतंत्र vector store आहे. index इतका मोठा झाल्यावर त्याची निर्मिती तुमच्या अॅप्लिकेशनच्या Postgres सोबत समान संसाधनांसाठी स्पर्धा करू नये असे वाटत असल्यास Qdrant उपयुक्त ठरते. pgvector मध्ये केवळ half precision आणि binary quantisation यांचे दस्तऐवजीकरण आहे, तर Qdrant मध्ये payload filtering तसेच scalar किंवा product 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 उपलब्ध असतो. Port 6334 वर gRPC उपलब्ध असतो. सार्वजनिक VPS वर दोन बाबी महत्त्वाच्या आहेत. Qdrant च्या अधिकृत दस्तऐवजानुसार सेवा default स्वरूपात "encryption किंवा authentication शिवाय" चालते. तसेच quickstart मधील -p 6333:6333 प्रत्येक interface वर bind होते. Docker स्वतःचे forwarding rules लिहित असल्यामुळे ufw rule लागू असतानाही तो port बाहेरून उपलब्ध करून देतो. 127.0.0.1 वर bind करा आणि API key सेट करा. सार्वजनिक IP वर key शिवाय उपलब्ध असलेला Qdrant instance म्हणजे तुमच्या documents ची सार्वजनिक प्रतच आहे.

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 योग्य प्रकारे संरचित आहे का, हा नेहमीच्या stateful-service चाच प्रश्न आहे. त्यामुळे Docker आणि host database मधील तुलना येथेही तशीच लागू होते.

Chroma आणि त्याचा उपयोग

Chroma हे शून्यापासून कार्यरत retrieval demo तयार करण्यासाठीचा सर्वात सोपा मार्ग आहे.

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

ते port 8000 वर सेवा देते आणि chromadb.HttpClient(host="localhost", port=8000) त्याला connect होते. Chroma मध्ये default embedding function समाविष्ट असते. त्यामुळे पहिल्या prototype साठी स्वतंत्र model server ची आवश्यकता नसते.

या निवडीतील तडजोड स्पष्टपणे समजून घ्या. या मार्गदर्शकात नमूद केलेले निर्णय Chroma लपवते, त्यामुळे ते वापरणे सोपे वाटते: कोणते distance metric वापरायचे आणि परिणामासाठी किती RAM लागेल. Prototype साठी हे योग्य आहे. मात्र ज्यासाठी तुम्हाला alert मिळतो त्या production प्रणालीसाठी ते चुकीचे ठरू शकते. तुमचा data आधीच Postgres मध्ये असल्यास, तो Chroma मध्ये हलवल्यामुळे एक अतिरिक्त process आणि synchronisation ची समस्या निर्माण होते. pgvector मध्ये नसलेली समस्या सोडवण्यासाठी हे करावे लागते.

इंडेक्ससाठी किती RAM लागेल

कच्च्या vectors पासून सुरुवात करा. कारण ते किमान आवश्यक आकार ठरवतात आणि tuning केल्याने तो कमी होत नाही.

bytes = number_of_vectors * dimensions * 4

प्रत्येक dimension साठी 4 bytes म्हणजे एक 32-bit float. RAM पेक्षा मोठ्या होणाऱ्या collections साठी Qdrant च्या मार्गदर्शकात metadata, indexes आणि optimisation दरम्यान तयार होणाऱ्या तात्पुरत्या segments साठी 1.5 गुणक जोडला आहे:

memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5

ही formula प्रत्यक्ष embedding models मध्ये आढळणाऱ्या dimensions वापरून one million vectors वर लागू केल्यास पुढील परिणाम मिळतात.

तक्ता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
  }
]

हे formula चे gibibytes (GiB) मधील outputs आहेत; हे प्रत्यक्ष मोजमाप नाही. मेमरीमध्ये राखून ठेवाव्या लागणाऱ्या जागेचा आकार म्हणून त्यांचा अर्थ घ्या. nomic-embed-text सारख्या 768-dimension model ला one million chunks साठी सुमारे 4.29 GiB लागतात. त्यामुळे Postgres साठी काही जागा शिल्लक ठेवून 8 GB plan मध्ये ते बसते. त्याच corpus साठी 3072 dimensions वापरल्यास 17.17 GiB लागतात आणि ते बसत नाही.

या chart मधील शेवटचा column नव्हे, तर पहिला column महत्त्वाचा आहे. Dimension अर्धी केल्यास त्यानंतर लागणारे प्रत्येक byte कायमचे अर्धे होतात. Public leaderboard वर थोडा कमी score मिळवणारे 768-dimension model VPS वर अनेकदा अधिक योग्य engineering choice ठरते. त्यानंतर pgvector चा halfvec type 16-bit floats साठवतो, त्यामुळे bytes पुन्हा अर्धे होतात. तो expression वापरून index तयार करतो:

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

Model निवडण्यापूर्वी एक मर्यादा लक्षात घ्या. pgvector चा vector type कमाल 16,000 dimensions स्वीकारतो, परंतु त्याचे HNSW आणि IVFFlat indexes फक्त 2,000 dimensions पर्यंतच लागू होतात. halfvec cast वापरल्यास ही मर्यादा 4,000 पर्यंत वाढते. त्यापेक्षा अधिक dimensions असल्यास pgvector binary quantisation वापरण्याची पद्धत दस्तऐवजीकरणात सांगते: binary_quantize(embedding)::bit(N) expression ला bit_hamming_ops वापरून index करा. यामुळे 64,000 dimensions पर्यंत support मिळतो. त्यानंतर सुरुवातीच्या candidates मधील सर्वोच्च results मूळ vectors विरुद्ध पुन्हा rank करा.

बांधणीच्या वेळी m आणि ef_construction ची किंमत

HNSW (hierarchical navigable small world) हा pgvector आणि Qdrant दोन्ही वापरत असलेला index आहे. हा स्तरित graph आहे. प्रत्येक vector हा nearby nodes शी links असलेला node असतो. प्रत्येक vector वाचण्याऐवजी search त्या links वरून query च्या दिशेने पुढे जाते.

m म्हणजे प्रत्येक node ठेवत असलेल्या links ची संख्या. Faiss documentation नुसार HNSW साठी memory प्रति vector (d * 4 + m * 2 * 4) bytes असते आणि m हे 4 ते 64 दरम्यान ठेवण्याची शिफारस केली जाते. हे 768 dimensions आणि 1 million vectors वर चालवून पाहा.

तक्ता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
  }
]

या फरकाचा आकार महत्त्वाचा आहे. Default m = 16 वरून m = 64 पर्यंत गेल्यास links ची संख्या प्रति vector 128 bytes वरून 512 bytes होते. 3072 bytes vector data च्या तुलनेत ही अतिरिक्त 384 bytes आहेत. त्यामुळे एकूण आकार 2.98 GiB वरून 3.34 GiB होतो. हा फरक सुमारे 12 percent आहे. या dimensions मध्ये m मुळे memory चा मुख्य वापर होत नाही. Vectors मुळे होतो.

m मुळे मुख्यतः build time आणि insert time वाढतो. कारण node ठेवताना तेवढ्या neighbours शोधून त्यांच्याशी links तयार करावे लागतात. ef_construction म्हणजे प्रत्येक node ठेवताना builder विचारात घेत असलेल्या candidate list ची size. हे वाढवल्यास graph अधिक चांगला तयार होतो, पण build धीमा होतो. Finished index ची size मात्र बदलत नाही.

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);

Build काही minutes मध्ये पूर्ण होईल की hours लागतील, हे ठरवणारी setting म्हणजे maintenance_work_mem. कारण पुरेशी memory उपलब्ध असेल तर pgvector memory मध्ये graph तयार करते. तो memory मध्ये बसत नसल्यास 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 दाखवत असलेली ही सर्वात उपयुक्त line आहे. याचा अर्थ build अधिक धीम्या path वर गेला आहे. त्यामुळे तो cancel करा, वर केलेल्या RAM गणनेपेक्षा setting अधिक वाढवा आणि पुन्हा सुरू करा. दुसऱ्या session मधून progress monitor करा:

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

HNSW प्रथम initializing आणि नंतर loading tuples दाखवते. Disk व्यस्त नसताना build बराच वेळ कमी percentage वर राहिला, तर ही maintenance_work_mem समस्या आहे; query अडकलेली नाही.

नियोजन करताना दोन तथ्ये लक्षात ठेवा. pgvector च्या README नुसार HNSW मध्ये IVFFlat पेक्षा "slower build times and uses more memory" असते. त्याच्या बदल्यात speed आणि recall यांच्यातील trade-off अधिक चांगला मिळतो. तसेच HNSW रिकाम्या table वर तयार करता येतो. IVFFlat ला आधी representative data वर k-means चालवावे लागते. त्यामुळे रिकाम्या table वर IVFFlat तयार केल्यास recall कमी मिळतो. नवीन schema साठी आधीच तयार करता येणारा index HNSW आहे.

ef_search: build नंतर समायोजित करायची सेटिंग

m आणि ef_construction index मध्ये कायमस्वरूपी साठवले जातात. ef_search तसे नाही. Graph मधून शोध घेताना search किती candidates ठेवतो हे ते ठरवते. हे तुम्ही प्रत्येक session किंवा query साठी बदलू शकता.

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

Default value 40 आहे. ती वाढवल्यास recall वाढतो आणि latency देखील वाढते. ती कमी केल्यास दोन्ही कमी होतात. Rebuild न करता बदलता येणारे हेच एकमेव recall नियंत्रण आहे. त्यामुळे तुम्हाला योग्य उत्तरे आधीच माहीत असलेल्या queries च्या निश्चित संचावर ते समायोजित करा. Recall मध्ये सुधारणा होणे थांबेपर्यंतच ते वाढवा.

एक अडचण आहे. Selective WHERE clause सोबत ef_search नीट कार्य करत नाही. कारण index निश्चित संख्येने candidates परत करतो आणि त्यानंतर filter लागू केला जातो. बहुतेक rows नाकारणारा filter असल्यास, table मध्ये जुळणाऱ्या rows असूनही तुम्हाला LIMIT पेक्षा कमी results मिळू शकतात. pgvector 0.8 यासाठी iterative scans देते:

SET hnsw.iterative_scan = relaxed_order;

त्यानंतर limit पूर्ण होईपर्यंत index अधिक candidates साठी पुन्हा scan केला जातो. ही प्रक्रिया hnsw.max_scan_tuples पर्यंत चालते. त्याची default value 20000 आहे. strict_order अचूक distance ordering कायम ठेवते, परंतु त्यासाठी अधिक खर्च येतो. Ubuntu 0.6.0 package मध्ये हे feature उपलब्ध नाही. Filter वापरताना काही matching rows गायब झाल्या, तर हेच त्याचे लक्षण आहे.

RAM मध्ये index का बसणे आवश्यक आहे

HNSW search ही graph वरून केली जाणारी वाटचाल आहे. प्रत्येक hop मागील node शी संबंधित नसलेल्या ठिकाणी साठवलेल्या node ला वाचतो. त्यामुळे access pattern जवळपास random असतो आणि read-ahead उपयोगी पडत नाही. Graph RAM मध्ये असताना प्रत्येक hop हा memory reference असतो. Graph RAM मध्ये नसेल, तर hop मुळे disk read होऊ शकतो. अशा वेळी काहीशे nodes वाचणाऱ्या search मुळे काहीशे reads होऊ शकतात.

Qdrant च्या documentation मध्ये याचे स्पष्ट प्रमाण दिले आहे: "तुम्ही RAM मध्ये निम्मे vectors साठवले, तर search latency साधारणपणे दुप्पट होईल." या विधानाचा आधार घेऊन नियोजन करा.

Index प्रत्यक्षात RAM मध्ये बसणार नसेल, तर प्रत्येक पर्याय जाणीवपूर्वक स्वीकारावयाचा trade-off आहे.

  • Vectors memory-map करा, जेणेकरून operating system वारंवार वापरली जाणारी pages cache करेल आणि कमी वापरली जाणारी pages disk वर ठेवेल. हे सहन करण्याजोगे कार्यप्रदर्शन मिळवण्यासाठी खाली fast NVMe (non-volatile memory express) storage आवश्यक आहे.
  • Quantise करा आणि प्रत्येक dimension चार bytes ऐवजी one byte म्हणून साठवा. त्यामुळे vector bytes चारपट कमी होतात; त्यासाठी recall मध्ये कमी, पण मोजता येण्याजोगी घट स्वीकारावी लागते.
  • pgvector मध्ये halfvec मध्ये cast करा. यामुळे one-byte quantisation पेक्षा recall loss कमी राहतो आणि bytes निम्मे होतात.
  • Smaller model वापरून embed करा. हा सर्वात स्वस्त उपाय आहे. मात्र यासाठी corpus पुन्हा embed करावा लागतो, म्हणून लोक हा उपाय टाळतात.

अपयशाच्या स्थिती आणि दिसणारे संदेश

could not open extension control file. चालू असलेल्या Postgres major version साठी pgvector package स्थापित केलेले नाही. sudo -u postgres psql -tAc 'SHOW server_version' वापरून version दाखवा आणि त्याच्याशी जुळणारे postgresql-NN-pgvector स्थापित करा.

ERROR: expected 768 dimensions, not 1536. column type आणि model यांच्यात विसंगती आहे. तुम्ही embedding models बदलले, पण embeddings पुन्हा तयार केले नाहीत. येथे अंशतः दुरुस्ती शक्य नाही, कारण दोन वेगवेगळ्या models मधील vectors एकमेकांशी अजिबात तुलनायोग्य नसतात. त्यामुळे प्रत्येक row पुन्हा generate करावी लागेल.

query धीमी आहे आणि EXPLAIN मध्ये sequential scan दिसतो. index operator class आणि query operator जुळत नाहीत. vector_cosine_ops फक्त <=> साठी काम करते. query वर EXPLAIN ANALYZE चालवा आणि Index Scan using ... on chunks शोधा. त्याऐवजी Seq Scan on chunks दिसल्यास, तुम्ही प्रत्यक्षात वापरत असलेल्या operator शी जुळणारी operator class वापरून index पुन्हा तयार करा.

LIMIT पेक्षा कमी rows मिळतात आणि WHERE clause उपस्थित आहे. ही वर सांगितलेली filtering trap आहे. hnsw.ef_search वाढवा किंवा pgvector 0.8 वर जा आणि hnsw.iterative_scan सेट करा.

index build पूर्ण होताना process बंद होतो आणि psql मध्ये कोणतीही error दिसत नाही. maintenance_work_mem मशीनच्या बहुतांश memory वर सेट केलेले असताना shared_buffers आणि तुमच्या application लाही memory हवी असल्यास, शेवटी kernel out-of-memory killer process बंद करतो. sudo dmesg -T | grep -i 'killed process' मध्ये postgres चे नाव असलेली line दिसते. ही setting कमी करा किंवा मोठ्या plan वर index तयार करा आणि dump restore करा.

निवड

तुम्ही आधीपासून Postgres चालवत असाल आणि तुमच्याकडे काही दशलक्षांपेक्षा कमी vectors असतील, तर pgvector वापरा. Index डेटासोबतच राहतो, filtering ही WHERE clause द्वारे होते आणि तुमच्या विद्यमान backup मध्ये त्याचा आधीच समावेश होतो. Index इतका मोठा असेल की त्याच्यासाठी स्वतंत्र memory ceiling आवश्यक वाटेल, किंवा तुम्हाला मोठ्या प्रमाणावर payload filtering हवी असेल, तर त्याच्या शेजारी Qdrant चालवा आणि व्यवस्थापित करण्यासाठी दुसरी service स्वीकारा.

साधारणपणे एक लाख vectors पेक्षा कमी असल्यास, काहीही install करण्यापूर्वी brute-force scan चे मोजमाप करा. त्या आकारासाठी perfect recall असलेली आणि build step न लागणारी exhaustive search हा तडजोडीचा पर्याय नाही. तोच योग्य पर्याय आहे. त्याऐवजी approximate index वापरल्यास, तुम्ही खर्च न करत असलेल्या milliseconds च्या बदल्यात tuning आणि RAM pressure स्वीकारता.

FAQ

मला स्वतंत्र vector database आवश्यक आहे का, की Postgres पुरेसे आहे?

तुमचा डेटा आधीपासून Postgres मध्ये असेल, तर बहुतेक तुलना सुचवतात त्यापेक्षा बराच काळ pgvector पुरेसे ठरते. ते vectors सामान्य column मध्ये साठवते. त्यामुळे filtered search ही WHERE clause असते आणि तुमच्या विद्यमान backup मध्ये index चाही समावेश होतो. Vector workload साठी स्वतंत्र memory ceiling आवश्यक असेल किंवा payload filtering आणि pgvector मध्ये उपलब्ध नसलेले scalar किंवा product quantisation आवश्यक असेल, तेव्हा Qdrant सारख्या स्वतंत्र store कडे जा.

एका VPS मध्ये किती vectors ठेवता येतात?

अंदाज न लावता number_of_vectors * dimensions * 4 bytes * 1.5 वापरून गणना करा. 768 dimensions असलेले 1 million vectors साधारण 4.3 GiB जागा घेतात. त्यामुळे 8 GB plan मध्ये Postgres साठी जागा राखूनही ते ठेवता येतात. 3072 dimensions असलेले 1 million vectors साधारण 17 GiB जागा घेतात आणि त्यासाठी बरीच मोठी plan आवश्यक असते. या गणनेवर सर्वाधिक परिणाम करणारी संख्या म्हणजे तुमच्या embedding model ची dimension. त्यामुळे memory bill लक्षात घेऊन model निवडा.

एकाच box वर network ही समस्या नसते. त्यामुळे परिणाम करणाऱ्या दोन गोष्टी तपासा. प्रथम, embedding call स्वतंत्रपणे किती वेळ घेतो ते मोजा. CPU वर query vector तयार करण्यास search पेक्षा बराच अधिक वेळ लागू शकतो. दुसरे, index RAM मध्ये आहे का ते तपासा. HNSW search graph मध्ये random पद्धतीने पुढे जातो. त्यामुळे graph disk वर गेल्यावर प्रत्येक hop मुळे disk read होऊ शकतो. Qdrant च्या मार्गदर्शनानुसार RAM मध्ये ठेवलेल्या vectors ची संख्या निम्मी केल्यास search latency साधारण दुप्पट होते.

HNSW index तयार करावाच का?

साधारण 100,000 vectors पेक्षा कमी असतील, तर नाही. Exhaustive scan प्रत्येक query साठी n * d * 4 bytes वाचतो. 768 dimensions असलेल्या 100,000 vectors साठी हे 307 MB होते. आधुनिक CPU perfect recall आणि कोणतीही build step नसताना हे काही दहा milliseconds मध्ये stream करू शकतो. प्रथम तुमच्या hardware वर scan मोजा. Benchmark लेखात तसे सांगितले आहे म्हणून नव्हे, तर मोजलेला scan time खरोखरच धीमा असल्यावर index तयार करा.

m वाढवल्याचा प्रत्यक्ष खर्च काय असतो?

Memory पेक्षा build time आणि insert time वर त्याचा अधिक परिणाम होतो. 768 dimensions मध्ये default m = 16 वरून m = 64 पर्यंत गेल्यास graph links प्रति vector 128 bytes वरून 512 bytes होतात. 3072 bytes vector data च्या तुलनेत ही अतिरिक्त 384 bytes वाढ आहे. त्यामुळे एकूण memory सुमारे 12 percent ने वाढते. मात्र प्रत्येक insert वेळी चारपट अधिक neighbours शोधून त्यांच्याशी link करावे लागते. प्रथम ef_search समायोजित करा. ते बदलण्यासाठी कोणताही खर्च येत नाही आणि rebuild आवश्यक नसतो.

हा लेख उपयुक्त होता का?

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