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 चा संपूर्ण प्रवास पाहूया.
- Embedding model query text चे vector मध्ये रूपांतर करते. CPU वर लहान string साठी याला दहा ते काहीशे मिलीसेकंद लागतात. GPU वर हा वेळ single-digit मिलीसेकंद असतो.
- Vector store कडे पाठवला जातो. Loopback TCP किंवा Unix domain socket वर यासाठी मिलीसेकंदाच्या अंशाइतका वेळ लागतो.
- Store आपला index तपासतो आणि सर्वात जवळच्या rows परत करतो.
- तुमचा 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;'August 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-pgvector17 ऐवजी तुमच्या server ची major version द्या. ती version sudo -u postgres psql -tAc 'SHOW server_version' दाखवते. चुकीच्या major version साठी तयार केलेले 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 directorySchema सामान्य 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 करते. हे वरील brute force ची Postgres आवृत्ती आहे आणि तिचा recall देखील तितकाच परिपूर्ण असतो. max_parallel_workers_per_gather वाढवल्यास त्यावर अधिक cores वापरले जातात. हे प्रथम करा आणि index नंतर तयार करा. त्यामुळे index विरुद्ध मोजण्यासाठी तुमच्याकडे recall baseline उपलब्ध असेल.
डेटाबेसपेक्षा index मोठा झाल्यावर Qdrant
Qdrant हा Rust मध्ये लिहिलेला dedicated vector store आहे. तुमचा index इतका मोठा असेल की त्याची build प्रक्रिया अॅप्लिकेशनच्या Postgres संसाधनांशी स्पर्धा करू नये असे तुम्हाला वाटत असेल, किंवा pgvector मध्ये नसलेले payload filtering आणि quantisation हवे असेल, तर Qdrant योग्य ठरतो.
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/qdrantPort 6333 REST (representational state transfer) API आणि /dashboard वरील dashboard साठी वापरला जातो. Port 6334 gRPC साठी वापरला जातो. सार्वजनिक VPS वर दोन बाबी महत्त्वाच्या आहेत. Qdrant च्या अधिकृत documentation नुसार सेवा default स्वरूपात “encryption किंवा authentication शिवाय” चालते. तसेच quickstart मधील -p 6333:6333 प्रत्येक interface वर bind होते. Docker स्वतःचे forwarding rules लिहित असल्यामुळे ते ufw rule च्या पलीकडे port प्रकाशित करते. 127.0.0.1 वर bind करा आणि API key सेट करा. सार्वजनिक IP वर key शिवाय reachable असलेला 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 चालू नाही किंवा तो अन्य ठिकाणी bound आहे. तुमच्या box साठी तो container योग्य प्रकारे configured आहे का, हा नेहमीचा 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 साठी एक 32-bit float असल्यामुळे 4 bytes लागतात. Qdrant च्या capacity planning documentation मध्ये metadata आणि optimisation दरम्यान तयार होणारे तात्पुरते segments यांच्यासाठी 1.5 multiplier जोडला आहे:
memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5प्रत्यक्ष embedding models तयार करतात त्या dimensions वर, ही formula one million vectors साठी लागू केल्यावर पुढील परिणाम मिळतात.
The data behind this chart
[
{
"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) मधील output आहे; हे प्रत्यक्ष मोजमाप नाही. तुमच्या memory मध्ये राखून ठेवावी लागणारी जागा म्हणून त्याकडे पाहा. 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 पर्यंतच काम करतात. त्यापेक्षा जास्त dimensions असल्यास halfvec cast चे index तयार करा; त्याची मर्यादा 4,000 dimensions आहे. अन्यथा index तयार करू नका.
बांधणीच्या वेळी m आणि ef_construction ची किंमत
HNSW (hierarchical navigable small world) हा pgvector आणि Qdrant दोन्ही वापरणारा index आहे. ही layered graph रचना आहे. प्रत्येक vector हा links असलेला node असतो. हे links जवळच्या nodes कडे असतात. Search करताना सर्व data वाचण्याऐवजी query च्या दिशेने या links वरून पुढे जाता येते.
m म्हणजे प्रत्येक node ठेवत असलेल्या links ची संख्या. Faiss documentation नुसार HNSW साठी memory वापर (d * 4 + m * 2 * 4) bytes per vector इतका असतो. त्यात m हे मूल्य 4 ते 64 दरम्यान ठेवण्याची शिफारस केली आहे. हे 768 dimensions आणि एक million vectors साठी मोजा.
The data behind this chart
[
{
"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 पर्यंत गेल्यास प्रत्येक vector साठी links करिता 512 bytes अधिक लागतात. Vector data साठी 3072 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 लागतील हे maintenance_work_mem ठरवते. pgvector graph memory मध्ये तयार करते, जोपर्यंत तो memory मध्ये बसतो. तो 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 दाखवत असलेली ही notice सर्वाधिक उपयुक्त 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 reports करते. Disk व्यस्त नसताना build बराच वेळ कमी percentage वर राहिल्यास ही maintenance_work_mem समस्या असते; query अडकलेली नसते.
नियोजन करताना दोन facts लक्षात ठेवा. pgvector च्या README नुसार HNSW ला IVFFlat पेक्षा "has slower build times and uses more memory" लागते. त्याच्या बदल्यात speed आणि recall यांच्यातील trade-off अधिक चांगला मिळतो. तसेच HNSW empty table वर तयार करता येतो. IVFFlat ला आधी representative data वर k-means चालवावे लागते. त्यामुळे empty table वर IVFFlat तयार केल्यास recall कमी मिळतो. Fresh 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 पुरेसे आहे?
तुमचा data आधीच Postgres मध्ये असेल, तर बहुतेक तुलना सुचवतात त्यापेक्षा बराच काळ pgvector पुरेसे ठरते. ते vectors सामान्य column मध्ये साठवते. त्यामुळे filtered search हा WHERE clause असतो आणि तुमच्या विद्यमान backup मध्ये index चा समावेश होतो. Vector workload साठी स्वतंत्र memory ceiling आवश्यक असेल, किंवा pgvector मध्ये उपलब्ध नसलेले payload filtering आणि quantisation आवश्यक असेल, तेव्हा Qdrant सारख्या dedicated 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 निवडा.
Index त्याच machine वर असताना माझा vector search धीमा का आहे?
एकाच machine वर network ही समस्या नसते. त्यामुळे परिणाम करणाऱ्या दोन गोष्टी तपासा. प्रथम, embedding call स्वतंत्रपणे time करा. 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 article मध्ये तसे सांगितले आहे म्हणून नव्हे, तर मोजलेला scan time खरोखरच जास्त असल्यास index तयार करा.
m वाढवल्याचा प्रत्यक्ष खर्च काय असतो?
Memory पेक्षा build time आणि insert time वर त्याचा अधिक परिणाम होतो. 768 dimensions मध्ये default m = 16 वरून m = 64 पर्यंत वाढ केल्यास प्रत्येक vector साठी graph links चे 512 bytes वाढतात. Vector data साठी 3072 bytes असतात. त्यामुळे एकूण memory सुमारे 12 percent ने वाढते. मात्र प्रत्येक insert वेळी चारपट neighbours शोधून त्यांच्याशी link करावे लागतात. प्रथम ef_search tune करा. ते बदलण्यासाठी काही खर्च होत नाही आणि rebuild आवश्यक नसतो.