VPS पर vector database कैसे चलाएं: pgvector vs Qdrant
अपने VPS पर pgvector, Qdrant या Chroma चलाने का सही तरीका जानें। index के लिए आवश्यक RAM की गणना करें और latency कम करने के लिए loopback socket का उपयोग करना सीखें।
VPS पर vector database चलाने की वास्तविक लागत
VPS (virtual private server) पर vector database चलाने से वह समस्या समाप्त हो जाती है जिसका समाधान बेचकर managed vendors मुनाफा कमाते हैं। आपका application और index एक ही machine पर स्थित होते हैं, इसलिए search request network के बजाय loopback socket से होकर गुजरती है। जो लागत शेष रहती है, वह हमेशा से ही वास्तविक रही है: text को vectors में बदलना। इसके पीछे दो और लागतें होती हैं: index बनाने में लगने वाला समय और वह RAM जो index के सक्रिय रहने तक उपयोग होती है।
यह उन निर्णयों को बदल देता है जो महत्वपूर्ण हैं। Region और endpoint round trip अब आपकी चिंता का विषय नहीं रहते। Vector count, dimensions और चार bytes का गुणनफल आपकी चिंता बन जाता है, क्योंकि यही तय करता है कि index आपके द्वारा हर महीने किराए पर ली गई memory में फिट होगा या नहीं।
एक ही सर्वर पर मिलीसेकंड्स वास्तव में कहाँ खर्च होते हैं
एक self-hosted stack में similarity query की प्रक्रिया को समझें।
- Query text को एक embedding model द्वारा vector में बदला जाता है। CPU पर एक छोटी string के लिए इसमें दस से सैकड़ों मिलीसेकंड्स लगते हैं। GPU पर यह समय सिंगल डिजिट में होता है।
- Vector को store में भेजा जाता है। Loopback TCP या Unix domain socket के माध्यम से इसमें एक मिलीसेकंड का बहुत छोटा हिस्सा लगता है।
- Store अपने index को स्कैन करता है और निकटतम rows को लौटाता है।
- आपका कोड matching text को पढ़ता है और एक prompt तैयार करता है।
इस सूची में Step 1 आमतौर पर सबसे अधिक समय लेता है। Step 2 वह चरण है जिस पर hosted vendors प्रतिस्पर्धा करते हैं, लेकिन एक ही बॉक्स पर इसका प्रभाव न के बराबर होता है। अनुमान न लगाएँ। अपने सर्वर पर दोनों सिरों का समय मापें।
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 में चलाएँ। यदि पहला कमांड embed: 0.184312s प्रिंट करता है और psql का उत्तर Time: 4.201 ms है, तो index को ट्यून करना गलत काम है: आपकी latency embedding model के कारण है। Ollama के साथ स्थानीय रूप से embedding model चलाना Step 1 को Step 2 और 3 वाले CPU पर ही रखता है, इसलिए दोनों हिस्से समान cores और समान RAM के लिए प्रतिस्पर्धा करते हैं। इस store के इर्द-गिर्द बना ingest और retrieval loop self-hosted RAG pipeline गाइड में कवर किया गया है। RAG का अर्थ है retrieval augmented generation: आप अपने स्वयं के दस्तावेज़ों को खोजते हैं और सबसे अच्छे matches को एक prompt में पेस्ट करते हैं।
एक लाख वेक्टर्स से कम होने पर, उन सभी को स्कैन करें
एक exhaustive scan क्वेरी की तुलना हर स्टोर किए गए वेक्टर से करता है। परिभाषा के अनुसार इसमें रिकॉल (recall) सटीक होता है। इसके लिए किसी इंडेक्स या बिल्ड स्टेप की आवश्यकता नहीं होती है, और यह आपके डेटा के पीछे पुराना (out of date) नहीं हो सकता।
गणित आपको बताता है कि यह कब तक प्रभावी है। एक स्कैन प्रति क्वेरी n * d * 4 बाइट्स पढ़ता है, जहाँ n वेक्टर की संख्या है और d आयाम (dimension) है। 768 आयामों वाले 100,000 वेक्टर्स पर यह प्रति क्वेरी 307 MB होता है, जिसे एक आधुनिक CPU कुछ ही मिलीसेकंड में स्ट्रीम कर लेता है। 5 मिलियन वेक्टर्स पर यह प्रति क्वेरी 15 GB हो जाता है, जो अब एक क्वेरी नहीं रह जाती।
इसलिए वेक्टर्स को 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]]दोनों पक्षों को यूनिट लेंथ पर स्केल किया जाता है, इसलिए डॉट प्रोडक्ट ही cosine similarity है और उच्च स्कोर का अर्थ है कि वे अधिक मिलते-जुलते हैं। mat को हर क्वेरी के बजाय स्टार्टअप पर एक बार लोड करें, इससे SQLite रीड पूरी तरह से हॉट पाथ (hot path) से हट जाता है।
इसे अस्वीकार करने से पहले अपने सिस्टम पर इसका समय मापें।
import time
t = time.perf_counter()
search(q)
print(f"{(time.perf_counter() - t) * 1000:.1f} ms")ईमानदार सीमाएं: यह एक ऐसी प्रक्रिया है जो पूरे मैट्रिक्स को RAM में रखती है, और यह आपको कोई मेटाडेटा फिल्टरिंग या समवर्ती लेखक (concurrent writer) की सुविधा नहीं देती है। जब इनमें से कोई भी कारण आपकी असंतुष्टि का कारण बने, तो आगे बढ़ें। SQLite स्वयं एक गंभीर सर्वर-साइड स्टोर है, जिसे the SQLite in production guide में विस्तार से समझाया गया है, और यदि आपका वास्तविक वर्कलोड पंक्तियों (rows) को सर्व करने के बजाय कॉलम को स्कैन करना है, तो the DuckDB and SQLite comparison पढ़ना अधिक उपयोगी है।
pgvector, जब आप पहले से ही Postgres चला रहे हों
यदि आपके application में पहले से ही Postgres database मौजूद है, तो pgvector सबसे कम नया surface area जोड़ता है। यह एक extension है, कोई अलग service नहीं। Vectors उस row के साथ एक सामान्य table में रहते हैं जिसका वे वर्णन करते हैं, इसलिए filtered search एक WHERE clause है, न कि कोई दूसरी system जिसे sync में रखना पड़े।
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-pgvector17 को अपने server के major version से बदलें, जिसे sudo -u postgres psql -tAc 'SHOW server_version' print करता है। यदि आप गलत 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 version है और इसमें वही perfect recall मिलता है। max_parallel_workers_per_gather को बढ़ाने से उस पर अधिक cores का उपयोग होता है। इसे पहले करें और index बाद में लगाएँ, क्योंकि अब आपके पास index को मापने के लिए एक recall baseline है।
Qdrant, जब इंडेक्स डेटाबेस से बड़ा हो जाए
Qdrant एक समर्पित वेक्टर स्टोर है जिसे Rust में लिखा गया है। यह तब उपयोगी होता है जब इंडेक्स इतना बड़ा हो जाए कि आप नहीं चाहेंगे कि इसका निर्माण आपके एप्लिकेशन के Postgres के साथ संसाधनों के लिए प्रतिस्पर्धा करे, या जब आपको ऐसे पेलोड फ़िल्टरिंग और क्वांटाइज़ेशन की आवश्यकता हो जो pgvector प्रदान नहीं करता है।
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 पर एक डैशबोर्ड प्रदान करता है, और 6334 gRPC के लिए है। एक सार्वजनिक VPS पर दो विवरण महत्वपूर्ण हैं। Qdrant का अपना दस्तावेज़ीकरण कहता है कि सेवा डिफ़ॉल्ट रूप से "बिना किसी एन्क्रिप्शन या प्रमाणीकरण के" चलती है, और क्विकस्टार्ट से प्राप्त -p 6333:6333 हर इंटरफ़ेस से बाइंड होता है। Docker इसे ufw नियम के बावजूद पब्लिश कर देता है क्योंकि यह अपने स्वयं के फॉरवर्डिंग नियम लिखता है। इसे 127.0.0.1 पर बाइंड करें और एक API key सेट करें। बिना किसी key के सार्वजनिक IP पर उपलब्ध Qdrant इंस्टेंस आपके दस्तावेज़ों की एक सार्वजनिक प्रतिलिपि के समान है।
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"}} प्राप्त होने का अर्थ है कि हेडर का नाम या key गलत है, और कुछ भी न मिलने का अर्थ है कि कंटेनर चल नहीं रहा है या कहीं और बाइंड है। क्या वह कंटेनर आपके बॉक्स के लिए सही है, यह एक सामान्य स्टेटफुल-सर्विस प्रश्न है, इसलिए Docker बनाम होस्ट डेटाबेस तुलना यहाँ अपरिवर्तित रूप से लागू होती है।
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 इसलिए सुविधाजनक है क्योंकि यह उन निर्णयों को छिपा देता है जिनके बारे में यह guide है: कौन सा distance metric उपयोग करें, और परिणाम के लिए कितनी RAM की आवश्यकता होगी। यह एक prototype के लिए सही है, लेकिन उस सिस्टम के लिए गलत है जिसके लिए आपको रात में alert (page) किया जाए। यदि आपका data पहले से ही Postgres में है, तो उसे Chroma में ले जाने से एक अतिरिक्त process और synchronization की समस्या पैदा होती है, जबकि pgvector में ऐसी कोई समस्या नहीं होती।
इंडेक्स को कितनी RAM की आवश्यकता होगी
कच्चे वेक्टर्स (raw vectors) से शुरुआत करें, क्योंकि ये न्यूनतम आवश्यकता हैं और किसी भी ट्यूनिंग से इन्हें कम नहीं किया जा सकता।
bytes = number_of_vectors * dimensions * 4प्रति डाइमेंशन एक 32-बिट फ्लोट के लिए चार बाइट्स की आवश्यकता होती है। Qdrant का क्षमता नियोजन (capacity planning) दस्तावेज़ मेटाडेटा और ऑप्टिमाइज़ेशन के दौरान बनने वाले अस्थायी सेगमेंट्स के लिए 1.5 का मल्टीप्लायर जोड़ने का सुझाव देता है:
memory_size = number_of_vectors * vector_dimension * 4 bytes * 1.5यहाँ दस लाख वेक्टर्स पर लागू किया गया वह फॉर्मूला है, उन डाइमेंशन्स के साथ जो वास्तविक एम्बेडिंग मॉडल्स उत्पन्न करते हैं।
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
}
]ये परिणाम गिबिबाइट्स (GiB) में फॉर्मूला का आउटपुट हैं, न कि कोई माप। इन्हें उस मेमोरी स्पेस के आकार के रूप में समझें जिसे आपको खाली रखना होगा। 768-डाइमेंशन वाला मॉडल जैसे कि nomic-embed-text, दस लाख चंक्स पर लगभग 4.29 GiB की मांग करता है, जो 8 GB के प्लान में Postgres के लिए जगह छोड़ते हुए फिट हो जाता है। वही कॉर्पस 3072 डाइमेंशन्स पर 17.17 GiB की मांग करता है और इसमें फिट नहीं होता।
नियंत्रण का मुख्य बिंदु उस चार्ट का पहला कॉलम है, न कि अंतिम। डाइमेंशन को आधा करने से उसके बाद के सभी बाइट्स हमेशा के लिए आधे हो जाते हैं। 768-डाइमेंशन वाला मॉडल, जो किसी पब्लिक लीडरबोर्ड पर थोड़ा कम स्कोर करता है, अक्सर VPS पर इंजीनियरिंग के लिए बेहतर विकल्प होता है। pgvector का halfvec टाइप 16-बिट फ्लोट्स को स्टोर करता है, जो बाइट्स को फिर से आधा कर देता है, और यह एक एक्सप्रेशन के माध्यम से इंडेक्सिंग करता है:
CREATE INDEX ON chunks USING hnsw ((embedding::halfvec(768)) halfvec_cosine_ops);मॉडल चुनने से पहले एक सीमा जिसे जानना आवश्यक है। pgvector का vector टाइप 16,000 डाइमेंशन्स तक स्वीकार करता है, लेकिन इसके HNSW और IVFFlat इंडेक्स केवल 2,000 तक ही कवर करते हैं। इससे ऊपर आप halfvec कास्ट को इंडेक्स करते हैं, जो 4,000 तक पहुँचता है, या फिर आप इंडेक्सिंग नहीं करते हैं।
build time पर m और ef_construction की लागत क्या है
HNSW (hierarchical navigable small world) वह इंडेक्स है जिसका उपयोग pgvector और Qdrant दोनों करते हैं। यह एक लेयर्ड ग्राफ है। प्रत्येक वेक्टर एक नोड है जिसमें नजदीकी नोड्स के लिंक होते हैं, और सर्च सब कुछ पढ़ने के बजाय उन लिंक्स के सहारे क्वेरी की ओर बढ़ती है।
m यह निर्धारित करता है कि प्रत्येक नोड कितने लिंक रखता है। Faiss डॉक्यूमेंटेशन के अनुसार HNSW मेमोरी (d * 4 + m * 2 * 4) बाइट्स प्रति वेक्टर होती है और यह m को 4 और 64 के बीच रखने की सलाह देता है। इसे दस लाख वेक्टर्स पर 768 डायमेंशन्स के साथ चलाएं।
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
}
]उपयोगी आश्चर्य उस अंतर का आकार है। डिफ़ॉल्ट m = 16 से m = 64 पर जाने से 3072 बाइट्स वेक्टर डेटा के मुकाबले प्रति वेक्टर 512 बाइट्स लिंक जुड़ जाते हैं, जिससे कुल आकार 2.98 GiB से बढ़कर 3.34 GiB हो जाता है। यह लगभग 12 प्रतिशत है। इन डायमेंशन्स पर m वह जगह नहीं है जहाँ आपकी मेमोरी खर्च होती है। मेमोरी वेक्टर्स में खर्च होती है।
m की वास्तविक लागत बिल्ड टाइम और इंसर्ट टाइम है, क्योंकि एक नोड को रखने का अर्थ है उतने ही पड़ोसियों को खोजना और लिंक करना। ef_construction उस कैंडिडेट लिस्ट का आकार है जिसे बिल्डर प्रत्येक नोड को रखते समय ध्यान में रखता है। इसे बढ़ाने से एक बेहतर ग्राफ बनता है और बिल्ड धीमा हो जाता है, और यह तैयार इंडेक्स के आकार को बिल्कुल नहीं बदलता है।
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 वह सेटिंग है जो यह तय करती है कि बिल्ड में मिनट लगेंगे या घंटे, क्योंकि 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 द्वारा प्रिंट की गई सबसे उपयोगी लाइन है। इसका मतलब है कि बिल्ड बहुत धीमे पाथ पर चला गया है, इसलिए इसे रद्द करें, सेटिंग को ऊपर गणना की गई RAM फिगर से अधिक बढ़ाएं, और फिर से शुरू करें। दूसरे सेशन से प्रोग्रेस को मॉनिटर करें:
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS pct
FROM pg_stat_progress_create_index;HNSW initializing और फिर loading tuples रिपोर्ट करता है। यदि बिल्ड किसी ऐसी डिस्क पर लंबे समय तक कम प्रतिशत पर अटका रहता है जो व्यस्त नहीं है, तो यह maintenance_work_mem की समस्या है, न कि कोई स्टक क्वेरी।
योजना बनाते समय दो तथ्यों का ध्यान रखें। pgvector का README कहता है कि HNSW में IVFFlat की तुलना में "बिल्ड टाइम धीमा होता है और अधिक मेमोरी का उपयोग होता है", और इसके बदले में यह बेहतर स्पीड बनाम रिकॉल ट्रेड प्रदान करता है। और HNSW को एक खाली टेबल पर बनाया जा सकता है, जबकि IVFFlat को पहले प्रतिनिधि डेटा पर k-means चलाना पड़ता है, इसलिए इसे खाली टेबल पर बनाने से रिकॉल खराब मिलता है। एक फ्रेश स्कीमा पर HNSW वह इंडेक्स है जिसे आप पहले ही बना सकते हैं।
ef_search: वह knob जिसे आप build के बाद tune करते हैं
m और ef_construction index में स्थिर (frozen) रहते हैं। ef_search ऐसा नहीं है। यह निर्धारित करता है कि graph को traverse करते समय search कितने candidates को बनाए रखती है, और आप इसे प्रति session या प्रति query बदल सकते हैं।
SET hnsw.ef_search = 100;
SELECT id, body FROM chunks ORDER BY embedding <=> $1 LIMIT 5;Default मान 40 है। इसे बढ़ाने पर recall बढ़ता है, और साथ ही latency भी बढ़ती है। इसे कम करने पर दोनों कम हो जाते हैं। यह एकमात्र recall control है जिसे आप rebuild किए बिना बदल सकते हैं, इसलिए इसे उन queries के एक निश्चित सेट के विरुद्ध tune करें जिनके सही उत्तर आप पहले से जानते हैं, और तब रुकें जब recall में सुधार होना बंद हो जाए।
एक समस्या है। ef_search का WHERE clause के साथ तालमेल खराब रहता है, क्योंकि index एक निश्चित संख्या में candidates लौटाता है और filter उसके बाद लागू होता है। ऐसा filter जो अधिकांश rows को reject कर देता है, वह आपको LIMIT से कम परिणाम दे सकता है, जबकि table में matching rows मौजूद होती हैं। pgvector 0.8 इसका समाधान iterative scans के साथ देता है:
SET hnsw.iterative_scan = relaxed_order;इसके बाद index को अधिक candidates के लिए फिर से scan किया जाता है जब तक कि limit पूरी न हो जाए, जो कि hnsw.max_scan_tuples तक हो सकती है, जिसका default मान 20000 है। strict_order सटीक distance ordering बनाए रखता है और इसमें अधिक लागत आती है। यह वह feature है जो Ubuntu 0.6.0 package में नहीं है, और filter के अंतर्गत rows का गायब होना ही वह तरीका है जिससे आपको इसका पता चलता है।
इंडेक्स का RAM में फिट होना क्यों आवश्यक है
HNSW सर्च एक ग्राफ पर चलने के समान है। प्रत्येक हॉप (hop) एक ऐसे नोड को पढ़ता है जो पिछले नोड से पूरी तरह अलग स्थान पर स्थित होता है, इसलिए एक्सेस पैटर्न रैंडम (random) होता है और read-ahead इसमें कोई मदद नहीं करता। जब तक ग्राफ RAM में रहता है, तब तक प्रत्येक हॉप एक मेमोरी रेफरेंस होता है। एक बार जब यह RAM से बाहर हो जाता है, तो एक हॉप डिस्क रीड (disk read) बन सकता है, और एक ऐसी सर्च जो कुछ सौ नोड्स को छूती है, वह कुछ सौ डिस्क रीड में बदल जाती है।
Qdrant का डॉक्यूमेंटेशन इसे स्पष्ट करता है: "यदि आप RAM में आधे वेक्टर स्टोर करते हैं, तो सर्च लेटेंसी लगभग दोगुनी हो जाएगी।" इस वाक्य को ध्यान में रखकर योजना बनाएं।
जब इंडेक्स वास्तव में फिट न हो, तो हर विकल्प एक ऐसा समझौता होना चाहिए जिसे आप जानबूझकर करें।
- वेक्टर्स को memory-map करें ताकि ऑपरेटिंग सिस्टम हॉट पेजों को कैश (cache) कर सके और कोल्ड पेजों को डिस्क पर छोड़ दे। इसे सहन करने योग्य बनाने के लिए नीचे तेज NVMe (non-volatile memory express) स्टोरेज की आवश्यकता होती है।
- क्वांटाइज (Quantise) करें, प्रत्येक डाइमेंशन को चार बाइट्स के बजाय एक बाइट के रूप में स्टोर करें। यह एक छोटी, मापने योग्य रिकॉल (recall) लागत पर वेक्टर बाइट्स को चार गुना कम कर देता है।
- pgvector में
halfvecमें कास्ट करें, जो एक-बाइट क्वांटाइजेशन की तुलना में कम रिकॉल नुकसान के साथ बाइट्स को आधा कर देता है। - छोटे मॉडल के साथ एम्बेड करें। यह सबसे सस्ता समाधान है और जिसे लोग अक्सर छोड़ देते हैं, क्योंकि इसका मतलब पूरे कॉर्पस (corpus) को फिर से एम्बेड करना होता है।
विफलता के प्रकार और आपको दिखाई देने वाली स्ट्रिंग्स
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. कॉलम टाइप और मॉडल मेल नहीं खाते हैं। आपने एम्बेडिंग मॉडल बदल दिया है और डेटा को फिर से एम्बेड नहीं किया है। यहाँ कोई आंशिक सुधार संभव नहीं है, क्योंकि दो अलग-अलग मॉडलों के वेक्टर्स की तुलना नहीं की जा सकती, इसलिए प्रत्येक पंक्ति को फिर से जनरेट करना होगा।
क्वेरी धीमी है और EXPLAIN एक sequential scan दिखाता है। इंडेक्स ऑपरेटर क्लास और क्वेरी ऑपरेटर मेल नहीं खाते हैं। vector_cosine_ops केवल <=> को सपोर्ट करता है। क्वेरी पर EXPLAIN ANALYZE चलाएँ और Index Scan using ... on chunks खोजें। यदि आपको इसके बजाय Seq Scan on chunks दिखाई देता है, तो उस ऑपरेटर क्लास के साथ इंडेक्स को फिर से बनाएँ जो उस ऑपरेटर से मेल खाता है जिसका आप वास्तव में उपयोग कर रहे हैं।
LIMIT से कम पंक्तियाँ हैं, और एक WHERE क्लॉज मौजूद है। यह ऊपर बताया गया फिल्टरिंग ट्रैप है। 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 को नामित करती है। सेटिंग को कम करें, या किसी बड़े प्लान पर इंडेक्स बनाएँ और डंप को रिस्टोर करें।
एक का चयन करना
यदि आप पहले से ही Postgres चला रहे हैं और आपके पास कुछ मिलियन से कम vectors हैं, तो pgvector का उपयोग करें। Index डेटा के साथ ही रहता है, फ़िल्टरिंग एक WHERE clause है, और आपका मौजूदा बैकअप इसे पहले से ही कवर करता है। यदि index इतना बड़ा है कि उसे अपनी memory ceiling की आवश्यकता है, या आपको भारी payload फ़िल्टरिंग की आवश्यकता है, तो इसके साथ Qdrant चलाएं और संचालन के लिए एक दूसरी service को स्वीकार करें।
लगभग एक लाख vectors से कम के लिए, कुछ भी install करने से पहले brute-force scan को मापें। उस आकार पर perfect recall और बिना किसी build step के exhaustive search कोई समझौता नहीं है। यह सही उत्तर है, और इसके बजाय approximate index का उपयोग करने का अर्थ है उन milliseconds के बदले में tuning और RAM का दबाव लेना जिन्हें आप खर्च ही नहीं कर रहे थे।
FAQ
क्या मुझे एक समर्पित vector database की आवश्यकता है, या Postgres पर्याप्त है?
यदि आपका डेटा पहले से ही Postgres में है, तो pgvector अधिकांश तुलनाओं के सुझाव से कहीं अधिक समय तक पर्याप्त रहता है। यह vectors को एक सामान्य कॉलम में स्टोर करता है, इसलिए filtered search एक WHERE क्लॉज है और आपका मौजूदा बैकअप इंडेक्स को कवर करता है। जब vector workload को अपनी मेमोरी सीमा की आवश्यकता हो, या जब आपको ऐसी payload filtering और quantisation की आवश्यकता हो जो pgvector प्रदान नहीं करता, तब Qdrant जैसे समर्पित स्टोर पर जाएँ।
एक VPS में कितने vectors आ सकते हैं?
अनुमान लगाने के बजाय number_of_vectors * dimensions * 4 bytes * 1.5 का उपयोग करके गणना करें। दस लाख 768-dimension वाले vectors लगभग 4.3 GiB के होते हैं, इसलिए 8 GB का प्लान Postgres के लिए जगह के साथ इसे संभाल सकता है। दस लाख 3072-dimension वाले vectors लगभग 17 GiB के होते हैं और इसके लिए बहुत बड़े प्लान की आवश्यकता होती है। जो संख्या इसे सबसे अधिक प्रभावित करती है, वह आपके embedding model का dimension है, इसलिए उस मॉडल को मेमोरी बिल को ध्यान में रखते हुए चुनें।
जब इंडेक्स एक ही मशीन पर हो तो मेरी vector search धीमी क्यों है?
एक ही बॉक्स पर नेटवर्क आपकी समस्या नहीं है, इसलिए उन दो चीजों को देखें जो समस्या हो सकती हैं। सबसे पहले, embedding कॉल को अलग से टाइम करें, क्योंकि CPU पर query vector उत्पन्न करने में अक्सर सर्च से कहीं अधिक समय लगता है। दूसरा, जाँचें कि इंडेक्स RAM में है या नहीं। एक HNSW सर्च ग्राफ के माध्यम से रैंडम तरीके से जंप करती है, इसलिए जैसे ही ग्राफ डिस्क पर जाता है, प्रत्येक जंप एक डिस्क रीड बन सकता है, और Qdrant का अपना मार्गदर्शन यह है कि RAM में रखे गए vectors को आधा करने से सर्च लेटेंसी लगभग दोगुनी हो जाती है।
क्या मुझे HNSW इंडेक्स बनाना चाहिए?
लगभग एक लाख vectors से नीचे नहीं। एक exhaustive scan प्रति क्वेरी n * d * 4 बाइट्स पढ़ता है, जो 768 dimensions के 100,000 vectors पर 307 MB होता है, और एक आधुनिक CPU इसे बिना किसी बिल्ड स्टेप के और सटीक रिकॉल के साथ मिलीसेकंड में स्ट्रीम कर देता है। पहले अपने हार्डवेयर पर स्कैन को मापें। इंडेक्स तब बनाएँ जब मापा गया स्कैन समय वास्तव में बहुत धीमा हो, न कि इसलिए क्योंकि किसी बेंचमार्क लेख ने ऐसा कहा है।
m को बढ़ाने से मुझे वास्तव में क्या नुकसान होता है?
मेमोरी से कहीं अधिक, बिल्ड टाइम और इंसर्ट टाइम। 768 dimensions पर, डिफ़ॉल्ट m = 16 से m = 64 तक जाने पर प्रति vector 512 बाइट्स के ग्राफ लिंक जुड़ते हैं, जबकि 3072 बाइट्स का vector डेटा होता है, इसलिए कुल मेमोरी लगभग 12 प्रतिशत बढ़ जाती है। हालाँकि, प्रत्येक इंसर्ट को चार गुना अधिक पड़ोसियों को खोजना और लिंक करना पड़ता है। पहले ef_search को ट्यून करें, क्योंकि इसे बदलने में कोई लागत नहीं आती और किसी रीबिल्ड की आवश्यकता नहीं होती।