
slater v0.24.4
कम-मेमोरी graphdb जिसमें Bolt+tls समर्थन, एट-रेस्ट एन्क्रिप्शन और वेक्टर शामिल हैं, जो लोकल रेप्लिका ग्राफ उपयोग-मामलों के लिए डिज़ाइन किया गया है।
Slater
वर्तमान संस्करण: v0.24.4 — सभी रिलीज़.
एक पंक्ति में: Slater उन ग्राफ़ों को सर्व करता है जो मेमोरी में नहीं समाते — करोड़ों नोड्स और अरबों एजेस, सिर्फ कुछ सौ MB RAM में — मानक Bolt के ऊपर, ताकि कोई भी neo4j ड्राइवर बिना बदलाव के काम करे, ग्राफ़ के बगल में डिस्क-नेटिव वेक्टर खोज भी हो, और यह लाइव, टिकाऊ राइट्स भी लेता है, बिना यह सब खोए। रेज़िडेंट मेमोरी आपके चुने हुए कैश बजट से तय होती है, ग्राफ़ के आकार से नहीं।
शॉर्टकट
Why Slater exists
एक ग्राफ़ डेटाबेस डेटा को चीज़ों (नोड्स) और उनके बीच के संबंधों (एजेस) के रूप में संग्रहीत करता है, जहाँ संबंधों को पहले दर्जे का नागरिक माना जाता है। यही वह चीज़ है जो आप चाहते हैं जब आपके सवाल पंक्तियों के बजाय कनेक्शनों के बारे में हों — "इस खाते से तीन हॉप के भीतर कौन है?", "इस बिल्ड के पीछे पूरी डिपेंडेंसी चेन क्या है?", "कौन से खाते एक डिवाइस, एक पता और एक कार्ड साझा करते हैं?" — ऐसे क्वेरी जो SQL में रिकर्सिव जॉइन का दलदल बन जाते हैं लेकिन ग्राफ़ में स्वाभाविक रूप से निकल आते हैं।
ग्राफ़ डेटाबेस के बारे में सबसे आम शिकायत यह है कि वे RAM में जितना रख सकते हैं उससे आगे स्केल नहीं करते। उनमें से कई (जैसे neo4j, Memgraph, FalkorDB, आदि) पूरे ग्राफ़ को रेज़िडेंट रखते हैं: 40 GB का ग्राफ़ 40 GB मेमोरी चाहता है — प्रति इंस्टेंस। प्रति रीजन, प्रति टेनेंट या प्रति पॉड एक रेप्लिका चाहिए? बिल को गुणा करें। और एक निश्चित आकार के बाद वे लोड ही नहीं होते: जैसे 90 मिलियन नोड / 1.5B-एज वाले Wikidata ग्राफ़ को ~64–128 GiB रेज़िडेंट चाहिए, इसलिए इन-मेमोरी इंजन इसे खोल ही नहीं सकते।
Slater इसका जवाब है। ग्राफ़ को मेमोरी में लोड करने के बजाय, यह उसे एक बार, ऑफ़लाइन कंपाइल करता है: slater-build आपके डेटा को एक content-addressed, अपरिवर्तनीय ऑन-डिस्क इमेज में बदल देता है, और उसके बाद कोई भी संख्या में Slater सर्वर उस इमेज को Bolt के ऊपर सर्व करते हैं (जिससे आपके मौजूदा neo4j ड्राइवर बिना बदलाव काम करते हैं), ब्लॉक्स को माँग पर पेज करते हुए और केवल एक निश्चित कैश बजट रेज़िडेंट रखते हुए। इसी तरह वही 90M-नोड ग्राफ़ कुछ सौ MB RAM से सर्व होता है — ग्राफ़ का आकार और मेमोरी बिल एक-दूसरे से अलग हो जाते हैं। एक 4 GB ग्राफ़ और एक 400 GB ग्राफ़ को सर्व करने के लिए समान RAM लगती है, इसलिए आप सस्ते, स्टेटलेस रीड रेप्लिका फैला सकते हैं और ग्राफ़ को हीप के बजाय स्टोर में रख सकते हैं।
यह इसे RAG के पीछे नॉलेज ग्राफ़, रिकमेंडेशन और आइडेंटिटी ग्राफ़, डिपेंडेंसी ग्राफ़ — यानी किसी भी बड़ी और जुड़ी हुई चीज़ के लिए स्वाभाविक विकल्प बनाता है जिसे आप सस्ते में और बार-बार क्वेरी करना चाहते हैं। डिस्क-नेटिव वेक्टर खोज ग्राफ़ के ठीक बगल में रहती है, इसलिए वही इंजन एम्बेडिंग के लिए रिट्रीवल लेयर भी है।
हालाँकि, एक बार कंपाइल होने का मतलब जम जाना नहीं है। वह इमेज एक आधार है, अंतिम स्थिति नहीं: उसके ऊपर एक ऑप्ट-इन राइट लेयर बैठती है, जिससे एक लाइव ग्राफ़ को बिना कुछ पुनर्निर्मित किए सुधा और बढ़ाया जा सकता है।
Reads and writes
कोर अपरिवर्तनीय है; ग्राफ़ नहीं है। राइटेबल लेयर चालू करें (delta.enabled) और आप Bolt के ऊपर लिखें — एक प्रॉपर्टी सुधारें, एक नोड जोड़ें, एक एज हटाएँ — और बदलाव टिकाऊ रूप से सेव हो जाता है, इमेज को पुनर्निर्मित किए बिना। रीड साइड पर इसे सस्ता रखने वाली चीज़ यह है कि राइट्स कहाँ रहते हैं।
राइट्स अपरिवर्तनीय कोर के ऊपर एक log-structured-merge (LSM) लेयर में इकट्ठा होते हैं: एक राइट-अहेड लॉग और एक इन-मेमोरी टेबल, जो अपरिवर्तनीय डेल्टा सेगमेंट में spill होते हैं, और समय-समय पर consolidation द्वारा एक नए कोर में वापस मोड़ दिए जाते हैं। इससे आपको क्या मिलता है:
- जिस ग्राफ़ पर नहीं लिखा गया है, उस पर रीड्स की लागत बिल्कुल पहले जैसी ही रहती है। एक खाली डेल्टा एक मर्ज नहीं, बल्कि एक पूर्वानुमेय शाखा होती है — रीड पाथ बाइट-समान होता है, चाहे राइटेबल लेयर चालू हो या नहीं।
- किसी राइट की रीड लागत डेल्टा के आकार के साथ बढ़ती है, ग्राफ़ के आकार के साथ नहीं। पूरे ग्राफ़ के उत्तर —
count(*), लेबल और रिलेशनशिप-टाइप मार्जिनल — लंबित राइट्स के बावजूद मेटाडेटा रीड्स बने रहते हैं: डेल्टा अपने खुद के काउंटर रखता है, इसलिए 91.6M-नोड कोर पर आधे मिलियन लंबित राइट्स के साथ भीcount(*)बिना एक भी ब्लॉक छुए दसियों मिलीसेकंड में उत्तर दे देता है। - स्वीकृत होने का मतलब है टिकाऊ। एक अकेला राइटर कतार को खाली करता है और
SUCCESSकेवल उसfsyncके बाद लौटाता है जो राइट को कवर करता है। अपने राइट्स को समूहित करें और वे सस्ते होते हैं — एक राइट-UNWINDप्रति बैच एकfsyncकरता है, प्रति पंक्ति नहीं। - Business-key राइट्स, दोनों डायलेक्ट में।
MERGE/MATCH … SET/DELETE(औरCREATE/REMOVE, डिटैच डिलीट, रिलेशनशिप राइट्स) जो नोड के आइडेंटिटी प्रॉपर्टी पर keyed होते हैं — या समकक्ष ISO GQL डेटा-संशोधन स्टेटमेंट (INSERT/SET/REMOVE/DELETE), जो उसी पाथ पर उतरते हैं। नोड्स और एजेस पर सुधारें, इन्सर्ट करें, upsert करें और हटाएँ, उसी तरह जैसे आपका डेटा पहले से संबोधित है।
जब लेयर बंद हो — जो कि डिफ़ॉल्ट है — Slater शुद्ध अपरिवर्तनीय कोर सर्व करता है और राइट्स को अस्वीकार करता है। पूरा मॉडल देखने के लिए राइटेबल लेयर देखें।
नाम के बारे में। Slater का नाम Archer (एक बेहतरीन शो) में CIA एजेंट के नाम पर रखा गया है जो केवल एक नाम से पुकारे जाने पर ज़ोर देता है — "Just… Slater" — और उसमें मेरा पसंदीदा किरदार है। देखें किरदार विकी पेज।
What you get
- RAM आपके कैश बजट से तय होती है, आपके ग्राफ़ के आकार से नहीं — जितने चाहें उतने रीड रेप्लिका फैलाएँ; ग्राफ़ को कभी मेमोरी में समाना ज़रूरी नहीं है।
- ग्राफ़ के लिए ड्रॉप-इन — Bolt बोलता है, इसलिए कोई भी मानक neo4j ड्राइवर (JS, Python, Go…) बिना बदलाव चलता है। यह Cypher है (साथ में ISO GQL का एक हिस्सा, रीड्स और राइट्स); सीखने के लिए कुछ नया नहीं।
- लाइव, टिकाऊ राइट्स — अपरिवर्तनीय कोर के ऊपर एक ऑप्ट-इन LSM लेयर: नोड्स और एजेस पर business-key
MERGE/SET/DELETE, group-committed औरfsync-टिकाऊ, consolidation द्वारा नए कोर में वापस मोड़े जाते हैं। रीड्स को इसकी कीमत नहीं चुकानी पड़ती। - फ़ाइल स्वैप से डिप्लॉयमेंट — ऑफ़लाइन एक नई content-hashed generation बनाएँ,
currentपॉइंटर को atomically पलटें, और सर्वर उसे उठा लेते हैं। हर ब्लॉक checksum होता है, इसलिए आधी-कॉपी की गई इमेज सर्व होने के बजाय अस्वीकार कर दी जाती है। - अंतर्निर्मित वेक्टर खोज — डिस्क-नेटिव approximate-nearest-neighbour (cosine, L2, या dot KNN) आपके ग्राफ़ के ठीक बगल में बैठता है, उस समय के लिए जब यह RAG पाइपलाइन के पीछे रिट्रीवल लेयर हो, और एम्बेडिंग को जगह पर लिखा जा सकता है — वेक्टर जोड़ने या बदलने के लिए कोई ऑफ़लाइन पुनर्निर्माण नहीं।
- डिज़ाइन से सुरक्षित — रीड और राइट अनुमतियाँ स्वतंत्र हैं, साथ में वैकल्पिक at-rest एन्क्रिप्शन, TLS Bolt, argon2id-हैश्ड ACLs, और रीड रेप्लिका के लिए रीड-ओनली कंटेनर rootfs भी। एक मास्टर की कॉन्फ़िगर करें और ऑन-डिस्क इमेज एन्क्रिप्टेड होने के साथ-साथ authenticated भी होती है — उसका मैनिफेस्ट एक keyed MAC रखता है, इसलिए डेटा डायरेक्ट्री तक राइट एक्सेस रखने वाला लेकिन की न रखने वाला हमलावर ऐसा मैनिफेस्ट जाली नहीं बना सकता जिसे सर्वर स्वीकार करे। की के बिना आपको फिर भी कंटेंट हैश मिलता है, जो आधी-कॉपी या भ्रष्ट इमेज को पकड़ लेता है — लेकिन जानबूझकर बदली गई इमेज को नहीं। कौन सा कॉन्फ़िगरेशन क्या देता है।
Features
| सुविधा | आपके लिए इसका मतलब |
|---|---|
| सीमित, पूर्वानुमेय मेमोरी | रेज़िडेंट मेमोरी तीन कैश बजट को ट्रैक करती है जो आप तय करते हैं, सीमित प्रति-एंट्री और एलोकेटर ओवरहेड के साथ — यह ग्राफ़ के आकार के साथ नहीं बढ़ती; आप पूरे ग्राफ़ के लिए प्रावधान करने के बजाय प्रदर्शन/RAM का ट्रेड-ऑफ़ ट्यून करते हैं। बैकग्राउंड purge वाला jemalloc एलोकेटर भारी क्वेरी बर्स्ट के बाद मुक्त की गई मेमोरी को OS को लौटा देता है, इसलिए रेज़िडेंट आकार बर्स्ट के बाद के हाई-वॉटर मार्क पर अटका नहीं रहता, बल्कि अपने आइडल फ्लोर की ओर वापस गिर जाता है। |
| बॉक्स से बाहर मल्टी-टेनेंट | एक सर्वर कई ग्राफ़ होस्ट करता है, प्रति-उपयोगकर्ता रीड अनुमतियों के साथ — मल्टी-डेटाबेस आइसोलेशन जो ज़्यादातर ग्राफ़ डीबी पेड/एंटरप्राइज़ टियर के लिए सुरक्षित रखते हैं। |
| at rest और in transit एन्क्रिप्शन | प्रति-ब्लॉक XChaCha20-Poly1305 सीलिंग (की कभी डिस्क पर नहीं लिखी जाती) साथ में वैकल्पिक TLS (bolt+s://)। यह संरचना से GDPR-अनुकूल है। एन्क्रिप्शन ही authenticated अखंडता भी खरीदता है: बिल्डर मैनिफेस्ट को keyed MAC से सील करता है, और की रखने वाला सर्वर उसे सत्यापित करता है तथा ऐसी generation को सर्व करने से मना कर देता है जिसका मैनिफेस्ट जाली हो, बदला गया हो, या उसका MAC निकाल दिया गया हो। बिना की वाली (प्लेनटेक्स्ट) इमेज केवल बिना की वाले कंटेंट हैश द्वारा सुरक्षित होती है — पूर्णता और भ्रष्टाचार के लिए, छेड़छाड़ के लिए नहीं। देखें प्रत्येक कॉन्फ़िगरेशन में अखंडता का क्या अर्थ है। |
| छोटा इंस्टॉल | distroless glibc आधार पर एक छोटा stripped बाइनरी (कोई shell/apt नहीं) — मल्टी-आर्क (amd64/arm64) इमेज ~22 MB में खिंचती है, या सर्वर-ओनली slater:latest-lite टैग के लिए ~12 MB; पूरी तरह Rust TLS, कोई OpenSSL नहीं। Pull करें और चलाएँ। |
| आवधिक प्रकाशन के लिए बना | ग्राफ़ ऑफ़लाइन बनाएँ, उसे अपरिवर्तनीय रूप से सर्व करें, फिर शून्य डाउनटाइम के साथ नए संस्करण को atomically स्वैप करें — डेटा-वेयरहाउस / शेड्यूल्ड-रिफ़्रेश वर्कलोड के लिए आदर्श। |
| लोड के तहत मज़बूत | सर्वर और ऑफ़लाइन बिल्डर दोनों #![forbid(unsafe_code)] के साथ कंपाइल होते हैं — इंजन की एकमात्र unsafe ऑडिट की गई jemalloc एलोकेटर क्रेट में रहती है। कोर अपरिवर्तनीय है, इसलिए रीड्स कोई लॉक नहीं लेते और कभी किसी राइटर का इंतज़ार नहीं करते; एक अकेला राइटर म्यूटेशन को केवल राइट पाथ के पीछे सीरियलाइज़ करता है। कोई GC pause नहीं, कोई data race नहीं। एक खराब क्वेरी सर्वर को नीचे नहीं गिरा सकती। |
| आपके neo4j टूल्स के साथ काम करता है | Bolt 5.4 / 4.4 / 4.1 बोलता है — मानक neo4j ड्राइवर (JS, Python, Go, Java…), cypher-shell, या ग्राफ़ ब्राउज़र बिना बदलाव उपयोग करें। |
| समृद्ध Cypher क्वेरी सतह | एक व्यापक रीड सतह: MATCH/WHERE/WITH/UNION, CALL {…} सबक्वेरी, 70+ फ़ंक्शन और एग्रीगेशन, temporal और geospatial मान, और regex। |
| लाइव, टिकाऊ राइट्स | अपरिवर्तनीय कोर के ऊपर एक ऑप्ट-इन सिंगल-राइटर LSM लेयर (delta.enabled): नोड्स और रिलेशनशिप पर business-key MERGE / SET / DELETE / CREATE / REMOVE, बैच्ड राइट-UNWIND (प्रति बैच एक fsync), और CALL slater.consolidate() — group-committed, fsync-टिकाऊ, और consolidation द्वारा एक नए कोर में वापस मोड़ दिया जाता है। डेल्टा खाली होने पर रीड पाथ बाइट-समान रहता है। |
| ISO GQL, रीड और राइट | उसी Bolt कनेक्शन पर ISO GQL (ISO/IEC 39075) का एक उपसमुच्चय बोलता है — quantified paths, path restrictors, shortest-path selectors, label/type boolean expressions, FOR, CAST, एक वैकल्पिक GQL/CYPHER डायलेक्ट प्रीफ़िक्स — और, राइटेबल लेयर चालू होने पर, GQL के डेटा-संशोधन स्टेटमेंट (INSERT / SET / REMOVE / [DETACH] DELETE) उसी टिकाऊ राइट पाथ पर उतरते हैं। Cypher और GQL, रीड्स और राइट्स, एक ही इंजन में। |
| एक इंजन में वेक्टर + ग्राफ़ | एम्बेडिंग/RAG के लिए डिस्क-नेटिव ANN वेक्टर खोज (Vamana + PQ; cosine / L2 / dot), साथ में ग्राफ़ एल्गोरिदम (PageRank, BFS, betweenness, WCC…) — लाखों वेक्टरों के साथ भी सीमित मेमोरी। एम्बेडिंग writable हैं (FreshDiskANN-शैली राइट लैडर): वेक्टर इन्सर्ट / अपडेट / डिलीट करें, तुरंत KNN में दिखाई देते हैं, बिना पुनर्निर्माण के बेस में मोड़ दिए जाते हैं। |
| नेटवर्क स्टोरेज पर सुरक्षित | हर फ़ाइल BLAKE3 content-hashed होती है और खोलते समय सत्यापित होती है; फटी या आधी-कॉपी की गई इमेज सर्व नहीं होती, अस्वीकार कर दी जाती है। NFS/रिमोट वॉल्यूम के लिए डिज़ाइन किया गया (कोई mmap आश्चर्य नहीं)। |
| प्लगेबल स्टोरेज बैकएंड | उसी generation फॉर्मेट को लोकल फाइलसिस्टम, S3 (S3-संगत) बकेट, या Google Cloud Storage बकेट से सर्व करें — एक बार प्रकाशित करें, स्टेटलेस रेप्लिका में फैलाएँ — ऑब्जेक्ट स्टोर के सामने वैकल्पिक लोकल-SSD कैश टियर के साथ। देखें Storage backends। |
दो बाइनरी इस वर्कस्पेस को बनाते हैं:
| Binary | Role |
|---|---|
slater | ऑनलाइन Bolt सर्वर (कंटेनर ENTRYPOINT): रीड्स सर्व करता है और, delta.enabled के साथ, सिंगल-राइटर टिकाऊ राइट पाथ। |
slater-build | ऑफ़लाइन कंपाइलर: primitive-Cypher डंप को एक अपरिवर्तनीय, content-hashed generation डायरेक्ट्री में बदलता है। |
Slater बल्क बिल्डिंग को सर्विंग से अलग करता है: slater-build भारी काम
ऑफ़लाइन करता है — आपके डेटा को इन्जेस्ट करके एक अपरिवर्तनीय generation में कंपाइल करना — इसलिए एक
कोल्ड ग्राफ़ कभी सर्विंग हॉट पाथ पर नहीं बनता। सर्वर के भीतर, रीड
सतह Cypher के एक व्यापक हिस्से का उत्तर देती है — पैटर्न मैचिंग, WITH/UNION/CALL {…}
सबक्वेरी, 70+ स्केलर और एग्रीगेट फ़ंक्शन, temporal और geospatial मान, ग्राफ़
एल्गोरिदम (algo.*), और डिस्क-नेटिव वेक्टर KNN (db.idx.vector.queryNodes) —
जबकि राइटेबल लेयर का डेल्टा ओवरले उस सतह के नीचे बैठता है और खाली होने पर
ज़ीरो-कॉस्ट होता है, इसलिए रीड्स कभी राइट-साइड मशीनरी नहीं उठाते। आप ग्राफ़ को दो तरह से
अपडेट कर सकते हैं: Bolt के ऊपर लाइव लिखकर (देखें राइटेबल लेयर),
या ऑफ़लाइन एक नई generation बनाकर और current पॉइंटर को atomically स्वैप करके, जिसे
चालू सर्वर अपने generation guard के ज़रिए उठा लेता है (देखें
Generation guard)।
Documentation
पूरा उपयोगकर्ता मैनुअल docs/manual/ में रहता है — एक फ़ीचर-दर-फ़ीचर मार्गदर्शिका जो हर क्षमता के लिए बताती है कि वह क्या है, क्यों मौजूद है, और उसे कैसे उपयोग करें, साथ में काम किए हुए उदाहरण जिन्हें आप बंडल किए गए नमूना ग्राफ़ के खिलाफ चला सकते हैं। इस अवलोकन से आगे किसी भी चीज़ के लिए वहीं से शुरू करें।
- नए हैं? Quickstart पाँच चरणों में ग्राफ़ बनाता और सर्व करता है।
- क्वेरी लिख रहे हैं? Querying, Functions & expressions, Procedures & algorithms, Vector search, Writing data।
- ग्राफ़ बना रहे हैं? Building graphs और Build CLI reference।
- Slater चला रहे हैं? Deployment, Storage, Configuration reference, Security, Performance tuning।
Running with Docker
Slater को Docker डिप्लॉयमेंट के रूप में चलाने के लिए डिज़ाइन किया गया है — यही उपयोग करने का अपेक्षित तरीका है। पहले से बनी मल्टी-आर्क इमेज (linux/amd64 + linux/arm64) Docker Hub पर hikarisystems/slater पर प्रकाशित होती हैं, हर रिलीज़ पर :latest और :vX.Y.Z टैग के साथ:```sh
docker pull hikarisystems/slater:latest
एक Docker-कमांड-केवल उपयोग, कॉन्फ़िगरेशन और संचालन मार्गदर्शिका
[`DOCKERHUB.md`](https://github.com/hikari-systems/slater/blob/HEAD/DOCKERHUB.md) में रहती है (और Docker Hub अवलोकन पृष्ठ पर मिरर की जाती है) —
**यदि आप तैनाती कर रहे हैं तो वहाँ से शुरू करें।** संक्षेप में:```sh
# Build a graph generation with the offline writer:
docker run --rm -v slater-data:/data -v "$PWD/dumps:/dumps:ro" \
--entrypoint /app/slater-build hikarisystems/slater:latest \
--input /dumps/people.cypher --graph people --data-dir /data
# Serve it over Bolt on 7687 (read-only unless `delta.enabled`):
docker run -d --name slater -p 7687:7687 \
-v slater-data:/data:ro -v "$PWD/acl.json:/config/acl.json:ro" \
hikarisystems/slater:latest
स्थानीय रूप से इमेज बनाने के लिए इसके बजाय (जैसे विकास के लिए):```sh
Build the image (both binaries).
docker compose build
Serve (expects generations under the slater-data volume / your /data mount).
docker compose up slater
Build a generation with the offline writer (profile build):
docker compose run --rm builder
--input /dumps/people.cypher --graph people --data-dir /data
The builder stage installs `cmake`, `clang` और `libclang-dev` rustls `aws-lc-rs` बैकएंड के लिए; `git` (पहले से बेस इमेज में मौजूद) `hs-utils` git+tag निर्भरता के लिए आवश्यक है, जिसे `.cargo/config.toml` git CLI के माध्यम से प्राप्त करता है।
नीचे दिए गए अनुभाग on-disk प्रारूप, कॉन्फ़िगरेशन, ACL, और एक स्थानीय (गैर-Docker) कार्यशील उदाहरण को कवर करते हैं।
## यह कैसे काम करता है```
slater-build slater (Bolt server)
dump.cypher ──────────▶ /data/<graph>/<uuid>/ ──────────▶ neo4j driver
(offline, atomic) MANIFEST.json, *.blk, (bolt / bolt+s)
range/*.isam, vector/*.{vamana,pq},
current → <uuid>
- एक generation एक अपरिवर्तनीय निर्देशिका होती है: एक
MANIFEST.json(प्रतीक तालिकाएँ, इंडेक्स डिस्क्रिप्टर, एक वैकल्पिक एन्क्रिप्शन हेडर), कॉलमर ब्लॉक फ़ाइलें (node_props.blk,node_labels.blk,edge_props.blk,topology.csr.blk,vectors.f32.blk), रेंज इंडेक्स (range/<name>.isam), थ्रेशोल्ड-ऊपर ANN इंडेक्स (vector/<label>.<prop>.{vamana,pq}), और एकcurrentटेक्स्ट पॉइंटर। - हर ब्लॉक zstd-संपीड़ित और BLAKE3-चेकसमित होता है;
--encryptके साथ प्रत्येक ब्लॉक को अतिरिक्त रूप से XChaCha20-Poly1305 (AEAD at rest) से सील किया जाता है। - सर्वर एक generation को मैनिफेस्ट के विरुद्ध हर फ़ाइल को पुनः-हैश करके खोलता है, इसलिए एक आधी-कॉपी / छोटी (truncated) इमेज — डेटा निर्देशिका पर एक टूटी हुई कॉपी, जो रिमोट/नेटवर्क स्टोरेज हो सकती है — परोसे जाने के बजाय अस्वीकार कर दी जाती है।
- रीड्स तीन सीमित कैश पूल के माध्यम से प्रवाहित होती हैं — एक डीकंप्रेस्ड-ब्लॉक LRU, एक वेक्टर-इंडेक्स पूल (निवासी PQ कोड + एक Vamana-ब्लॉक LRU), और एक रिज़ल्ट LRU — प्रत्येक का अपना बाइट बजट होता है। प्रत्येक पूल अपने पास रखी चीज़ों का भार लेता है और अपने बजट के भीतर रहने के लिए बाहर निकालता (evict) है, इसलिए RSS बजट को बाध्य प्रति-प्रविष्टि और आवंटक (allocator) ओवरहेड के भीतर ट्रैक करता है, न कि ग्राफ़ के साथ बढ़ता है।
लिखने योग्य परत
delta.enabled के साथ, अपरिवर्तनीय generation एक छोटे log-structured-merge tree का पूर्णतः-संकुचित निचला स्तर ("core") बन जाता है, और लाइव राइट्स उसके ऊपर चलती हैं:```
write (Bolt) read (Bolt)
│ │
▼ ▼
┌──────────────┐ flush ┌──────────────┐ ┌──────────────────────┐
│ WAL + active │ ───────▶ │ L0 delta │ │ a query pins one │
│ memtable │ │ segments │ │ (core, delta) view │
└──────────────┘ └──────┬───────┘ │ and reads the merge │
(fsync = ack) │ └──────────────────────┘
consolidation │ (folds core + delta → fresh core)
▼
┌─────────────┐
│ new core │ (atomic current swap)
└─────────────┘
* **स्थायित्व तल — WAL.** प्रत्येक म्यूटेशन एक एकल के पीछे क्रमबद्ध किया जाता है
प्रति ग्राफ लेखक, एक प्रति-ग्राफ राइट-अहेड लॉग में जोड़ा जाता है, और `fsync` किया जाता है
Bolt `SUCCESS` लौटाने से पहले — इसलिए *स्वीकृत ⇒ स्थायी*, और फटी हुई पूँछ
रीप्ले पर गिरा दी जाती है। एक बैच राइट-`UNWIND` अपनी पंक्तियाँ जोड़ता है और **एक**
`fsync` पूरे बैच के लिए प्रतिबद्ध करता है। WAL **केवल स्थानीय डिस्क** है (यह
स्टोरेज बैकएंड के माध्यम से रूट नहीं होता), जो *लेखक* नोड को स्टेटफुल बनाता है: उसे
`delta.walDir` पर एक स्थायी स्थानीय वॉल्यूम चाहिए। रीड रेप्लिका स्टेटलेस रहती हैं।
* **मेमटेबल → L0 → समेकन।** लेखन एक इन-रैम मेमटेबल में जमा होते हैं
(`delta.memtableBytes` द्वारा सीमित); जब यह भर जाता है तो यह एक अपरिवर्तनीय L0 में फ्लश होता है
डेल्टा सेगमेंट। एक **समेकन** `{core + delta}` को एक नए कोर में विलीन करता है,
विलय किए गए दृश्य को `slater-build` के माध्यम से वापस क्रमबद्ध करके और `current` को
परमाणु रूप से बदलकर — किसी भी प्रकाशित जनरेशन के समान सामग्री-हैश गार्ड। इसे
`CALL slater.consolidate()` से हाथ से ट्रिगर करें, स्वचालित रूप से `delta.deltaCorePercent` पर
कोर के आकार के (वैकल्पिक रूप से ऑफ-पीक `delta.consolidateWindow` तक सीमित), या
`delta.deltaHardBytes` थ्रॉटल को अनियंत्रित वृद्धि को रोकने दें।
* **ओवरले रीड सतह के नीचे बैठता है।** निष्पादक एक के माध्यम से पढ़ता है
`ReadView` जो या तो नंगा कोर है (डेल्टा हमेशा खाली) या एक विलय
`(core, delta)` दृश्य; इंजन उस पर मोनोमोर्फाइज़्ड होता है, इसलिए एक खाली डेल्टा
एक ही अनुमानित शाखा में संकलित होता है और केवल-पढ़ने का पथ बाइट-समान होता है।
संपूर्ण-ग्राफ काउंटर (`count(*)`, लेबल/रिलटाइप मार्जिनल्स) डेल्टा के अपने
लाइव काउंटरों से परोसे जाते हैं, इसलिए वे लंबित लेखन के साथ भी मेटाडेटा रीड बने रहते हैं।
* **एक क्वेरी एक स्थिर स्नैपशॉट देखती है।** यह अपने पूरे जीवन के लिए एक `(core, delta)` टुपल को पिन करती है।
कोई मल्टी-स्टेटमेंट ट्रांज़ैक्शन और कोई रोलबैक नहीं है — एक लेखन
एक स्थायी, व्यावसायिक-कुंजी-संबोधित सुधार है, OLTP ट्रांज़ैक्शन नहीं।
सटीक लेखन व्याकरण और नियंत्रण विकल्प [कॉन्फ़िगरेशन](#environment--configuration)
तालिका (`delta.*`) और नीचे [कार्य उदाहरण](#worked-example) में हैं।
### रेंज इंडेक्स (ISAM)
एक रेंज इंडेक्स (`range/<name>.isam`, प्रत्येक अनुक्रमित `(label, property)` के लिए एक) एक
`MATCH (n:Label {prop: v})` या `WHERE n.prop <op> v` को मेल खाने वाले नोड
आईडी पर **लेबल को स्कैन किए बिना** हल करने देता है। यह एक
**[ISAM](https://en.wikipedia.org/wiki/ISAM)** (Indexed Sequential Access Method)
संरचना है — क्लासिक *स्थिर, क्रमबद्ध, ब्लॉक-संरचित* इंडेक्स, जो एक अपरिवर्तनीय जनरेशन के लिए बिल्कुल
सही आकार है: रीबैलेंस करने के लिए कोई इंसर्ट नहीं होते, इसलिए
ISAM की सरलता वह लाभ देती है जिसे B-tree का म्यूटेशन तंत्र केवल
जटिल बनाता।
* प्रविष्टियाँ `(value, entity_id)` मान के अनुसार क्रमबद्ध होती हैं और उसी में पैक की जाती हैं
zstd-संपीड़ित 256 KiB ब्लॉकों में, बाकी सब कुछ की तरह।
* एक छोटा **निवासी शीर्ष-स्तर** प्रत्येक ब्लॉक की पहली कुंजी रखता है (एक स्पार्स
इंडेक्स)। एक लुकअप उस इन-मेमोरी शीर्ष-स्तर पर बाइनरी-सर्च करता है ताकि वह *एक* ब्लॉक ढूँढ सके
जिसमें कुंजी हो सकती है, उस ब्लॉक को पढ़ता + डीकंप्रेस करता है, और उसे स्कैन करता है — इसलिए एक समानता
लुकअप **एक ब्लॉक रीड** है, और एक रेंज स्कैन उसके द्वारा फैले ब्लॉकों के सन्निहित रन को चलता है।
(यही कारण है कि एक `meshUi`-अनुक्रमित लुकअप एकल-अंक मिलीसेकंड है
जबकि एक गैर-अनुक्रमित संपत्ति पर समान मैच पूरे लेबल को स्कैन करता है।)
* प्लानर इसे `NodeScan::RangeEq` / `RangeRange` के माध्यम से चुनता है; एक गैर-अनुक्रमित
विधेय लेबल स्वीप या पूर्ण स्कैन पर वापस गिर जाता है, निष्पादक
किसी भी तरह हर विधेय की दोबारा जाँच करता है।
### वेक्टर खोज (Vamana + PQ) — cosine, L2 और dot, रीड *और* राइट
वेक्टर KNN (`db.idx.vector.queryNodes`) **cosine, L2, या dot-product (MIPS)**
इंडेक्स पर चलता है। आधार इंडेक्स ऑफ़लाइन दो निष्पादन पथों के साथ बनाया जाता है, जिसे प्रति इंडेक्स
`--ann-threshold` (डिफ़ॉल्ट 50 000 वेक्टर) द्वारा चुना जाता है:
* **सीमा से नीचे — ब्रूट फोर्स।** पूर्ण `f32` वेक्टर
`vectors.f32.blk` में रहते हैं; एक क्वेरी इंडेक्स के समूह को स्कैन करती है और सटीक दूरी की गणना
इंडेक्स की मीट्रिक में करती है। सरल और सटीक; जब वेक्टर सेट छोटा हो तो ठीक है।
* **सीमा पर या उससे ऊपर — Vamana + PQ**, डिस्क-नेटिव ANN पथ जो
निवासी मेमोरी को बाउंडेड रखता है चाहे कितने भी वेक्टर हों:
* **[Vamana](https://arxiv.org/pdf/2401.11324)** DiskANN कार्य-श्रृंखला का ग्राफ इंडेक्स है:
एक एकल प्रॉक्सिमिटी ग्राफ जिसके किनारों को प्रून किया जाता है (
`--vamana-r` आउट-डिग्री और `--vamana-alpha` लंबे-किनारे कारक) ताकि एक *ग्रीडी
बीम सर्च* — मेडॉयड से शुरू करें, बार-बार क्वेरी की ओर हॉप करें,
`vectorQuery.beamWidth` चौड़ाई की उम्मीदवार सूची रखते हुए — कुछ ही हॉप्स में नोड के
सच्चे पड़ोसियों तक पहुँचता है, यानी **प्रति क्वेरी कुछ यादृच्छिक ब्लॉक रीड**। ग्राफ
ब्लॉक (`vector/<label>.<prop>.vamana`) थोक में नहीं रखे जाते, बल्कि वेक्टर कैश के माध्यम से पेज इन किए जाते हैं।
* **[प्रोडक्ट क्वांटाइज़ेशन (PQ)](https://medium.com/aiguys/product-quantization-k-nn-for-big-datasets-12431d764c4e)**
प्रत्येक वेक्टर को एक छोटे कोड में संपीड़ित करता है (`--pq-subspaces` × `--pq-bits`):
आयामों को उप-स्थानों में विभाजित किया जाता है, प्रत्येक स्वतंत्र रूप से k-means-क्लस्टर किया जाता है, और
वेक्टर को निकटतम-सेंट्रॉइड आईडी के टुपल के रूप में संग्रहीत किया जाता है। ये कोड
(`vector/<label>.<prop>.pq`) **निवासी** रखने के लिए पर्याप्त छोटे हैं, इसलिए
बीम सर्च RAM से उम्मीदवारों को स्कोर करता है और केवल चुने हुए कुछ पूर्ण वेक्टर डिस्क से पढ़े जाते हैं।
वह निवासी PQ सेट ही है जिसे `cache.vectorCacheBytes` पूल पिन करता है।
**राइटेबल एम्बेडिंग्स — वेक्टर राइट लैडर ([FreshDiskANN](https://arxiv.org/abs/2105.09613)-शैली)।**
एक अनुक्रमित एम्बेडिंग प्रथम श्रेणी का राइटेबल मान है। `SET n.embedding = vecf32([…])` (और `REMOVE`) राइट
डेल्टा में आता है और **सटीक रैंक के साथ तुरंत KNN-दृश्यमान** होता है, फिर एक सेगमेंट
फ्लश, एक मर्ज, और एक समेकन से बच जाता है। एक क्वेरी तीन स्तरों तक मर्ज करती है — सीलबंद आधार
इंडेक्स, एक सीलबंद प्रति-सेगमेंट इंडेक्स, और एक इन-मेमोरी **RW-इंडेक्स** (राइट डेल्टा पर
एक लाइव म्यूटेबल Vamana) — इसलिए विलंबता स्थिर रहती है जैसे-जैसे लेखन जमा होते हैं, लंबित
लेखन की गिनती के साथ बढ़ने के बजाय। एक डिलीट एक *छेद* छोड़ती है: नोड वापस लौटाया जाना बंद हो जाता है
लेकिन एक नेविगेशनल वेपॉइंट बना रहता है जब तक कि एक पृष्ठभूमि **डिलीट-समेकन** इसे ग्राफ से बाहर
नहीं निकाल देता, इसलिए डिलीट क्वेरी IO की लागत देना बंद कर देते हैं। और क्योंकि ऑन-डिस्क ग्राफ
अपने पड़ोसियों को नोड आईडी के बजाय लेआउट स्थिति से संबोधित करता है, `CALL slater.consolidate()`
Vamana को **रेफरेंस द्वारा** ले जाता है — हार्ड-लिंक्ड, बाइट-समान — और केवल एक
छोटे आईडी कॉलम को फिर से लिखता है, वेक्टर राइट्स को O(N·R·L) ग्राफ पुनर्निर्माण
**के बिना** आधार में मोड़ता है। मापे गए आँकड़े, चेतावनियों के साथ, [प्रदर्शन रिपोर्ट](https://github.com/hikari-systems/slater/blob/HEAD/docs/PERF-REPORT.md) में हैं।
## स्टोरेज बैकएंड (फ़ाइलसिस्टम / S3 / GCS)
हर जनरेशन फ़ाइल को `std::fs` के बजाय सीधे एक **`ObjectStore`** अमूर्तन के माध्यम से खोला जाता है,
तो *समान* ऑन-डिस्क बाइट प्रारूप — ब्लॉक, इंडेक्स,
मैनिफेस्ट, `current` पॉइंटर — किसी भी बैकएंड से अपरिवर्तित परोसा जाता है; केवल *बाइट्स
कहाँ से आते हैं* भिन्न होता है, पाठक, क्वेरी इंजन, या
अखंडता जाँच कभी नहीं। हॉट पथ स्थितीय रीड (`read_exact_at`) है, जो
स्थानीय फ़ाइल पर `pread` और ऑब्जेक्ट स्टोर पर HTTP बाइट-रेंज अनुरोध पर मैप होता है
— Slater कभी mmap नहीं करता, इसलिए स्पष्ट, बाउंडेड-रीड मॉडल
हर जगह समान है।
**तीन प्रथम श्रेणी के बैकएंड**, `dataBackend.kind` द्वारा चुने जाते हैं। फ़ाइलसिस्टम
सरल डिफ़ॉल्ट है; **Amazon S3 और Google Cloud Storage समान, पूर्ण रूप से
समर्थित ऑब्जेक्ट-स्टोर बैकएंड हैं** — प्रकाशित इमेज दोनों के साथ संकलित आती है,
इसलिए प्रत्येक केवल कॉन्फ़िगरेशन-आधारित है, और एक बार बनाया गया जनरेशन
उनमें से किसी से भी परोसा जा सकता है (यहाँ तक कि `fs` → S3 → GCS में माइग्रेट किया गया) बिना पुनर्निर्माण के।
| `dataBackend.kind` | स्थितीय रीड | खोलने पर अखंडता | प्रमाण-पत्र |
| --- | --- | --- | --- |
| `fs` *(default)* | `pread` | प्रत्येक फ़ाइल का पूर्ण BLAKE3 पुनः-हैश | — |
| `s3` | HTTP `Range` GET | सर्वर **SHA-256** `HEAD` के माध्यम से (अनुपस्थित होने पर → BLAKE3 बॉडी पुनः-हैश) | कॉन्फ़िगरेशन कुंजियाँ, AWS श्रृंखला, या IAM भूमिका |
| `gcs` | HTTP रेंज रीड | सर्वर **CRC32C** `get_object` के माध्यम से (अनुपस्थित होने पर → BLAKE3 बॉडी पुनः-हैश) | ADC / Workload Identity, या service-account JSON |
दोनों ऑब्जेक्ट स्टोर अखंडता को **उस चेकसम से सत्यापित करते हैं जिसे स्टोर
पहले से गणना करके रखता है**, ऑब्जेक्ट मेटाडेटा के रूप में प्राप्त किया जाता है: `slater-build`
अपलोड पर चेकसम भेजता है (स्टोर उसके विरुद्ध बाइट्स को मान्य करता है और उसे संग्रहीत करता है), और
सर्वर इसे खोलने पर वापस पढ़ता है और मैनिफेस्ट से तुलना करता है — प्रति फ़ाइल एक मेटाडेटा
अनुरोध, कोई बॉडी डाउनलोड नहीं। यह सामग्री-ग्रेड है और आत्मा में समान है
S3 (SHA-256) और GCS (CRC32C) में। जब कोई ऑब्जेक्ट **कोई** सर्वर-संग्रहीत
चेकसम नहीं रखता (आउट-ऑफ-बैंड में कॉपी किया गया, या किसी भिन्न डिफ़ॉल्ट के साथ अपलोड किया गया), तो सर्वर
**ऑब्जेक्ट बॉडी को मैनिफेस्ट BLAKE3 के विरुद्ध पुनः-हैश** करता है, उसकी
बाइट लंबाई पर भरोसा करने के बजाय — एक अनुरोधित अखंडता जाँच को कभी भी चुपचाप आकार
तुलना में अधोगति नहीं किया जाता। Slater-प्रकाशित जनरेशन हमेशा चेकसम रखते हैं, इसलिए वे
सस्ते मेटाडेटा पथ पर बने रहते हैं।
यह कॉलम, प्रत्येक बैकएंड पर, यह जाँचता है कि फ़ाइलें **मैनिफेस्ट से मेल खाती हैं**।
मैनिफेस्ट पर स्वयं भरोसा किया जा सकता है या नहीं, यह एक अलग प्रश्न है, और यह
मास्टर कुंजी है जो इसका उत्तर देती है: कुंजी कॉन्फ़िगर होने पर मैनिफेस्ट एक कुंजीयुक्त MAC रखता है
जिसे सर्वर किसी भी फ़ील्ड पर भरोसा करने से पहले सत्यापित करता है (इन हैशों सहित), इसलिए
छेड़छाड़ की गई फ़ाइलों का वर्णन करने के लिए फिर से लिखा गया मैनिफेस्ट अस्वीकार कर दिया जाता है; बिना कुंजी के तुलना पूरे समय अकुंजीयुक्त रहती है, और
जो कोई डेटा निर्देशिका में लिख सकता है वह किसी फ़ाइल और मैनिफेस्ट को एक साथ
फिर से लिख सकता है। देखें
[प्रत्येक कॉन्फ़िगरेशन में अखंडता का अर्थ](https://github.com/hikari-systems/slater/blob/HEAD/THREAT_MODEL.md#what-integrity-means-in-each-configuration)।
जाँच को स्वयं `dataBackend.verifyIntegrity: false` के साथ बंद किया जा सकता है, जो
इसे तेज़ ओपन के लिए बदल देता है।
### फ़ाइलसिस्टम (`fs`)
डिफ़ॉल्ट, `dataBackend.fs.dir` पर आधारित। अधिकांश परिनियोजनों के लिए
सही विकल्प: स्थानीय SSD (या NFS/EBS माउंट) पर एक जनरेशन जो केवल-पढ़ने के लिए परोसा जाता है।
खोलने पर अखंडता हर फ़ाइल का पूर्ण BLAKE3 पुनः-हैश है।
### Amazon S3 (`s3`)
एक S3 या S3-संगत बकेट (AWS, MinIO, localstack)। प्रमाण-पत्र **सबसे पहले**
कॉन्फ़िगरेशन से आते हैं (`dataBackend.s3.awsAccessKey` / `awsSecretKey`, साथ ही
अस्थायी STS प्रमाण-पत्रों के लिए `awsSessionToken`) और मानक AWS
श्रृंखला (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` पर्यावरण चर, साझा प्रोफ़ाइल, या
instance/IRSA भूमिका) पर वापस गिर जाते हैं जब खाली छोड़ दिया जाता है।```sh
# serve from S3 (env-var form; see the config table for every key)
dataBackend__kind=s3
dataBackend__s3__bucket=slater
dataBackend__s3__region=eu-west-2
dataBackend__s3__awsAccessKey=… # omit to use the AWS chain / instance role
dataBackend__s3__awsSecretKey=…
# S3-compatible (e.g. MinIO): also set
dataBackend__s3__endpoint=http://minio:9000
dataBackend__s3__pathStyle=true # required by most S3-compatible servers
# publish a generation into the bucket (remote `current` pointer written last)
slater-build --input people.cypher --graph people --data-dir /data \
--publish-s3-bucket slater --publish-s3-region eu-west-2 --publish-s3-prefix prod
# MinIO: add --publish-s3-endpoint http://localhost:9000 --publish-s3-path-style
Google Cloud Storage (gcs)
एक GCS बकेट, JSON API के माध्यम से एक्सेस की जाती है। प्रमाणीकरण GCP-मूल (native) है: डिफ़ॉल्ट रूप से
यह एप्लिकेशन डिफ़ॉल्ट क्रेडेंशियल्स को हल करता है — GKE वर्कलोड आइडेंटिटी, GCE
मेटाडेटा सर्वर, या एक gcloud / GOOGLE_APPLICATION_CREDENTIALS कुंजी। सेट करें
dataBackend.gcs.credentialsPath (एक सेवा-खाता JSON कुंजी फ़ाइल) या इनलाइन
credentialsJson एक स्पष्ट कुंजी के लिए। dataBackend.gcs.endpoint एक
fake-gcs-server इम्यूलेटर को इंगित करता है, और dataBackend.gcs.anonymous=true सक्षम करता है
अप्रमाणित पहुंच केवल उस इम्यूलेटर के लिए — वास्तविक GCS के विरुद्ध कभी नहीं।```sh
serve from GCS (env-var form; see the config table for every key)
dataBackend__kind=gcs dataBackend__gcs__bucket=slater dataBackend__gcs__prefix=prod dataBackend__gcs__credentialsPath=/secrets/sa.json # omit for ADC / Workload Identity
```sh
# publish a generation into the bucket (remote `current` pointer written last)
slater-build --input people.cypher --graph people --data-dir /data \
--publish-gcs-bucket slater --publish-gcs-prefix prod
# explicit key: add --publish-gcs-credentials /secrets/sa.json
सभी मामलों में slater-build तैयार जनरेशन को पहले --data-dir में लिखता है (अपना लोकल स्टेजिंग क्षेत्र) और अतिरिक्त रूप से इसे बकेट में अपलोड करता है; रिमोट current पॉइंटर सबसे अंत में लिखा जाता है, इसलिए एक सर्विंग नोड कभी आधी-प्रकाशित जनरेशन नहीं देखता।
ऑब्जेक्ट स्टोर का उपयोग कब करें (S3 या GCS)
s3 या gcs तब चुनें जब आप जनरेशन को नोड की डिस्क के बजाय टिकाऊ, केंद्रीय ऑब्जेक्ट स्टोरेज में रखना चाहते हैं — आम तौर पर: एक बार प्रकाशित करें और कई स्टेटलेस, डिस्क-रहित सर्वर रेप्लिकाओं में फैलाएँ जो सभी एक ही बकेट पढ़ते हैं; बिल्ड होस्ट को सर्व होस्ट से अलग करें; या वॉल्यूम प्रबंधित करने के बजाय स्टोर की स्थायित्व/संस्करण/जीवनचक्र सुविधाओं पर निर्भर रहें। इसकी कीमत विलंबता है: एक कोल्ड ब्लॉक नेटवर्क राउंड-ट्रिप (~10–50 ms) है, स्थानीय रीड (~0.1 ms) के बजाय। Slater इसे अधिकांशतः इन-मेमोरी ब्लॉक कैश, समवर्ती रीड-अहेड, और नीचे दिए गए वैकल्पिक डिस्क कैश से छिपा लेता है। यदि आपके जनरेशन पहले से तेज़ स्थानीय स्टोरेज पर हैं और आपको केंद्रीय-बकेट मॉडल की आवश्यकता नहीं है, तो fs सरल और तेज़ है।
लोकल-डिस्क ब्लॉक कैश (ऑब्जेक्ट-स्टोर दूसरा स्तर)
इन-मेमोरी BlockCache जानबूझकर छोटा रखा गया है (बाउंडेड RSS मुख्य गारंटी है), इसलिए RAM से बड़े वर्किंग सेट पर वही ब्लॉक हर स्पिल पर ऑब्जेक्ट स्टोर से दोबारा लाए जाएँगे। एक वैकल्पिक लोकल-SSD दूसरा कैश स्तर इसका समाधान करता है: RAM से निकाला गया ब्लॉक नए ऑब्जेक्ट GET के बजाय लोकल डिस्क (~0.1 ms) से परोसा जाता है, जो इन-मेमोरी निष्कासन से बचता है और ऑब्जेक्ट-स्टोर अनुरोधों की संख्या/लागत घटाता है — जिससे ऑब्जेक्ट-स्टोर-बैक्ड नोड गर्म होने पर लोकल-फाइलसिस्टम प्रदर्शन के करीब पहुँच जाता है। यह s3 और gcs दोनों के लिए ऑप्ट-इन है, जो dataBackend.<s3|gcs>.diskCacheBytes > 0 और एक लिखने योग्य diskCacheDir सेट करने पर सक्षम होता है।
- यह सील्ड बाइट्स को बिल्कुल वैसे ही कैश करता है जैसे लाए गए हैं — पहले से संपीड़ित, और (
--encryptजनरेशन के लिए) अभी भी AEAD-सील्ड — डिक्रिप्ट/डीकंप्रेस के नीचे। कैश परत कभी एन्क्रिप्शन कुंजी नहीं रखती और फिर कभी एन्क्रिप्ट नहीं करती, इसलिए एट-रेस्ट स्थिति मुफ्त में सुरक्षित रहती है: एक एन्क्रिप्टेड जनरेशन डिस्क पर अभी भी सील्ड ही पहुँचता है। - लेखन राइट-बिहाइंड होते हैं: मिस होने पर प्राप्त बाइट्स तुरंत क्वेरी को लौटा दी जाती हैं, फिर एक बैकग्राउंड थ्रेड डिस्क लेखन और LRU ट्रिम करता है, जिससे क्वेरी पथ कभी डिस्क I/O पर ब्लॉक नहीं होता। निष्कासन कैश को उसके बाइट बजट के भीतर रखता है; हर रीड पर सत्यापित एक प्रति-फ़ाइल चेकसम एक भ्रष्ट कैश फ़ाइल को मिस में बदल देता है (→ ऑब्जेक्ट स्टोर से दोबारा लाना)।
diskCacheDirएक वास्तविक लिखने योग्य वॉल्यूम पर इंगित करना चाहिए — कभीtmpfsनहीं (tmpfs RAM होता है और बाउंडेड-RSS गारंटी को विफल कर देगा)। इसे ट्रैक करने वाला इन-मेमोरी इंडेक्स थोड़ी RAM लेता है (प्रति कैश किए गए ब्लॉक ~दसियों बाइट्स), जो आपकी RSS सीमा में गिना जाता है — डायरेक्टरी को इन-मेमोरी ब्लॉक कैश से ≫ बड़ा आकार दें।- इस स्तर की दूसरी RAM लागत राइट-बिहाइंड कतार है, जो ब्लॉकों को डिस्क तक पहुँचने के क्रम में स्टेज करती है। यह
blockCacheBytes / 8पर सीमित है (diskCacheBytesद्वारा फ़्लोर किया गया) — डिफ़ॉल्ट पर 8 MiB — और बढ़ने के बजाय त्यागता है, इसलिए ठंडा स्कैन इसे फुला नहीं सकता; एक त्यागा गया ब्लॉक अगली मिस पर बस फिर से लाया जाता है। इसे किसी कॉन्फ़िगरेशन की आवश्यकता नहीं है: यहblockCacheBytesके साथ स्केल होता है, इसलिए डिस्क स्तर अपने इंडेक्स के अलावा RSS बजट में कोई नया अंक नहीं जोड़ता।
माउंट्स
एक रीड रेप्लिका रीड-ओनली रूट फाइलसिस्टम और एक गैर-रूट उपयोगकर्ता (appuser:1000) के साथ चलती है — उसे जो कुछ भी चाहिए वह रीड-ओनली माउंट होता है। एक राइटर (delta.enabled) को अपने WAL के लिए अतिरिक्त रूप से एक टिकाऊ, लिखने योग्य वॉल्यूम की आवश्यकता होती है।
| Path | Purpose | Notes |
|---|---|---|
/data | ग्राफ़ जनरेशन (<graph>/<uuid>/… + current). | रेप्लिकाओं के लिए रीड-ओनली; slater-build द्वारा निर्मित। रिमोट/नेटवर्क स्टोरेज (जैसे NFS) पर हो सकता है, इसलिए रीड तेज़ लोकल-SSD विलंबता मानकर नहीं चलतीं। |
/sandbox | प्रति-पर्यावरण कॉन्फ़िग ओवरले + सीक्रेट्स। | /sandbox/config.json को बेक्ड-इन config.json के ऊपर डीप-मर्ज किया जाता है; इसमें acl.json, TLS PEM सामग्री, एट-रेस्ट कुंजी फ़ाइल भी रहती है। |
/tmp, /run | स्क्रैच (tmpfs). | एक रीड रेप्लिका डिफ़ॉल्ट रूप से कभी डिस्क पर नहीं लिखती। |
(writer) delta.walDir | राइट-अहेड लॉग + L0 डेल्टा सेगमेंट, जब delta.enabled हो। | लिखने योग्य, और एक टिकाऊ, वास्तविक वॉल्यूम — कभी tmpfs नहीं (यह स्थायित्व की निचली सीमा है)। एक सापेक्ष पथ डेटा डिर के अंतर्गत हल होता है; यहाँ राइटर को अपना स्वयं का पर्सिस्टेंट वॉल्यूम दें। |
| (optional) disk cache | लोकल-डिस्क ब्लॉक कैश, जब dataBackend.s3.diskCacheBytes / dataBackend.gcs.diskCacheBytes > 0 हो। | लिखने योग्य, और एक वास्तविक वॉल्यूम — tmpfs नहीं। s3 और gcs बैकएंड द्वारा उपयोग किया जाता है; देखें स्टोरेज बैकएंड. |
पर्यावरण / कॉन्फ़िगरेशन
कॉन्फ़िगरेशन हाउस-स्टैंडर्ड लेयर्ड लोडर द्वारा लोड होता है: बेक्ड-इन config.json, फिर उसके ऊपर /sandbox/config.json डीप-मर्ज, फिर KEY__sub पर्यावरण ओवरराइड्स (नेस्टिंग के लिए डबल अंडरस्कोर; कुंजियाँ camelCase कॉन्फ़िग से मेल खाती हैं)।
हर कॉन्फ़िगरेशन नॉब — उसकी camelCase कुंजी, KEY__sub पर्यावरण ओवरराइड, उसका डिफ़ॉल्ट, और वह क्या करता है — कॉन्फ़िगरेशन संदर्भ में तालिकाबद्ध है। सबसे अधिक ट्यून किए जाने वाले नॉब हैं कैश बजट (cache.*), क्वेरी गार्ड (query.*), कनेक्शन कैप (server.*), स्टोरेज बैकएंड (dataBackend.*), और राइटेबल लेयर (delta.*)।
रेजिडेंट मेमोरी blockCacheBytes + vectorCacheBytes + resultCacheBytes को बाउंडेड प्रति-एंट्री और एलोकेटर ओवरहेड के भीतर ट्रैक करती है — हर पूल अपनी सामग्री का भार खुद तौलता है (स्ट्रिंग और कंटेनर आवंटित क्षमता द्वारा) और बजट में बने रहने के लिए निष्कासित करता है, लेकिन प्रति-एंट्री बहीखाता और एलोकेटर का साइज़-क्लास राउंडिंग आपके सेट किए गए अंक के ऊपर बैठते हैं — साथ ही एक छोटा निश्चित ओवरहेड (और lazy डिग्री कॉलम के लिए degreeColumnBytes तक, जब डिग्री-सम count(endpoint) फास्ट पथ का उपयोग होता है)। यह ग्राफ़ आकार से स्वतंत्र है — यही मुख्य गारंटी है, जिसे rss_stays_bounded_under_sustained_knn_load इंटीग्रेशन टेस्ट द्वारा परखा जाता है, जो पीक-बनाम-वार्म RSS वृद्धि को जोड़े गए बजटों के भीतर अच्छी तरह बनाए रखता है। प्रति-कनेक्शन बफ़र कैश बजटों के बाहर रहते हैं, इसलिए गारंटी प्रतिकूल लोड के तहत केवल इसलिए कायम रहती है क्योंकि server.maxConnections सीमित करता है कि एक बार में कितने मौजूद हो सकते हैं।
नेटवर्क पोस्चर
Slater एक रीड रेप्लिका हैंडल है; कनेक्शन-सुरक्षा का प्राथमिक नियंत्रण नेटवर्क है, बाइनरी नहीं। इसे एक निजी इंटरफ़ेस से बाइंड करें, नेटवर्क लेयर पर स्रोत रेंज प्रतिबंधित करें (सिक्योरिटी ग्रुप / NetworkPolicy), और — यदि यह भरोसेमंद क्लाइंट्स के अलावा किसी और का सामना करता है — तो इसे कनेक्शन-सीमित करने वाले L4 प्रॉक्सी (HAProxy maxconn + प्रति-स्रोत stick-table, या nftables connlimit + hashlimit) के पीछे रखें। यह फ़ाइल डिस्क्रिप्टर को प्रोसेस को सौंपे जाने से पहले बैठता है, इसलिए यह सबसे मज़बूत सीमा है।
ऊपर दी गई इन-बाइनरी सीमाएँ (maxConnections, maxPreAuthConnections, maxConnectionsPerIp, डिफरेंशियल बाइट कैप, और loginTimeoutMs) डिफेंस-इन-डेप्थ हैं: वे डिफ़ॉल्ट रूप से चालू और उदार हैं ताकि वैध क्लाइंट आबादी के लिए अदृश्य रहें, लेकिन वे बाउंडेड-RSS गारंटी को तब भी कायम रखती हैं जब प्रॉक्सी भूल जाए। पूर्ण रक्षात्मक मुद्रा के लिए docs/HARDENING.md देखें, और विहित विवरण के लिए THREAT_MODEL.md / SECURITY_WORKLIST.md देखें।
जनरेशन गार्ड
Slater हर ग्राफ़ के current पॉइंटर को हर generationPollMs पर पोल करता है (पोल, inotify नहीं — डेटा डिर रिमोट/नेटवर्क स्टोरेज जैसे NFS हो सकती है, जहाँ फाइलसिस्टम परिवर्तन इवेंट अविश्वसनीय होते हैं)। जब यह बदलता है:
reloadStrategy=exit(डिफ़ॉल्ट): सर्वर फ़ैटल लॉग करता है और गैर-शून्य के साथ बाहर निकलता है ताकि ऑर्केस्ट्रेटर इसे नई जनरेशन के विरुद्ध सफाई से पुनः आरंभ करे।reloadStrategy=swap: सर्वर नई जनरेशन को खोलता है और मान्य करता है (बूट के समान कंटेंट-हैश गार्ड), उसे परमाणु रूप से स्वैप करता है, और चल रही क्वेरीज़ को पुरानी जनरेशन पर पूरा होने देता है। एक भ्रष्ट/अधूरी नई छवि अस्वीकार कर दी जाती है और पुरानी जनरेशन सेवा देती रहती है।
ACL
acl.json उपयोगकर्ताओं को argon2id पासवर्ड हैश और प्रति-ग्राफ़ read / write अनुदानों से मैप करता है। एक हैश बनाएँ (कभी भी क्लियरटेक्स्ट न रखें) इसके साथ:```sh
slater hash-password 's3cret' # prints a $argon2id$… string for acl.json
एक प्रारंभिक `acl.json` रिपॉज़िटरी रूट में शामिल है; इसकी संरचना इस प्रकार है:```json
{
"users": {
"reporting": {
"passwordArgon2id": "$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>",
"grants": {
"people": ["read"],
"products": ["read", "write"]
}
}
}
}
-
users— प्रति लॉगिन एक प्रविष्टि, उपयोगकर्ता नाम द्वारा कुंजीबद्ध। -
passwordArgon2id—slater hash-passwordसे प्राप्त$argon2id$…स्ट्रिंग (कभी भी स्पष्ट पाठ में नहीं; फ़ाइल स्वयं सादा JSON है और साझा स्टोरेज पर रहती है)। -
grants— प्रति-ग्राफ क्षमता सूचियाँ। दो अनुमतियाँ अर्थपूर्ण हैं:read— ग्राफ़ को क्वेरी करें। जो ग्राफ़ किसी उपयोगकर्ता के grants में अनुपस्थित है, वह उसके लिए अदृश्य है।write— राइटेबल लेयर (delta.enabled) के माध्यम से ग्राफ़ को परिवर्तित करें:MERGE/SET/DELETEस्टेटमेंट्स औरCALL slater.consolidate()।
वे स्वतंत्र हैं: एक
readअनुदान कोई लेखन पहुँच प्रदान नहीं करता। इसलिए राइटेबल लेयर चालू करने से आपके मौजूदा पाठक लेखक नहीं बन सकते। एक लेखक को दोनों की आवश्यकता होती है —["read", "write"]— क्योंकि लिखने के लिए व्यावसायिक कुंजी हल करना एक पठन है। अपरिचित अनुमति स्ट्रिंग्स को अनदेखा किया जाता है (वे कुछ भी प्रदान नहीं करतीं)।
इसे aclPath द्वारा नामित पथ पर केवल-पठनीय माउंट करें (डिफ़ॉल्ट /config/acl.json)।
सर्वर इसे प्रत्येक जनरेशन हॉट-स्वैप पर पुनः लोड करता है, और संग्रहीत ACL स्टैम्प की
पुनः जाँच प्रत्येक पुनः लोड पर की जाती है (देखें requireAclStamp)।
स्वास्थ्य जाँच
slater बाइनरी अपनी स्वयं की लाइवनेस प्रोब के रूप में भी कार्य करती है: slater healthcheck [host] [port] सर्वर के विरुद्ध एक Bolt हैंडशेक (कोई HTTP अनुरोध नहीं) करता है और
यदि यह एक प्रोटोकॉल संस्करण पर सहमति बनाता है तो 0 से बाहर निकलता है, अन्यथा 1 से — डिफ़ॉल्ट रूप से
localhost और कॉन्फ़िगर किए गए Bolt पोर्ट का उपयोग करते हुए। यह वही है जो कंटेनर
HEALTHCHECK चलाता है, ताकि ऑर्केस्ट्रेटर एक वास्तविक Bolt-तैयार सर्वर देखें, न कि केवल एक
खुला सॉकेट:```sh
slater healthcheck localhost 7687 # exit 0 = healthy
docker exec slater /app/slater healthcheck # inside the container
## एक-शॉट क्वेरी
स्क्रिप्टिंग, CI जाँच और त्वरित लुकअप के लिए, `slater query` ग्राफ़ की वर्तमान जनरेशन को माउंट करता है, एक ही रीड-ओनली Cypher क्वेरी को इन-प्रोसेस चलाता है, परिणाम को JSON ऑब्जेक्ट के रूप में प्रिंट करता है और बाहर निकल जाता है — कोई सर्वर नहीं, कोई Bolt कनेक्शन नहीं। यह सर्वर के समान कॉन्फ़िग का सम्मान करता है (स्टोरेज बैकएंड, एन्क्रिप्शन कुंजी, क्वेरी बजट):```sh
# GRAPH defaults to `defaultGraph`. Without -q, normal datestamped logging
# (config, "opened generation", …) is written to stdout alongside the result.
slater query mygraph 'MATCH (n) RETURN count(n) AS c'
# -q/--quiet ⇒ logging suppressed, so stdout is *only* the compact result JSON
slater query mygraph -q 'MATCH (c:Company) RETURN c.ticker AS t LIMIT 3' | jq
# {"columns":["t"],"rows":[["AUPH"],["KYMR"],["MREO"]]}
नोड्स और रिलेशनशिप्स अपने लेबल/प्रकार और गुणों तक विस्तारित होते हैं। -q का उपयोग करें
जब आप मशीन-पार्स करने योग्य आउटपुट चाहते हैं (परिणाम JSON ही
stdout पर एकमात्र चीज़ है); लॉग्स के साथ ऑपरेटर-उन्मुख रन के लिए इसे छोड़ दें। -q के बिना एक
केवल-मेट्रिक्स सारांश प्रत्येक रन के बाद लॉग किया जाता है — उदा.```text
INFO query executed cost=2389 resultCount=10 execMs=441 limitRowCount=10
carrying the query `cost` (चार्ज किए गए तत्व), `resultCount`, `execMs`, और
`limitRowCount` (केवल जब क्वेरी `LIMIT` निर्दिष्ट करती है) — कभी भी क्वेरी टेक्स्ट
या कोई परिणाम मान नहीं। सफलता पर एग्ज़िट स्थिति `0` है, पार्स/ओपन/एक्ज़ीक्यूट
त्रुटि पर `1` (stderr पर संदेश)।
## एक ग्राफ़ निर्यात करें (`slater dump`)
`slater dump` एक **चालू** सर्वर से ग्राफ़ को बिज़नेस-कुंजी `MERGE`
Cypher के रूप में निर्यात करता है — वही डायलेक्ट जिसे `slater-build` ग्रहण करता है — ताकि एक ग्राफ़ राउंड-ट्रिप हो
(dump → `slater-build` → नई पीढ़ी) माइग्रेशन या टेक्स्ट बैकअप के लिए। `slater query` के विपरीत,
यह **Bolt** पर कनेक्ट होता है, प्रमाणित करता है, और प्रति-ग्राफ़
ACLs का सम्मान करता है, इसलिए इसे सर्वर पर डिस्क एक्सेस की आवश्यकता नहीं होती। पासवर्ड
`SLATER_DUMP_PASSWORD` या stdin से पढ़ा जाता है (कभी फ़्लैग से नहीं, इसे `ps`/हिस्ट्री से बाहर रखते हुए)।```sh
# List the graphs the authenticated user may read.
SLATER_DUMP_PASSWORD=pw slater dump --list -u reporting
# Dump a graph to a file (identity keys inferred from range indexes).
SLATER_DUMP_PASSWORD=pw slater dump people -u reporting -o people.cypher
# Rebuild it into a fresh generation.
slater-build --input people.cypher --graph people --data-dir ./data
प्रत्येक लेबल की पहचान कुंजी वह प्रॉपर्टी है जो उसकी रेंज इंडेक्स द्वारा वहन की जाती है; --key Label=prop (दोहराने योग्य) या वैश्विक --pk <field> के साथ ओवरराइड करें। CREATE INDEX DDL पहले उत्सर्जित किया जाता है ताकि रीबिल्ड इंडेक्सों को पुनः निर्मित कर सके। एक मल्टी-लेबल नोड हर लेबल रखता है — उसे MERGE (n:Ident:Other {key: v}) के रूप में उत्सर्जित किया जाता है, जिसमें पहचान लेबल (बिज़नेस कुंजी प्रदान करने वाला) पहले और शेष क्रमबद्ध होते हैं; मर्ज केवल पहचान लेबल पर कुंजीबद्ध होता है, इसलिए अनुगामी लेबल बिना कोई अन्य नोड बनाए नोड पर लिख दिए जाते हैं। विशेष वर्णों वाले लेबल, रिलेशनशिप प्रकार और प्रॉपर्टी कुंजियों को उत्सर्जन पर बैकटिक में उद्धृत किए जाते हैं, जिससे असामान्य नाम विश्वसनीय रूप से राउंड-ट्रिप होते हैं और रीबिल्ड में Cypher इंजेक्ट नहीं कर सकते। वेक्टर (और अन्य मान जिनमें Cypher-लिटरल वर्तनी नहीं होती) MERGE डंप में शामिल नहीं किए जा सकते और stderr पर चेतावनी के साथ छोड़ दिए जाते हैं। सफलता पर निकास स्थिति 0 है, त्रुटि पर 1।
हल किया गया उदाहरण
एक सम्पूर्ण, चलाने योग्य वॉकथ्रू — ग्राफ बनाना, उसे सर्व करना, neo4j के JavaScript और Python ड्राइवरों से कनेक्ट करना, और उसमें डेटा लिखना — मैनुअल के त्वरित आरंभ और डेटा लिखना पृष्ठों पर मौजूद है, जिसमें docs/manual/examples/ में उपलब्ध बंडल नमूना ग्राफ का उपयोग किया गया है।
विकास```sh
export PATH="$HOME/.cargo/bin:$PATH" cargo build cargo test # unit + the bounded-RSS headline integration test cargo clippy --all-targets -- -D warnings cargo fmt --all -- --check
### ऑब्जेक्ट-स्टोर बैकएंड वैकल्पिक कार्गो सुविधाएँ हैं
एक साधारण `cargo build` एक **केवल-फाइलसिस्टम** बाइनरी बनाता है — `s3` और `gcs`
बैकएंड कार्गो सुविधाओं के पीछे गेटेड हैं ताकि डिफ़ॉल्ट बिल्ड छोटा रहे (कोई AWS
या Google SDK नहीं, कोई async रनटाइम नहीं)। आपको जिसकी आवश्यकता हो उसे **दोनों** `slater`
(सर्व) और `slater-build` (प्रकाशित) पर सक्षम करें:```sh
# S3 only / GCS only / both
cargo build -p slater -p slater-build --features s3
cargo build -p slater -p slater-build --features gcs
cargo build -p slater -p slater-build --features s3,gcs
प्रत्येक crate समान s3 / gcs features उजागर करता है जो graph-format/{s3,gcs} की ओर अग्रेषित होते हैं। रनटाइम पर किसी बैकएंड का अनुरोध करना (dataBackend.kind=s3|gcs, या slater-build --publish-{s3,gcs}-*) बिना उसके feature के संकलित हुए, स्पष्ट "built without the … feature" त्रुटि के साथ तुरंत विफल हो जाता है। प्रकाशित Docker image दोनों को सक्षम करती है (Dockerfile CARGO_FEATURES), इसलिए पूर्व-निर्मित images को अतिरिक्त फ़्लैग की आवश्यकता नहीं होती — यह केवल स्रोत से निर्माण करते समय मायने रखता है। एकीकरण परीक्षण भी इसी प्रकार गेटेड हैं: --features s3 --test s3_minio, --features gcs --test gcs_emulator (एक fake-gcs-server), और --features gcs --test gcs_real (ADC के माध्यम से वास्तविक GCS); प्रत्येक तब तक स्किप होता है जब तक उसके SLATER_* env vars सेट न हों।
डिज़ाइन, माइलस्टोन लेजर और निर्णय लॉग के लिए docs/PLAN.md, docs/PROGRESS.md और docs/DECISIONS.md देखें।
प्रदर्शन
अधिकतम छह इंजन, एक single-client सुइट, 62k-नोड वाले toy ग्राफ़ से लेकर Wikidata 91.6M नोड्स / 1.5B edges तक के ग्राफ़। प्रत्येक इंजन को अलगाव में मापा जाता है (बाकी सभी कंटेनर रोक दिए जाते हैं — RSS और विलंबता उसकी अपनी footprint होती है)। नीचे दी गई विलंबता तालिकाएँ Slater 0.21.0 पर पुनः मापी गईं (writeable build): छोटे/मध्यम ग्राफ़ (MeSH, EU-AI-Act) ताज़ा मापे गए, और 91.6M ग्राफ़ एक नए same-box, shared-anchor slater-बनाम-Neo4j पास के रूप में (वह तालिका देखें)। resident-memory आंकड़े पिछले पास से ही लिए गए हैं (कंटेनर cgroup के माध्यम से मापा गया; writable layer के idle रहने पर read path byte-identical है)। अन्य इंजनों के आंकड़े स्थापित क्रॉस-इंजन रन हैं (उनके संस्करण/प्रदर्शन अपरिवर्तित हैं)। सभी आंकड़े माध्यिकाएँ (ms) या पीक resident memory (MiB) हैं। हर जगह कम बेहतर है; bold = पंक्ति में सर्वश्रेष्ठ। slater को उसके local-filesystem (fs) backend पर चलाया गया; S3 और GCS बैकएंड स्थानीय-रीड विलंबता को object-store round-trips के बदले में बदल देते हैं (in-memory caches और वैकल्पिक local-disk cache tier द्वारा कम किया गया), इसलिए ये आंकड़े इंजन की विशेषता बताते हैं, न कि network-storage deployment की।
| engine | श्रेणी | मेमोरी सीमा |
|---|---|---|
| slater | disk-backed, paged | query.maxIntermediate कार्यशील सेट को स्वचालित रूप से सीमित करता है |
| Neo4j 5 | disk-backed, JVM | ~2 GiB heap + off-heap, क्वेरी की परवाह किए बिना committed |
| Memgraph · FalkorDB | in-memory | पूरा ग्राफ़ RAM में resident |
| ArcadeDB | in-memory, JVM | पूरा ग्राफ़ resident; सबसे भारी |
| LadybugDB | embedded, columnar | मैनुअल buffer pool जो क्वेरी से बड़ा होना चाहिए |
जो तीन इंजन डिस्क से पेजिंग करते हैं — slater, Neo4j 5 और LadybugDB — सभी पाँचों ग्राफ़ लोड करते हैं। in-memory तिकड़ी (Memgraph · FalkorDB · ArcadeDB) 1.5B edge वाले ग्राफ़ को बिल्कुल भी धारण नहीं कर सकती (इसे ~64–128 GiB resident चाहिए), और ArcadeDB का importer भी इसे पूरा नहीं कर सकता।
Resident memory (MiB) — ग्राफ़ के ~1,500× बढ़ने पर सीमित
प्रत्येक आंकड़ा committed working memory है — जिसे OS reclaim नहीं कर सकता। slater को छोड़कर हर इंजन अपने ग्राफ़ को committed anonymous memory में रखता है (अपना heap, Neo4j का off-heap page cache, या एक buffer pool), इसलिए उसकी पीक RSS ही उसकी committed footprint है। केवल slater अपने on-disk store के reclaimable OS page cache से सेवा करता है, इसलिए उसका आंकड़ा anon working set है; store का page cache (दबाव में evictable — slater सेवा देता रहता है) बाहर रखा गया है, और 91.6M ग्राफ़ के लिए कोष्ठकों में कुल के रूप में दिखाया गया है। Bold = सबसे कम।
| ग्राफ़ (नोड्स / edges) | slater | Neo4j 5 | Memgraph | FalkorDB | ArcadeDB | LadybugDB |
|---|---|---|---|---|---|---|
| pole — 62k / 106k | 11 | 746 | 114 | 140 | 1,556 | 198 |
| MeSH — 341k / 469k | 63 | 1,083 | 358 | 455 | 1,631 | 121 |
| EU-AI-Act — 21k / 45k (+55 MiB vec) | 99 | 729 | 229 | 312 | 1,948 | 286 |
| Wikidata — 91.6M / 1.5B | 584 (4,595 कुल) | ~2,900 | लोड नहीं हो सकता | लोड नहीं हो सकता | लोड नहीं हो सकता | ~652 † |
slater हर पैमाने पर सबसे कम है और ~50× बढ़ता है जबकि ग्राफ़ ~1,500× बढ़ता है — इसकी footprint क्वेरी working set को ट्रैक करती है, ग्राफ़ को नहीं (पूरे समय idle ~16–71 MiB)। in-memory तिकड़ी ~रैखिक रूप से बढ़ती है और 1.5B ग्राफ़ लोड नहीं कर सकती; Neo4j क्वेरी की परवाह किए बिना ~2 GiB heap commit करता है। († LadybugDB केवल bounded shapes पर — 1.5B edges पर इसकी hub / var-length / shortestPath traversals को अपने read pool को ≥2 GiB तक बढ़ाने की आवश्यकता होती है, बनाम slater की स्वचालित maxIntermediate कैप।) build-time value→count हिस्टोग्राम नगण्य resident memory जोड़ते हैं — कम-cardinality वाले indexed column के लिए कुछ KB, और Wikidata जैसे unique-key ग्राफ़ के लिए zero (wikidata_id हिस्टोग्राम cardinality cap से अधिक है, इसलिए कुछ भी संग्रहीत नहीं होता) — इसलिए ये आंकड़े उस feature से अपरिवर्तित हैं।
विलंबता (median ms) — ग्राफ़ RAM में समाता है (MeSH, 341k / 469k)
| क्वेरी रूप | slater | Neo4j 5 | Memgraph | FalkorDB | ArcadeDB | LadybugDB |
|---|---|---|---|---|---|---|
| count(*) सभी नोड्स | 0.41 | 15.0 | 23.8 | 16.4 | 82.0 | 2.2 |
| लेबल गणना | 0.42 | 4.2 | 20.7 | 1.1 | 4.4 | 4.3 |
| indexed point lookup | 0.43 | 3.9 | 0.48 | 0.48 | 0.65 | 8.8 |
| idx-eq गणना | 0.42 | 4.9 | 5.0 | 2.0 | 381 | 2.5 |
| 1-hop (indexed anchor) | 1.28 | 5.8 | 1.21 | 4.1 | 390 | 4.9 |
| 2-hop (बिना anchor) | 1.40 | 5.6 | 8.5 | 16.7 | 444 | 6.4 |
| group-by / count(DISTINCT) | 0.45 | 47–51 | 63–64 | 31–39 | 411 | 5.3 |
full-scan CONTAINS | 0.43 | 5.4 | 24.1 | 1.7 | 16.3 | 4.1 |
slater के पास metadata / index / scan रूप हैं (count, label, idx-eq, scan — ~0.4 ms, सेवा इंजनों से 10–200×), indexed point lookup (0.43 ms, अब in-memory जोड़ी के 0.48 ms से आगे), unanchored multi-hop (relationship-type scan के माध्यम से 2-hop 1.40 ms, क्षेत्र में सबसे तेज़), और — indexed grouping key पर build-time value→count हिस्टोग्राम के माध्यम से — whole-label group-by / count(DISTINCT) (0.45 ms, LadybugDB के columnar 5.3 ms से आगे)। in-memory सर्वर केवल raw 1-hop रखते हैं (Memgraph 1.21 ms बनाम slater का 1.28 ms)। (pole 62k/106k समान दिखता है: count/scan पर slater अकेला सबसे तेज़ ~0.4 ms, hops पर ~1.3–2.6 ms।)
विलंबता (median ms) — वेक्टर (EU-AI-Act kNN, 15k × 1024-dim)
| क्वेरी रूप | slater | Neo4j 5 | Memgraph | FalkorDB | LadybugDB |
|---|---|---|---|---|---|
| kNN top-10 Concept | 2.9 | 8.6 | 1.9 | 1.2 | 2.8 |
| kNN top-10 Chunk | 2.4 | 5.7 | 1.9 | 1.5 | 3.2 |
slater kNN का उत्तर exact brute-force स्कैन से देता है (ये सेट इसकी 50k-वेक्टर ANN सीमा से नीचे हैं) जबकि अन्य अनुमानित resident HNSW का उपयोग करते हैं — इसलिए slater के परिणाम exact हैं (recall 1.0)। एक SIMD दूरी कर्नेल + एक resident, pre-normalised वेक्टर मैट्रिक्स ने Concept को ~23 → ~2.9 ms और Chunk को ~10 → ~2.4 ms पर ला दिया, इसलिए slater अब Neo4j और LadybugDB को हराता है और Memgraph के ~1.4× के भीतर है, केवल FalkorDB से पीछे — जबकि exact।
Vector write ladder — बिना rebuild के insert / update / delete
उपरोक्त तालिकाएँ क्रॉस-इंजन read तुलनाएँ हैं। वेक्टर write पथ (स्थिर Vamana बेस के ऊपर FreshDiskANN-शैली write ladder) का कोई क्रॉस-इंजन समकक्ष नहीं है — यहाँ कोई अन्य इंजन disk-native, writable ANN नहीं करता — इसलिए नीचे के आंकड़े एक सिंथेटिक, embedding-जैसे fixture (एक low-rank manifold, dim 768, असमान norms) पर single-engine component बेंचमार्क हैं, जो crates/slater/benches/ के अंतर्गत committed हैं और पूरी तरह — कार्यप्रणाली और हर caveat सहित — docs/PERF-REPORT.md में लिखे गए हैं। Recall हमेशा live set पर exact brute force के विरुद्ध मापा जाता है, कभी एक index दूसरे के विरुद्ध नहीं। यहाँ पैमाना प्रतिनिधिक है और केवल वहीं extrapolate किया गया है जहाँ मीट्रिक size-linear है।
| गुण | मापा गया | यह क्यों मायने रखता है |
|---|---|---|
| KNN विलंबता बनाम लंबित writes | RW-index ~1.5–2 ms, flat 50k लंबित तक; प्री-इंडेक्स brute-forced ओवरले 1.9 → 115 ms (delta में रैखिक) — 50k पर 61× | क्वेरी विलंबता नहीं बिगड़ती जब consolidations के बीच writes जमा होते हैं |
| Embedding insert | ~1.5–2 ms प्रति वेक्टर live index में | एक write तुरंत KNN-visible होता है; delta-rebuild बजट ≈ 2 ms × delta cap है |
| Delete IO at iso-recall | 67 % डिलीट पर प्रति क्वेरी 2.9× कम node-fetches, 80 % पर 5.2× (recall ≥ 0.90) | एक consolidated ग्राफ़ डिलीट किए गए वेक्टरों के लिए कोई read tax नहीं चुकाता |
| Consolidation, pure permutation | O(1) — .vamana hard-linked byte-identical है, केवल id column फिर से लिखा जाता है | वेक्टर writes को base में समाहित करने से O(N·R·L) rebuild छूट जाता है |
| पूरी ladder में Recall | cosine, L2 और dot के लिए consolidated ≥ base | write ladder हर rung पर recall को संरक्षित रखता है |
एक आंकड़ा जो समर्पित perf बॉक्स का हक़दार है वह slow-path consolidation rewrite throughput है — जब एक consolidation शुद्ध permutation के बजाय deletes या नए वेक्टर ले जाता है, तो यह single-thread zstd और local disk से बंधा एक sequential recompress होता है, इसलिए निरपेक्ष MiB/s environment-विशिष्ट है (रिपोर्ट आकार दिखाती है और पर्यावरणीय सीमा समझाती है)।
विलंबता (median ms) — ग्राफ़ ≫ RAM (Wikidata 91.6M / 1.5B)
in-memory इंजन (Memgraph / FalkorDB / ArcadeDB) इस ग्राफ़ को बिल्कुल भी लोड नहीं कर सकते (~64–128 GiB resident)। केवल slater और Neo4j 5 ही करते हैं। यह एक fresh same-box, same-day पास है जो एक shared, fixed anchor set के विरुद्ध है — हर क्वेरी दोनों इंजनों पर समान नोड्स को हिट करती है, इसलिए head-to-head तुलना apples-to-apples है (मध्यम-degree anchors का एक सामान्य wikidata_id पूल; यह क्यों मायने रखता है, इसके लिए नीचे दिया नोट देखें)। slater दोनों fanouts पर दिखाया गया है (query.maxFanout 1 = throughput डिफ़ॉल्ट, 8 = latency डायल जो cold block reads को ओवरलैप करता है)। Bold = पंक्ति में सर्वश्रेष्ठ।
| क्वेरी रूप | slater (fan 1) | slater (fan 8) | Neo4j 5 |
|---|---|---|---|
| count(*) सभी नोड्स | 0.41 | 0.41 | 3606 |
| point lookup (indexed) | 0.72 | 0.49 | 6.3 |
| degree (1-hop गणना) | 0.43 | 0.44 | 6.0 |
| 1-hop पड़ोसी | 9.8 | 4.5 | 10.1 |
| 2-hop | 37 | 23 | 34.5 |
| 3-hop | 32 | 25 | 74 |
var-length *1..2 distinct | 985 | 1056 | 47 |
ईमानदार तस्वीर: slater metadata / index रूपों पर हावी है — count(*) metadata-सेवित है (0.41 ms बनाम Neo4j का 3.6 s डिस्क स्कैन, ~8800×), और point-lookup / degree / 3-hop ~2–10× तेज़ चलते हैं — 1–2-hop पर Neo4j के बराबर है (fanout 8 cold reads पर आगे निकल जाता है), लेकिन var-length *1..2 distinct में निर्णायक रूप से हारता है (≈1 s बनाम Neo4j का 47 ms): slater का variable-length distinct विस्तार यहाँ काफी धीमा है, यह एक वास्तविक कमज़ोरी है जो अपनी अलग जाँच का हक़दार है। यह सब कुछ सौ MB RSS पर, बनाम Neo4j का committed ~2 GiB heap।
Anchors के बारे में। ये traversal संख्याएँ बहुत हद तक इस पर निर्भर करती हैं कि आप किन नोड्स से शुरू करते हैं — एक Wikidata mega-hub ("human", "country") से एक लिंक दूर स्थित नोड का 2-hop पड़ोस लाखों नोड्स का होता है, इसलिए var-length/hop लागत anchor चुनाव के साथ परिमाण के क्रमों में बदल जाती है। इस तालिका के पिछले संस्करण ने प्रत्येक इंजन के अपने "first N by scan" का नमूना लिया था, जो न तो स्थिर है और न ही तुलनीय; यह पास दोनों इंजनों के लिए एक single shared, degree-bounded anchor set निर्धारित करता है। (shortestPath इस पास से हटा दिया गया है — दो मनमाने anchors के बीच यह path-existence पर निर्भर है और सार्थक median के लिए बहुत अधिक variance वाला है।)
Multi-hop count(*) — मेमोरी result size से अलग
बिना cap वाला multi-hop RETURN count(*) मिलान की गई पंक्तियों को materialise करने के बजाय विस्तार के दौरान गिनता है। 91.6M ग्राफ़ पर समान hub anchors, maxIntermediate=20M:
| 3-hop count(*) @ 91.6M | fanout=1 | fanout=8 |
|---|---|---|
| विलंबता / पीक working set | 554 ms / 0.66 GiB | 298 ms / 1.9 GiB |
count O(1) पंक्तियाँ रखता है। चार्जिंग अपरिवर्तित है, इसलिए एक mega-hub count अब भी compute (adjacency reads) पर maxIntermediate को ट्रिप करता है, पहले की तरह bounded।
प्रति-क्वेरी समानांतरता (maxFanout)
query.maxFanout बढ़ाने से किसी क्वेरी के cold, I/O-bound block reads को कोरों में ओवरलैप किया जाता है — यह बड़े-cold-working-set disk-bound रूपों की मदद करता है और warm रूपों पर सपाट रहता है। 1.5B ग्राफ़ पर: shortestPath ≤6 918 → 608 ms (1.5×, सबसे बड़ी खोज 6,269 → 2,350 ms, 2.7×); 3-hop count 547 → 298 ms। maxFanout=1 डिफ़ॉल्ट है (throughput-उन्मुख); 8 latency डायल है, अधिक क्षणिक worker memory पर।
slater कहाँ जीतता / पीछे रहता है
| आयाम | slater | क्षेत्र का सर्वश्रेष्ठ | निर्णय |
|---|---|---|---|
| resident memory, किसी भी पैमाने पर | 11–584 MiB (62k → 91.6M) | in-memory 1.5–2.7 GiB; 1.5B लोड नहीं कर सकते | slater |
| count / metadata / scan | ~0.4 ms | सेवा इंजन 5–80 ms | slater (10–200×) |
| indexed point lookup | 0.43 ms (MeSH) | Memgraph · FalkorDB 0.48 ms | slater (in-memory जोड़ी से आगे) |
| unanchored multi-hop (rows) | 1.40 ms (MeSH 2-hop) | Neo4j 5.6 ms | slater (relationship-type scan) |
| aggregation (group-by / DISTINCT) | 0.45 ms | LadybugDB 5 ms (columnar) | slater (build-time histogram) |
| kNN | 2.4–2.9 ms (exact) | FalkorDB 1.2 ms (HNSW) | Neo4j/Ladybug को हराता है; Memgraph से ~1.4× पीछे; exact |
| 91.6M metadata / point / degree / 3-hop | 0.4–32 ms | Neo4j 6–3,600 ms | slater (2–8800×) |
| 91.6M 1–2-hop | 4.5–23 ms (fan 8) | Neo4j 10–35 ms | ~बराबर |
91.6M var-length *1..2 distinct | ~1 s | Neo4j 47 ms | Neo4j (slater की वास्तविक कमज़ोरी) |
पैमाने पर multi-hop count(*) | 0.3–0.6 GiB | in-memory इंजन row set को materialise करते हैं | slater, bounded |
पूर्ण per-engine तालिकाएँ (pole, MeSH, EU-AI-Act + blockCacheBytes RAM↔विलंबता डायल, Wikidata 1M और 91.6M) perf/cross-engine-hs/README.md में हैं; नया slater-only पास (दोनों fanouts, हर dataset) perf/PERF_CURRENT_STATUS.md में है।
समवर्तीता और brown-out (load testing)
उपरोक्त बेंचमार्क single-client हैं। पूरक अक्ष — कई समवर्ती clients के अंतर्गत व्यवहार — का अपना harness है, perf/loadtest/: Bolt पर एक Locust ड्राइवर और एक coordinator जो load बढ़ाता है, CALL slater.diagnostics() पढ़ता है, capacity knee ढूँढता है, और limiter का नाम बताता है (पूरी विधि docs/LOAD-TESTING.md में)। Wikidata-1M ग्राफ़ पर 256 MiB-cache रन की मुख्य बातें (एक 16-core बॉक्स):
| परिणाम | माप |
|---|---|
| 1000 समवर्ती clients, शून्य विफलताएँ तक थामे रखता है | throughput ~2.5k rps पर चरम पर; latency knee लगभग 750 clients पर शुरू होता है (p99 51 → 750 ms) — core contention के तहत queueing, कोई hard cap नहीं (single-run, WSL2) |
| Block cache bounded और प्रभावी | 100% hit rate, 0 evictions, cache-fitting working set के लिए 50 MB resident |
| सतत load के तहत RSS नियंत्रित | jemalloc allocator 100→500-client wiki_cache_churn ramp के दौरान RSS को ~0.6 GB पर रखता है — cache-bound और स्थिर, बिना किसी MALLOC_* tuning के (पूर्व MALLOC_ARENA_MAX=2 + trim threshold हटा दिया गया है); इसका background purge भी post-burst high-water को pinned छोड़ने के बजाय वापस कर देता है |
| समग्र मेमोरी bounded | server-wide query.maxIntermediateGlobal + adjacency-charged expansion 1000 clients पर wiki_budget 2-hop बाढ़ को OOM के बिना रोकते हैं (RSS ~0.6 GB; guard ~60% hub queries को retryable budget errors के रूप में गिरा देता है) |
Load test द्वारा उजागर की गई दोनों मेमोरी समस्याएँ अब बंद हो चुकी हैं; सभी load-testing doc में ट्रैक की गई हैं।
लाइसेंस
Apache License, Version 2.0 के तहत लाइसेंस प्राप्त। पूरा पाठ LICENSE में और attribution के लिए NOTICE देखें। जब तक आप स्पष्ट रूप से अन्यथा न कहें, इस कार्य में शामिल करने के लिए जानबूझकर प्रस्तुत किया गया कोई भी योगदान, जैसा कि Apache 2.0 लाइसेंस में परिभाषित है, बिना किसी अतिरिक्त शर्तों या नियमों के उपरोक्त अनुसार लाइसेंस प्राप्त होगा।
SPDX-License-Identifier: Apache-2.0