
قاعدة بيانات رسومية منخفضة استهلاك الذاكرة مع دعم Bolt+TLS، وتشفير عند التخزين، ومتجهات مصممة لحالات استخدام النسخ المتماثل المحلي للرسوم البيانية.
الإصدار الحالي: v0.25.2 — جميع الإصدارات.
في سطر واحد: يقدّم Slater رسومًا بيانية لا تتسع في الذاكرة — مئات الملايين من العقد ومليارات الحواف في بضع مئات من ميغابايتات RAM — عبر بروتوكول Bolt القياسي، بحيث يعمل أي برنامج تشغيل neo4j دون تعديل، مع بحث متجهي أصلي على القرص بجوار الرسم البياني، ويقبل كتابات حية ودائمة دون التخلي عن ذلك. يتم تحديد الذاكرة المقيمة بميزانية ذاكرة تخزين مؤقت تختارها أنت، وليس بحجم الرسم البياني.
اختصارات
قاعدة بيانات الرسوم البيانية تخزّن البيانات كـأشياء (عقد) والعلاقات بينها (حواف)، مع اعتبار العلاقات مواطنين من الدرجة الأولى. هذا ما تريده عندما تكون أسئلتك حول الاتصالات بدلاً من الصفوف — "من يقع ضمن ثلاث قفزات من هذا الحساب؟"، "ما هي سلسلة التبعيات الكاملة وراء هذا البناء؟"، "أي الحسابات تشترك في جهاز وعنوان وبطاقة؟" — الاستعلامات التي تتحول إلى مستنقع من الانضمامات العودية في SQL لكنها تخرج بشكل طبيعي في رسم بياني.
الشكوى الأكثر شيوعًا حول قواعد بيانات الرسوم البيانية هي أنها لا تتوسع إلى ما يتجاوز ما يمكنك الاحتفاظ به في RAM. العديد منها (مثل neo4j وMemgraph وFalkorDB وغيرها) يُبقي الرسم البياني كاملاً مقيمًا: رسم بياني بحجم 40 GB يتطلب 40 GB من الذاكرة — لكل مثيل. تريد نسخة متماثلة لكل منطقة أو مستأجر أو pod؟ اضرب الفاتورة. وبعد حجم معين لن يتم تحميلها ببساطة: على سبيل المثال، رسم Wikidata البياني الذي يضم 90 مليون عقدة / 1.5 مليار حافة يحتاج إلى ~64–128 GiB مقيمًا، لذا لا يمكن لمحركات الذاكرة فتحه إطلاقًا.
Slater هو الرد. بدلاً من تحميل الرسم البياني في الذاكرة، يقوم بتجميعه مرة واحدة، دون اتصال: يحوّل slater-build بياناتك إلى صورة على القرص غير قابلة للتغيير وموجهة بالمحتوى، ثم يخدم أي عدد من خوادم Slater تلك الصورة عبر Bolt (بحيث تعمل برامج تشغيل neo4j الحالية لديك دون تعديل)، مع ترحيل الكتل عند الطلب والاحتفاظ فقط بميزانية ذاكرة تخزين مؤقت ثابتة مقيمة. هكذا يخدم نفس الرسم البياني ذو 90 مليون عقدة من بضع مئات من ميغابايتات RAM — يتم فصل حجم الرسم البياني عن فاتورة الذاكرة. رسم بياني بحجم 4 GB ورسم بياني بحجم 400 GB يكلفان نفس RAM للخدمة، لذا يمكنك نشر نسخ متماثلة للقراءة رخيصة وعديمة الحالة والسماح للمخزن، وليس الكومة، بحمل الرسم البياني.
هذا يجعله مناسبًا بشكل طبيعي للرسوم البيانية المعرفية خلف RAG، ورسوم التوصيات والهوية، ورسوم التبعيات — أي شيء كبير ومترابط تريد الاستعلام عنه بتكلفة منخفضة وبشكل متكرر. البحث المتجهي الأصلي على القرص يعيش بجوار الرسم البياني مباشرة، لذا فإن نفس المحرك هو طبقة الاسترجاع للتضمينات أيضًا.
لكن التجميع مرة واحدة لا يعني التجميد. تلك الصورة هي أساس، وليست حالة نهائية: طبقة كتابة اختيارية تقع فوقها، بحيث يمكن تصحيح رسم بياني حي وتوسيعه دون إعادة بناء أي شيء.
النواة غير قابلة للتغيير؛ الرسم البياني ليس كذلك. فعّل الطبقة القابلة للكتابة (delta.enabled) وستكتب عبر Bolt — صحّح خاصية واحدة، أضف عقدة، اسحب حافة — وستُسجَّل التغييرات بشكل دائم، دون إعادة بناء الصورة. ما يُبقيها رخيصة على جانب القراءة هو مكان عيش الكتابات.
تتراكم الكتابات في طبقة دمج سجل-منظم (LSM) فوق النواة غير القابلة للتغيير: سجل كتابة مسبق وجدول في الذاكرة، مع انسكاب إلى مقاطع دلتا غير قابلة للتغيير، تُطوى مرة أخرى في نواة جديدة بواسطة توحيد دوري. ما الذي يشتريه لك ذلك:
count(*)، والهوامش الخاصة بالتسميات وأنواع العلاقات — تبقى قراءات بيانات وصفية حتى مع وجود كتابات معلقة: تحتفظ الدلتا بعداداتها الخاصة، لذا فإن count(*) عبر نواة بحجم 91.6 مليون عقدة مع نصف مليون كتابة معلقة لا يزال يجيب في عشرات المللي ثانية دون لمس كتلة واحدة.SUCCESS فقط بعد fsync الذي يغطي الكتابة. جمّع كتاباتك وستكون رخيصة — كتابة-UNWIND تلتزم fsync واحدًا لكل دفعة بدلاً من كل صف.MERGE / MATCH … SET / DELETE (وCREATE / REMOVE، وحذف الفصل، وكتابات العلاقات) مفتاحية على خاصية هوية العقدة — أو عبارات ISO GQL المكافئة لتعديل البيانات ( / / / )، التي تنزل على نفس المسار. صحّح وأدرج وحدّث واسحب، عبر العقد والحواف، موجهة بالطريقة التي تُخاطب بها بياناتك بالفعل.مع إيقاف الطبقة — الافتراضي — يخدم Slater النواة غير القابلة للتغيير النقية ويرفض الكتابات. راجع الطبقة القابلة للكتابة للنموذج الكامل.
حول الاسم. سُمي Slater على اسم عميل CIA في Archer (مسلسل رائع) الذي يصر على استخدام اسم واحد فقط — "فقط… Slater" — وهو أحد شخصياتي المفضلة فيه. راجع صفحة الويكي للشخصية.
MERGE / SET / DELETE بمفاتيح الأعمال عبر العقد والحواف، ملتزمة جماعيًا ودائمة عبر fsync، مطوية مرة أخرى في نواة جديدة بواسطة التوحيد. القراءات لا تدفع ثمنها.current بشكل ذري، وسيلتقطه الخوادم. كل كتلة مُدققة المجموع الاختباري، لذا تُرفض الصورة نصف المنسوخة بدلاً من خدمتها.ثنائيان يشكلان مساحة العمل:
| الثنائي | الدور |
|---|---|
slater | خادم Bolt عبر الإنترنت (نقطة دخول الحاوية): يخدم القراءات ومع delta.enabled، مسار الكتابة الدائم بكاتب واحد. |
slater-build | المترجم دون اتصال: يحوّل تفريغ Cypher بدائي إلى دليل جيل غير قابل للتغيير وموجه بالمحتوى. |
يقسم Slater البناء بالجملة عن الخدمة: يقوم slater-build بالعمل الشاق
دون اتصال — استيعاب بياناتك وتجميعها في جيل غير قابل للتغيير — بحيث لا يتم
تجميع رسم بياني بارد أبدًا على المسار الساخن للخدمة. داخل الخادم، يجيب سطح
القراءة على شريحة Cypher واسعة — مطابقة الأنماط، واستعلامات فرعية
WITH/UNION/CALL {…}، وأكثر من 70 دالة عددية وتجميعية، وقيم زمنية
وجغرافية، وخوارزميات رسوم بيانية (algo.*)، وKNN متجهي أصلي على القرص
(db.idx.vector.queryNodes) — بينما تقع طبقة الدلتا العلوية للطبقة القابلة
للكتابة تحت ذلك السطح وهي بتكلفة صفرية عندما تكون فارغة، بحيث لا تحمل
القراءات أبدًا آليات جانب الكتابة. يمكنك تحديث رسم بياني بطريقتين: اكتب إليه
حيًا عبر Bolt (راجع الطبقة القابلة للكتابة)،
أو ابنِ جيلًا جديدًا دون اتصال واستبدل مؤشر current بشكل ذري، والذي
يلتقطه الخادم الجاري عبر حارس الجيل الخاص به (راجع
حارس الجيل).
يعيش دليل المستخدم الكامل في docs/manual/ —
دليل ميزة-بميزة يشرح، لكل قدرة، ما هي، ولماذا
توجد، وكيفية استخدامها، مع أمثلة عملية يمكنك تشغيلها مقابل
رسم بياني عينة مرفق. ابدأ هناك لأي شيء يتجاوز هذه النظرة العامة.
graphiti-slater هو محوّل
يسمح لـ Graphiti بتخزين رسمه البياني
المعرفي الزمني في Slater، مع
docker-example/
قابل للتشغيل — بما في ذلك تعريضه لـ Claude Code كخادم MCP. راجع ذلك المستودع لكيفية
عمله وكيفية تشغيله.
صُمم 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/main/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
docker compose build
docker compose up slater
build):docker compose run --rm builder
--input /dumps/people.cypher --graph people --data-dir /data
مرحلة البناء تثبّت `cmake` و`clang` و`libclang-dev` لخلفية rustls
`aws-lc-rs`؛ و`git` (الموجود بالفعل في الصورة الأساسية) مطلوب لاعتماد
`hs-utils` git+tag، الذي يجلبه `.cargo/config.toml` عبر واجهة git CLI.
تغطي الأقسام أدناه تنسيق التخزين على القرص، والإعداد، وقوائم التحكم في الوصول (ACLs)،
ومثالًا عمليًا محليًا (بدون 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>
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.--encrypt تكون كل
كتلة مختومة إضافيًا بـ XChaCha20-Poly1305 (AEAD عند التخزين).مع تفعيل delta.enabled، يصبح الجيل غير القابل للتغيير المستوى السفلي المضغوط بالكامل (النواة) لشجرة دمج صغيرة منظمة بالسجلات، وتُركب الكتابات الحية فوقه:```
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`
قبل إرجاع `SUCCESS` الخاص بـ Bolt — لذا *الإقرار يعني المتانة*، ويُتجاهل الذيل الممزق
عند إعادة التشغيل. عملية كتابة مجمّعة عبر `UNWIND` تُلحق صفوفها وتُلتزم **بـ`fsync` واحد**
للدفعة بأكملها. سجل WAL **على القرص المحلي فقط** (لا يُمرَّر عبر الواجهة الخلفية للتخزين)،
مما يجعل عقدة *الكاتب* ذات حالة: فهي تحتاج وحدة تخزين محلية متينة عند `delta.walDir`.
بينما تبقى نسخ القراءة عديمة الحالة.
* **جدول الذاكرة (Memtable) → 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)** (طريقة الوصول المتسلسل المفهرس) —
الفهرس الكلاسيكي *الثابت، المرتب، ذو الكتل المنظمة*، وهو الشكل المناسب تمامًا
لجيل غير قابل للتعديل: لا توجد إدراجات تحتاج إعادة توازن، لذا
بساطة ISAM تحقق ما كانت آليات تعديل شجرة B ستُعقّده فقط.
* الإدخالات `(value, entity_id)` مرتبة حسب القيمة ومُعبأة في نفس
كتل 256 KiB المضغوطة بـzstd مثل كل شيء آخر.
* **مستوى علوي مقيم صغير** يحمل المفتاح الأول لكل كتلة (فهرس متناثر).
البحث الثنائي في ذلك المستوى العلوي في الذاكرة يجد *الكتلة الواحدة*
التي قد يوجد بها المفتاح، ثم يقرأ تلك الكتلة ويفك ضغطها ويمسحها — لذا البحث
بالتساوي هو **قراءة كتلة واحدة**، ومسح النطاق يمشي عبر سلسلة الكتل المتجاورة
التي يغطيها. (لهذا يكون البحث المفهرس عبر `meshUi` في نطاق أجزاء من الثانية
بينما نفس المطابقة على خاصية غير مفهرسة تمسح التسمية بالكامل.)
* المخطط يختاره عبر `NodeScan::RangeEq` / `RangeRange`؛ المسند غير المفهرس
يتراجع إلى مسح تسمية أو مسح كامل، مع إعادة فحص المنفّذ لكل مسند في كلتا الحالتين.
### البحث المتجهي (Vamana + PQ) — جيب التمام، L2 والضرب النقطي، قراءة *وكتابة*
بحث KNN المتجهي (`db.idx.vector.queryNodes`) يعمل عبر فهارس
**جيب التمام، أو L2، أو الضرب النقطي (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`) صغيرة بما يكفي لتبقى **مقيمة**، لذا
بحث الشعاع يقيّم المرشحين من الرام وفقط المتجهات الكاملة القليلة المختارة
تُقرأ من القرص. مجموعة PQ المقيمة هذه هي ما يثبّته تجمع `cache.vectorCacheBytes`.
**تضمينات قابلة للكتابة — سلم الكتابة المتجهية (بأسلوب [FreshDiskANN](https://arxiv.org/abs/2105.09613)).**
التضمين المفهرس قيمة قابلة للكتابة من الدرجة الأولى. `SET n.embedding = vecf32([…])` (و`REMOVE`) يهبط في
دلتا الكتابة ويكون **مرئيًا فورًا لـKNN بترتيب دقيق**، ثم ينجو من تفريغ
مقطع، ودمج، ودمج نهائي. الاستعلام يدمج حتى ثلاثة مستويات — الفهرس الأساسي المغلق،
فهرس مغلق لكل مقطع، وفهرس **RW في الذاكرة** (Vamana قابل للتعديل حي
فوق دلتا الكتابة) — لذا تبقى زمنية الاستجابة ثابتة مع تراكم الكتابات بدلًا من النمو
مع عدد الكتابات المعلقة. الحذف يترك *ثقبًا*: العقدة تتوقف عن الظهور في النتائج
لكنها تبقى نقطة تنقل حتى **دمج حذف** في الخلفية يزيلها
من الرسم البياني، لذا تتوقف عمليات الحذف عن تكلفة إدخال/إخراج الاستعلام. ولأن الرسم البياني
على القرص يخاطب جيرانه بموضع التخطيط وليس بمعرف العقدة، فإن `CALL slater.consolidate()`
ينقل Vamana **بالإشارة** — روابط صلبة، متطابقة بايتًا ببايت — ويعيد كتابة
عمود معرف صغير فقط، مطويًا كتابات المتجهات في الأساس **دون** إعادة بناء الرسم البياني
O(N·R·L). الأرقام المقاسة، مع التحفظات، موجودة في [تقرير الأداء](https://github.com/hikari-systems/slater/blob/main/docs/PERF-REPORT.md).
## الواجهات الخلفية للتخزين (نظام الملفات / S3 / GCS)
كل ملف جيل يُفتح عبر تجريد **`ObjectStore`** بدلًا من
`std::fs` مباشرة، لذا *نفس* تنسيق البايت على القرص — الكتل، الفهارس،
البيان، مؤشر `current` — يُقدَّم دون تغيير من أي واجهة خلفية؛ فقط *من أين
تأتي البايتات* يختلف، وليس القراء أو محرك الاستعلام أو
فحوصات السلامة. المسار الساخن هو قراءات موضعية (`read_exact_at`)، والتي تُرسم
على `pread` لملف محلي وطلب نطاق بايت HTTP على مخزن كائنات
— Slater لا يستخدم mmap أبدًا، لذا نموذج القراءة الصريح المحدود متطابق
في كل مكان.
**ثلاث واجهات خلفية من الدرجة الأولى**، تُختار عبر `dataBackend.kind`. نظام الملفات هو
الافتراضي البسيط؛ **Amazon S3 وGoogle Cloud Storage واجهتان خلفيتان متساويتان
ومدعومتان بالكامل لمخازن الكائنات** — الصورة المنشورة تأتي مع كلتيهما
مُجمَّعتين، لذا كل منهما مجرد إعداد، والجيل المبني مرة واحدة يمكن تقديمه من
أي منهما (حتى الترحيل `fs` → S3 → GCS) دون إعادة بناء.
| `dataBackend.kind` | القراءة الموضعية | السلامة عند الفتح | بيانات الاعتماد |
| --- | --- | --- | --- |
| `fs` *(الافتراضي)* | `pread` | إعادة تجزئة BLAKE3 كاملة لكل ملف | — |
| `s3` | طلب `Range` عبر HTTP | **SHA-256** من الخادم عبر `HEAD` (→ إعادة تجزئة BLAKE3 للجسم إذا غائب) | مفاتيح الإعداد، سلسلة AWS، أو دور IAM |
| `gcs` | قراءة نطاق HTTP | **CRC32C** من الخادم عبر `get_object` (→ إعادة تجزئة BLAKE3 للجسم إذا غائب) | ADC / Workload Identity، أو JSON لحساب الخدمة |
كلا مخزني الكائنات يتحققان من السلامة من **المجموع الاختباري الذي يحسبه المخزن
ويحتفظ به بالفعل**، المسترجع كبيانات وصفية للكائن: `slater-build` يرسل
المجموع الاختباري عند الرفع (يتحقق المخزن من البايتات ضده ويخزنه)، و
الخادم يقرأه عند الفتح ويقارنه بالبيان — طلب بيانات وصفية واحد
لكل ملف، دون تنزيل الجسم. إنه بجودة المحتوى ومتطابق في الروح
عبر S3 (SHA-256) وGCS (CRC32C). عندما يحمل كائن **لا** مجموعًا اختباريًا مخزنًا
على الخادم (مُنسخ خارج النطاق، أو مرفوع بإعداد افتراضي مختلف)، يقوم الخادم
**بإعادة تجزئة جسم الكائن مقابل BLAKE3 في البيان** بدلًا من الثقة بطوله
البايتي — فحص السلامة المطلوب لا يُخفَّض أبدًا بصمت إلى مقارنة حجم.
الأجيال المنشورة عبر Slater تحمل دائمًا المجموع الاختباري، لذا تبقى على
مسار البيانات الوصفية الرخيص.
ما يفحصه هذا العمود، على كل واجهة خلفية، هو أن الملفات **تطابق البيان**.
سواء كان البيان نفسه جديرًا بالثقة سؤال منفصل، وهو
المفتاح الرئيسي الذي يجيبه: مع تكوين مفتاح يحمل البيان MAC بمفتاح
يتحقق منه الخادم قبل الثقة بأي حقل (بما في ذلك هذه التجزئات)، لذا
بيان أعيدت كتابته لوصف ملفات معدلة يُرفض؛ بدون مفتاح تكون المقارنة غير موقعة في كل مكان، و
من يستطيع الكتابة إلى دليل البيانات يمكنه إعادة كتابة ملف والبيان
معًا. انظر
[ماذا تعني السلامة في كل إعداد](https://github.com/hikari-systems/slater/blob/main/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`، بالإضافة إلى
`awsSessionToken` لبيانات اعتماد STS المؤقتة) وتتراجع إلى سلسلة 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
gcs)حاوية GCS يتم الوصول إليها عبر واجهة JSON API. التفويض أصلي في GCP: بشكل افتراضي يتم حل بيانات الاعتماد الافتراضية للتطبيق — GKE Workload Identity، أو خادم بيانات GCE الوصفية، أو مفتاح gcloud / GOOGLE_APPLICATION_CREDENTIALS. قم بتعيين dataBackend.gcs.credentialsPath (ملف مفتاح JSON لحساب الخدمة) أو credentialsJson المضمّن لمفتاح صريح. يشير dataBackend.gcs.endpoint إلى محاكي fake-gcs-server، ويمكّن dataBackend.gcs.anonymous=true الوصول غير المصادق عليه لذلك المحاكي فقط — وليس أبدًا ضد GCS الحقيقي.```sh
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 يكون عندما تريد توليدات في مخزن كائنات مركزي دائم
بدلاً من قرص العقدة — عادةً: النشر مرة واحدة والتوزيع على العديد من
نسخ الخوادم عديمة الحالة وعديمة الأقراص التي تقرأ جميعها نفس الحاوية؛ أو فصل
مضيف البناء عن مضيفي الخدمة؛ أو الاعتماد على متانة/إصدار/دورة حياة
المخزن بدلاً من إدارة وحدات التخزين. المقابل هو
زمن الاستجابة: الكتلة الباردة هي رحلة شبكة ذهاباً وإياباً (~10–50 مللي ثانية) بدلاً من قراءة محلية
(~0.1 مللي ثانية). يخفي Slater معظم ذلك عبر ذاكرة التخزين المؤقت للكتل في الذاكرة، والقراءة المسبقة المتزامنة،
و ذاكرة التخزين المؤقت الاختيارية على القرص أدناه. إذا كانت توليداتك موجودة بالفعل
على تخزين محلي سريع ولا تحتاج إلى نموذج الحاوية المركزية، فإن fs أبسط
وأسرع.
ذاكرة BlockCache في الذاكرة صغيرة عمداً (حجم RSS المحدود هو الضمان
الرئيسي)، لذا على مجموعة عمل أكبر من ذاكرة الوصول العشوائي، ستُعاد جلب نفس الكتل
من مخزن الكائنات عند كل تجاوز. طبقة ثانية اختيارية على SSD محلي تحل ذلك: الكتلة
المطرودة من الذاكرة تُخدم من القرص المحلي
(~0.1 مللي ثانية) بدلاً من GET كائن جديد، مما ينجو من الإخراج من الذاكرة ويقلل
عدد/تكلفة طلبات مخزن الكائنات — مما يقرب العقدة المدعومة بمخزن الكائنات من
أداء نظام الملفات المحلي بمجرد أن تصبح دافئة. وهي اختيارية لكل من s3 و gcs،
تُفعَّل بتعيين dataBackend.<s3|gcs>.diskCacheBytes > 0 ودليل
diskCacheDir قابل للكتابة.
--encrypt) لا تزال مختومة بـ AEAD — أسفل فك التشفير/فك الضغط.
لا تحتفظ طبقة التخزين المؤقت بمفتاح التشفير أبداً ولا تعيد التشفير، لذا
تُحفظ حالة التخزين مجاناً: التوليد المشفر يصل إلى القرص
لا يزال مختوماً.diskCacheDir يجب أن يشير إلى وحدة تخزين حقيقية قابلة للكتابة — أبداً tmpfs
(tmpfs هي ذاكرة وصول عشوائي وستُبطل ضمان RSS المحدود). الفهرس في الذاكرة الذي
يتتبعه يكلف القليل من الذاكرة (~عشرات البايتات لكل كتلة مخزنة)، والتي تُحسب
ضد سقف RSS الخاص بك — اجعل الدليل ≫ ذاكرة التخزين المؤقت للكتل في الذاكرة.blockCacheBytes / 8 (مقيدة أرضياً بـ
diskCacheBytes) — 8 ميجابايت عند الافتراضي — وتتخلص بدلاً من أن تنمو، لذا
لا يمكن لفحص بارد تضخيمها؛ الكتلة المتخلص منها تُعاد جلبها ببساطة عند عدم تطابقها التالي.
لا تحتاج إلى إعداد: فهي تتدرج مع blockCacheBytes، لذا تضيف طبقة القرص
رقماً جديداً إلى ميزانية RSS إلى جانب فهرسها.تعمل نسخة القراءة بنظام ملفات جذر للقراءة فقط ومستخدم غير جذر
(appuser:1000) — كل ما تحتاجه مثبت للقراءة فقط. الكاتب
(delta.enabled) يحتاج بالإضافة إلى ذلك وحدة تخزين دائمة واحدة قابلة للكتابة لسجل WAL الخاص به.
يُحمَّل التكوين بواسطة المُحمِّل المتدرج القياسي للمنزل: config.json المدمج،
ثم /sandbox/config.json المدمج بعمق فوقه، ثم تجاوزات بيئة KEY__sub
(شرطة سفلية مزدوجة للتداخل؛ المفاتيح تطابق تكوين camelCase).
كل مقبض تكوين — مفتاحه camelCase، وتجاوز البيئة KEY__sub، وافتراضيه، وما يفعله — مُجدول في مرجع التكوين. المقابض الأكثر ضبطاً هي ميزانيات التخزين المؤقت (cache.*)، وحُرّاس الاستعلام (query.*)، وسقوف الاتصال (server.*)، وخلفية التخزين (dataBackend.*)، والطبقة القابلة للكتابة (delta.*).
الذاكرة المقيمة تتتبع
blockCacheBytes + vectorCacheBytes + resultCacheBytes ضمن حمل ثابت لكل إدخال وحمل المُخصِّص — كل تجمع يزن محتوياته الخاصة (السلاسل والحاويات حسب السعة المخصصة) ويُخرج ليظل تحت الميزانية، لكن
الدفاتر لكل إدخال وتقريب فئات الحجم للمُخصِّص تقع فوق الرقم الذي
تضبطه — بالإضافة إلى حمل ثابت صغير (وحتى degreeColumnBytes لعمود
الدرجة lazy، بمجرد تمرين المسار السريع لمجموع الدرجات 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.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 — سلسلة $argon2id$… الناتجة من slater hash-password
(وليس نصًا صريحًا أبدًا؛ فالملف نفسه بصيغة JSON عادية ويقع على تخزين مشترك).
grants — قوائم الصلاحيات لكل رسم بياني. هناك صلاحيتان ذات معنى:
read — الاستعلام عن الرسم البياني. الرسم البياني غير الموجود في صلاحيات المستخدم يكون غير مرئي له.write — تعديل الرسم البياني عبر الطبقة القابلة للكتابة (delta.enabled):
عبارات MERGE / SET / DELETE وCALL slater.consolidate().وهما لذا فإن تفعيل الطبقة القابلة للكتابة لا يمكن أن يرقّي القرّاء الحاليين إلى كتّاب. الكاتب يحتاج كلتيهما — — لأن حل مفتاح العمل لكتابته يُعد عملية قراءة. سلاسل الصلاحيات غير المعروفة يتم تجاهلها (لا تمنح شيئًا).
قم بتركيبه للقراءة فقط في المسار المحدد بواسطة 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
حمل الاستعلام `cost` (العناصر المُحصَّلة)، `resultCount`، `execMs`، و
`limitRowCount` (فقط عندما يحدد الاستعلام `LIMIT`) — ولا يُرسل نص الاستعلام أبدًا
أو أي قيمة نتيجة. حالة الخروج هي `0` عند النجاح، و`1` عند خطأ في التحليل/الفتح/التنفيذ
(رسالة على stderr).
## تصدير رسم بياني (`slater dump`)
يقوم `slater dump` بتصدير رسم بياني من خادم **قيد التشغيل** كـ Cypher بصيغة `MERGE`
بمفتاح العمل — نفس اللهجة التي يستوعبها `slater-build` — بحيث يعود الرسم البياني
(تفريغ ← `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})، مع
تسمية الهوية (التسمية التي توفر المفتاح التجاري) أولاً والباقي
مرتبًا؛ يتم ربط الدمج على تسمية الهوية وحدها، لذا تُكتب التسميات اللاحقة
على العقدة دون إنشاء عقدة أخرى. التسميات وأنواع العلاقات
ومفاتيح الخصائص التي تحتوي على أحرف خاصة تُحاط بعلامات backtick عند
الإصدار، لذا تعود الأسماء غير المعتادة بأمان ولا يمكنها حقن Cypher في
إعادة البناء. المتجهات (والقيم الأخرى
التي لا تحتوي على تهجئة حرفية في Cypher) لا يمكنها ركوب تفريغ MERGE ويتم إسقاطها
مع تحذير على stderr. حالة الخروج هي 0 عند النجاح، 1 عند الخطأ.
دليل كامل وقابل للتشغيل — بناء رسم بياني، تقديمه، الاتصال به عبر برامج تشغيل neo4j JavaScript و Python، والكتابة إليه — موجود في صفحتي Quickstart و Writing data من الدليل، باستخدام الرسم البياني النموذجي المرفق في docs/manual/examples/.
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
إن `cargo build` عادي ينتج ملفًا ثنائيًا **يعتمد على نظام الملفات فقط** — حيث أن خلفيتي `s3` و `gcs`
مقفلتان خلف ميزات Cargo بحيث يظل البناء الافتراضي صغيرًا (بدون AWS
أو Google SDK، وبدون وقت تشغيل غير متزامن). قم بتمكين ما تحتاجه على **كلٍّ من** `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 المطابقة التي تُمرَّر إلى
graph-format/{s3,gcs}. طلب backend في وقت التشغيل
(dataBackend.kind=s3|gcs، أو slater-build --publish-{s3,gcs}-*) دون
تجميع ميزته يفشل بسرعة مع رسالة خطأ واضحة "built without the … feature".
صورة Docker المنشورة تُمكّن كليهما (Dockerfile CARGO_FEATURES)، لذا
لا تحتاج الصور المبنية مسبقًا إلى أعلام إضافية — هذا يهم فقط عند البناء من المصدر.
اختبارات التكامل مقيدة بالمثل: --features s3 --test s3_minio،
--features gcs --test gcs_emulator (خادم fake-gcs-server)، و--features gcs --test gcs_real (GCS حقيقي عبر ADC)؛ كل منها يُتخطى ما لم تُضبط متغيرات بيئة SLATER_* الخاصة به.
انظر docs/PLAN.md وdocs/PROGRESS.md وdocs/DECISIONS.md للتصميم،
وسجل المعالم، وسجل القرارات.
حتى ستة محركات، مجموعة واحدة ذات عميل واحد، رسوم بيانية من عقدة تجريبية بحجم 62 ألف عقدة إلى ويكي بيانات
91.6 مليون عقدة / 1.5 مليار حافة. كل محرك يُقاس بمعزل عن الآخرين (كل حاوية أخرى
متوقفة — RSS وزمن الاستجابة هما بصمته الخاصة). جداول زمن الاستجابة أدناه
أُعيد قياسها على Slater 0.21.0 (البناء القابل للكتابة): الرسوم البيانية الصغيرة/المتوسطة (MeSH، EU-AI-Act)
حديثًا، والرسم البياني 91.6 مليون كتمرير نفس الجهاز، مرساة مشتركة slater-vs-Neo4j (انظر
ذلك الجدول). أرقام الذاكرة المقيمة منقولة من التمرير السابق (تُقاس عبر
cgroup الحاوية؛ مسار القراءة مطابق بايتًا بايتًا مع طبقة الكتابة خاملة). أرقام
المحركات الأخرى هي نتائج التمرير عبر المحركات الراسخة (إصداراتها/أداؤها لم يتغير).
كل الأرقام هي متوسطات (مللي ثانية) أو ذاكرة مقيمة ذروية (MiB). الأقل أفضل في كل مكان؛ الغامق =
الأفضل في الصف. شُغّل slater على backend نظام الملفات المحلي (fs) الخاص به؛ بينما
تستبدل backends S3 وGCS زمن استجابة القراءة المحلية
بجولات تخزين الكائنات (يُخففها التخزين المؤقت في الذاكرة وطبقة التخزين المؤقت الاختيارية على القرص المحلي)،
لذا تميز هذه الأرقام المحرك، وليس نشر تخزين الشبكة.
المحركات الثلاثة التي تُقسّم من القرص — slater وNeo4j 5 وLadybugDB — تُحمّل كل الرسوم البيانية الخمسة. الثلاثي في الذاكرة (Memgraph · FalkorDB · ArcadeDB) لا يمكنه استيعاب الرسم البياني ذي 1.5 مليار حافة على الإطلاق (يحتاج ~64–128 غيغابايت مقيم)، ومستورد ArcadeDB لا يمكنه إنهاؤه أيضًا.
كل رقم هو ذاكرة عمل ملتزمة — ما لا يمكن لنظام التشغيل استعادته. كل محرك باستثناء slater يحتفظ برسمه البياني في ذاكرة مجهولة ملتزمة (كومة خاصة، أو ذاكرة تخزين صفحات خارج الكومة في Neo4j، أو تجمع مخازن)، لذا فإن ذروة RSS الخاصة به هي بصمته الملتزمة. slater وحده يخدم من ذاكرة تخزين صفحات OS القابلة للاستعادة لمخزنه على القرص، لذا فإن رقمه هو مجموعة العمل المجهولة؛ ذاكرة تخزين صفحات المخزن (القابلة للإخلاء تحت الضغط — يستمر slater في الخدمة) مستبعدة، وتُعرض كـإجمالي بين قوسين للرسم البياني 91.6 مليون. الغامق = الأدنى.
slater هو الأدنى في كل مقياس وينمو ~50× بينما ينمو الرسم البياني ~1,500× — بصمته
تتبع مجموعة عمل الاستعلام، وليس الرسم البياني (خامل ~16–71 MiB طوال الوقت). ينمو
الثلاثي في الذاكرة ~خطيًا ولا يمكنه تحميل الرسم البياني 1.5 مليار؛ يلتزم Neo4j بكومة ~2 غيغابايت
بغض النظر عن الاستعلام. († LadybugDB على الأشكال المحدودة فقط — اجتيازات المحور / الطول المتغير /
أقصر مسار عند 1.5 مليار حافة تحتاج رفع تجمع القراءة إلى ≥2 غيغابايت، مقابل حد maxIntermediate
التلقائي في slater.) الرسوم البيانية للقيمة→العدد في وقت البناء تضيف ذاكرة مقيمة لا تذكر —
بضعة كيلوبايت لعمود مفهرس منخفض التنوع، وصفر للرسوم البيانية ذات المفاتيح الفريدة مثل Wikidata
(wikidata_id يتجاوز حد تنوع الرسم البياني، لذا لا يُخزن أي شيء) — لذا فإن هذه الأرقام
لم تتغير بفعل تلك الميزة.
يمتلك slater أشكال البيانات الوصفية / الفهرس / الفحص (count، label، idx-eq، scan — ~0.4 مللي ثانية، 10–200× محركات الخدمة)، والبحث النقطي المفهرس (0.43 مللي ثانية، الآن يتفوق على الزوج في الذاكرة 0.48 مللي ثانية)، والقفزات المتعددة بدون مرساة (قفزتان 1.40 مللي ثانية عبر فحص نوع العلاقة، الأسرع في المجال)، و— عبر رسم بياني للقيمة→العدد في وقت البناء على مفتاح التجميع المفهرس — group-by / count(DISTINCT) للتسمية الكاملة (0.45 مللي ثانية، متقدمًا على LadybugDB العمودي 5.3 مللي ثانية). تحتفظ خوادم الذاكرة فقط بـقفزة واحدة خام (Memgraph 1.21 مللي ثانية مقابل 1.28 مللي ثانية في slater). (pole 62 ألف/106 ألف يبدو مشابهًا: slater الأسرع الوحيد في count/scan ~0.4 مللي ثانية، ~1.3–2.6 مللي ثانية في القفزات.)
يجيب slater على kNN بفحص قوة غاشمة دقيق (هذه المجموعات أقل من عتبة ANN البالغة 50 ألف متجه) حيث يستخدم الآخرون HNSW مقيمًا تقريبيًا — لذا فإن نتائج slater دقيقة (استدعاء 1.0). نواة مسافة SIMD + مصفوفة متجهات مقيمة وطبيعية مسبقًا أخذت Concept من ~23 → ~2.9 مللي ثانية وChunk من ~10 → ~2.4 مللي ثانية، لذا يتفوق slater الآن على Neo4j وLadybugDB ويقع ضمن ~1.4× من Memgraph، متخلفًا فقط عن FalkorDB — بينما هو دقيق.
الجداول أعلاه مقارنات قراءة عبر المحركات. مسار كتابة المتجه (سلم
الكتابة بنمط FreshDiskANN فوق قاعدة Vamana
الثابتة) ليس له نظير عبر المحركات — لا يوجد محرك آخر هنا يقوم بـANN أصلي على القرص قابل للكتابة —
لذا فإن الأرقام أدناه هي معايير مكون لمحرك واحد عبر تركيبة اصطناعية
شبيهة بالتضمين (متشعب منخفض الرتبة، بُعد 768، معايير غير متساوية)، ملتزمة تحت
crates/slater/benches/ ومكتوبة بالكامل — مع
المنهجية وكل تحفظ — في docs/PERF-REPORT.md. الاستدعاء
يُقاس دائمًا مقابل قوة غاشمة دقيقة على المجموعة الحية، وليس فهرسًا مقابل آخر.
المقياس هنا تمثيلي ومُستقرأ فقط حيث يكون المقياس خطيًا في الحجم.
الرقم الوحيد الذي يريد صندوق الأداء المخصص هو إنتاجية إعادة كتابة الدمج بالمسار البطيء — عندما يحمل الدمج عمليات حذف أو متجهات جديدة بدلًا من تبديل نقي، فهو إعادة ضغط تسلسلية محدودة بـzstd أحادي الخيط والقرص المحلي، لذا فإن MiB/s المطلقة خاصة بالبيئة (يُظهر التقرير الشكل ويشرح النطاق البيئي).
محركات الذاكرة (Memgraph / FalkorDB / ArcadeDB) لا يمكنها تحميل هذا الرسم البياني على الإطلاق
(~64–128 غيغابايت مقيم). فقط slater وNeo4j 5 يفعلان ذلك. هذا تمرير جديد على نفس الجهاز ونفس اليوم
مقابل مجموعة مراسي ثابتة مشتركة — كل استعلام يصل إلى نفس العقد على كلا المحركين،
لذا فإن المواجهة المباشرة قابلة للمقارنة تمامًا (تجمع wikidata_id مشترك من المراسي متوسطة الدرجة؛
انظر الملاحظة أدناه حول سبب أهمية ذلك). يُعرض slater عند كلا التفرعين (query.maxFanout 1 =
افتراضي الإنتاجية، 8 = قرص زمن الاستجابة الذي يتداخل مع قراءات الكتل الباردة). الغامق = الأفضل في الصف.
الصورة الصادقة: slater يهيمن على أشكال البيانات الوصفية / الفهرس — count(*) هو
مُقدَّم من البيانات الوصفية (0.41 مللي ثانية مقابل فحص قرص Neo4j 3.6 ثانية، ~8800×)، وبحث النقطة / الدرجة /
ثلاث قفزات أسرع ~2–10× — متساوٍ مع Neo4j في 1–2 قفزة (التفرع 8 يتقدم في القراءات الباردة)،
لكنه يخسر var-length *1..2 distinct بشكل حاسم (≈1 ثانية مقابل 47 مللي ثانية في Neo4j): توسيع
slater المميز للطول المتغير أبطأ ماديًا هنا، نقطة ضعف حقيقية تستحق تحقيقًا خاصًا بها.
كل ذلك عند بضع مئات من ميغابايت من RSS مقابل كومة Neo4j الملتزمة ~2 غيغابايت.
حول المراسي. تعتمد أرقام الاجتياز هذه بشدة على أي العقد تبدأ منها — العقدة على بُعد رابط واحد من محور Wikidata الضخم ("human"، "country") لها حي قفزتين بقوة الملايين، لذا تتأرجح تكلفة الطول المتغير/القفزة بمراتب حجم مع اختيار المرساة. النسخة السابقة من هذا الجدول أخذت عينات من "أول N بالفحص" الخاص بكل محرك، وهو ليس مستقرًا ولا قابلاً للمقارنة؛ هذا التمرير يثبت مجموعة مراسي مشتركة واحدة محدودة الدرجة لكلا المحركين. (shortestPath محذوف من هذا التمرير — بين مرساتين عشوائيتين يعتمد على وجود المسار ومتغير جدًا بحيث لا يمكن حساب متوسطه بشكل ذي معنى.)
count(*) متعدد القفزات — الذاكرة مفصولة عن حجم النتيجةRETURN count(*) متعدد القفزات غير المحدود يعد أثناء التوسيع بدلًا من تجسيد
الصفوف المطابقة. نفس مراسي المحور على الرسم البياني 91.6 مليون، maxIntermediate=20M:
| 3-hop count(*) @ 91.6M | fanout=1 | fanout=8 |
|---|---|---|
| زمن الاستجابة / مجموعة عمل ذروية | 554 مللي ثانية / 0.66 غيغابايت | 298 مللي ثانية / 1.9 غيغابايت |
يحمل العدد صفوف O(1). الشحن لم يتغير، لذا فإن عدد المحور الضخم لا يزال يصل
إلى maxIntermediate على الحساب (قراءات الجوار)، محدودًا كما كان من قبل.
maxFanout)رفع query.maxFanout يتداخل مع قراءات الكتل الباردة، المرتبطة بـI/O للاستعلام عبر النوى —
يساعد الأشكال المرتبطة بالقرص ذات مجموعة العمل الباردة الكبيرة وهو ثابت على الأشكال الدافئة. على الرسم
البياني 1.5 مليار: shortestPath ≤6 918 → 608 مللي ثانية (1.5×، أكبر بحث 6,269 → 2,350 مللي ثانية، 2.7×)؛
3-hop count 547 → 298 مللي ثانية. maxFanout=1 هو الافتراضي (موجه للإنتاجية)؛ 8 هو
قرص زمن الاستجابة، بذاكرة عامل عابرة أكثر.
الجداول الكاملة لكل محرك (pole، MeSH، EU-AI-Act + قرص RAM↔زمن الاستجابة blockCacheBytes،
Wikidata 1 مليون و91.6 مليون) موجودة في
perf/cross-engine-hs/README.md؛ التمرير الجديد الخاص بـslater فقط
(كلا التفرعين، كل مجموعة بيانات) موجود في perf/PERF_CURRENT_STATUS.md.
المعايير أعلاه ذات عميل واحد. المحور التكميلي — السلوك تحت العديد من
العملاء المتزامنين — له أداته الخاصة، perf/loadtest/: سائق
Locust عبر Bolt بالإضافة إلى منسق يزيد الحمل تدريجيًا، ويقرأ CALL slater.diagnostics()،
ويجد نقطة انعطاف السعة، ويسمي المُحدد (الطريقة الكاملة في
docs/LOAD-TESTING.md). أبرز النتائج من تشغيل ذاكرة تخزين مؤقت 256 MiB على
رسم Wikidata-1M البياني (صندوق واحد 16 نواة):
كلتا مشكلتي الذاكرة اللتين كشفهما اختبار الحمل مغلقتان الآن؛ كل ذلك مُتتبع في وثيقة اختبار الحمل.
مرخص بموجب رخصة Apache، الإصدار 2.0. انظر LICENSE للنص
الكامل وNOTICE للإسناد. ما لم تذكر صراحةً
خلاف ذلك، فإن أي مساهمة مقدمة عمدًا للإدراج في هذا العمل،
كما هو معرّف في رخصة Apache 2.0، ستُرخص كما هو مذكور أعلاه، دون
أي شروط أو قيود إضافية.
SPDX-License-Identifier: Apache-2.0
INSERTSETREMOVEDELETE| الميزة | ماذا تعني لك |
|---|
| ذاكرة محدودة وقابلة للتنبؤ | الذاكرة المقيمة تتبع ثلاث ميزانيات ذاكرة تخزين مؤقت تحددها أنت، ضمن حمل زائد محدود لكل إدخال وللمخصص — لا تنمو مع حجم الرسم البياني؛ تضبط مقايضة الأداء/RAM بدلاً من توفير الموارد للرسم البياني بأكمله. مخصص jemalloc مع تطهير خلفي يعيد الذاكرة المحررة إلى نظام التشغيل بعد دفعات الاستعلام الثقيلة، لذا ينخفض الحجم المقيم نحو أرضيته الخاملة بدلاً من البقاء مثبتًا عند علامة ما بعد الدفعة العالية. |
| متعدد المستأجرين جاهزًا | خادم واحد يستضيف العديد من الرسوم البيانية مع منح قراءة لكل مستخدم — عزل متعدد قواعد البيانات تحتفظ به معظم قواعد بيانات الرسوم البيانية لطبقة مدفوعة/مؤسسية. |
| تشفير أثناء الراحة وأثناء النقل | ختم XChaCha20-Poly1305 لكل كتلة (المفتاح لا يُكتب أبدًا على القرص) بالإضافة إلى TLS اختياري (bolt+s://). صديق للـ GDPR بالتصميم. التشفير هو أيضًا ما يشتري سلامة موثَّقة: يختم الباني البيان بـ MAC مفتاحي، ويتحقق خادم يحمل المفتاح منه ويرفض خدمة جيل تم تزوير بيانه أو تغييره أو إزالة MAC منه. صورة بدون مفتاح (نص عادي) محمية بتجزئة المحتوى بدون مفتاح فقط — الاكتمال والفساد، وليس العبث. راجع ماذا تعني السلامة في كل إعداد. |
| تثبيت صغير | ثنائي صغير مجرد على أساس glibc distroless (بدون shell/apt) — صورة متعددة البنى (amd64/arm64) تُسحب بحوالي ~22 MB، أو ~12 MB لعلامة slater:latest-lite الخاصة بالخادم فقط؛ TLS نقي بـ Rust، بدون OpenSSL. اسحب وشغّل. |
| مصمم للنشر الدوري | ابنِ رسمًا بيانيًا دون اتصال، اخدمه غير قابل للتغيير، ثم استبدل إصدارًا جديدًا بشكل ذري مع صفر توقف — مثالي لأحمال عمل مستودع البيانات / التحديث المجدول. |
| متين تحت الحمل | الخادم والباني دون اتصال يترجمان معًا مع #![forbid(unsafe_code)] — unsafe الوحيد في المحرك يعيش في كريت مخصص jemalloc المُدقق. النواة غير قابلة للتغيير، لذا لا تأخذ القراءات أقفالًا ولا تنتظر كاتبًا أبدًا؛ كاتب واحد يسلسل الطفرات خلف مسار الكتابة وحده. لا توقف GC، ولا سباقات بيانات. استعلام سيئ واحد لا يمكنه إسقاط الخادم. |
| يعمل مع أدوات neo4j الخاصة بك | يتحدث Bolt 5.4 / 4.4 / 4.1 — استخدم برامج تشغيل neo4j القياسية (JS وPython وGo وJava…)، أو cypher-shell، أو متصفحات الرسوم البيانية دون تغيير. |
| سطح استعلام Cypher غني | سطح قراءة واسع: MATCH/WHERE/WITH/UNION، استعلامات فرعية CALL {…}، أكثر من 70 دالة وتجميعًا، قيم زمنية وجغرافية، وتعبيرات نمطية. |
| كتابات حية ودائمة | طبقة LSM اختيارية بكاتب واحد فوق النواة غير القابلة للتغيير (delta.enabled): MERGE / SET / DELETE / CREATE / REMOVE بمفاتيح الأعمال عبر العقد والعلاقات، وكتابة-UNWIND مجمعة (fsync واحد لكل دفعة)، وCALL slater.consolidate() — ملتزمة جماعيًا، ودائمة عبر fsync، ومطوية مرة أخرى في نواة جديدة بواسطة التوحيد. مسار القراءة متطابق بايتًا عندما تكون الدلتا فارغة. |
| ISO GQL، قراءة وكتابة | يتحدث مجموعة فرعية من ISO GQL (ISO/IEC 39075) عبر نفس اتصال Bolt — مسارات كمية، ومقيدات مسار، ومحددات أقصر مسار، وتعبيرات منطقية للتسميات/الأنواع، وFOR، وCAST، وبادئة لهجة اختيارية GQL/CYPHER — ومع تفعيل الطبقة القابلة للكتابة، تنزل عبارات GQL لتعديل البيانات (INSERT / SET / REMOVE / [DETACH] DELETE) على نفس مسار الكتابة الدائم. Cypher وGQL، قراءات وكتابات، في محرك واحد. |
| متجهات + رسم بياني في محرك واحد | بحث ANN متجهي أصلي على القرص (Vamana + PQ؛ cosine / L2 / dot) للتضمينات/RAG، بالإضافة إلى خوارزميات الرسوم البيانية (PageRank وBFS وbetweenness وWCC…) — ذاكرة محدودة حتى مع ملايين المتجهات. التضمينات قابلة للكتابة (سلم كتابة بأسلوب FreshDiskANN): أدخل / حدّث / احذف متجهًا، مرئيًا لـ KNN فورًا، مطويًا في الأساس دون إعادة بناء. |
| آمن على تخزين الشبكة | كل ملف مُجزأ بـ BLAKE3 للمحتوى ومُتحقق منه عند الفتح؛ تُرفض الصور الممزقة أو نصف المنسوخة، ولا تُخدم. مصمم لأحجام NFS/البعيدة (بدون مفاجآت mmap). |
| خلفيات تخزين قابلة للتوصيل | اخدم نفس تنسيق الجيل من نظام ملفات محلي، أو دلو S3 (متوافق مع S3)، أو دلو Google Cloud Storage — انشر مرة واحدة، ووزّع على نسخ متماثلة عديمة الحالة — مع طبقة ذاكرة تخزين مؤقت SSD محلية اختيارية أمام مخزن الكائنات. راجع خلفيات التخزين. |
| المسار | الغرض | ملاحظات |
|---|
/data | توليدات الرسم البياني (<graph>/<uuid>/… + current). | للقراءة فقط للنسخ؛ يُنتج بواسطة slater-build. قد يعيش على تخزين بعيد/شبكي (مثل NFS)، لذا لا يُفترض أن تكون القراءات بسرعات SSD محلية سريعة. |
/sandbox | طبقة إعدادات التكوين لكل بيئة + الأسرار. | /sandbox/config.json يُدمج بعمق فوق config.json المدمج؛ يحتوي أيضاً على acl.json، ومواد PEM الخاصة بـ TLS، وملف المفتاح أثناء التخزين. |
/tmp, /run | مساحة مؤقتة (tmpfs). | نسخة القراءة لا تكتب على القرص أبداً افتراضياً. |
(الكاتب) delta.walDir | سجل الكتابة المسبقة + مقاطع دلتا L0، عند تفعيل delta.enabled. | قابل للكتابة، ووحدة تخزين حقيقية دائمة — أبداً tmpfs (هي أرضية المتانة). المسار النسبي يُحل تحت دليل البيانات؛ أعطِ الكاتب وحدة تخزين دائمة خاصة به هنا. |
| (اختياري) ذاكرة التخزين المؤقت على القرص | ذاكرة التخزين المؤقت للكتل على القرص المحلي، عند dataBackend.s3.diskCacheBytes / dataBackend.gcs.diskCacheBytes > 0. | قابلة للكتابة، ووحدة تخزين حقيقية — ليست tmpfs. تُستخدم بواسطة خلفيتي s3 و gcs؛ انظر خلفيات التخزين. |
read لا تمنح أي وصول للكتابة.["read", "write"]| المحرك | الفئة | حد الذاكرة |
|---|
| slater | مدعوم بالقرص، مُقسَّم إلى صفحات | query.maxIntermediate يحد مجموعة العمل تلقائيًا |
| Neo4j 5 | مدعوم بالقرص، JVM | ~2 غيغابايت كومة + خارج الكومة، ملتزم به بغض النظر عن الاستعلام |
| Memgraph · FalkorDB | في الذاكرة | الرسم البياني كاملًا مقيم في RAM |
| ArcadeDB | في الذاكرة، JVM | الرسم البياني كاملًا مقيم؛ الأثقل |
| LadybugDB | مضمّن، عمودي | تجمع مخازن يدوي يجب أن يتجاوز الاستعلام |
| الرسم البياني (العقد / الحواف) | slater | Neo4j 5 | Memgraph | FalkorDB | ArcadeDB | LadybugDB |
|---|
| pole — 62 ألف / 106 ألف | 11 | 746 | 114 | 140 | 1,556 | 198 |
| MeSH — 341 ألف / 469 ألف | 63 | 1,083 | 358 | 455 | 1,631 | 121 |
| EU-AI-Act — 21 ألف / 45 ألف (+55 MiB متجه) | 99 | 729 | 229 | 312 | 1,948 | 286 |
| Wikidata — 91.6 مليون / 1.5 مليار | 584 (4,595 إجمالي) | ~2,900 | لا يمكن التحميل | لا يمكن التحميل | لا يمكن التحميل | ~652 † |
| الشكل | 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 |
| بحث نقطة مفهرس | 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.28 | 5.8 | 1.21 | 4.1 | 390 | 4.9 |
| قفزتان (بدون مرساة) | 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 |
فحص كامل CONTAINS | 0.43 | 5.4 | 24.1 | 1.7 | 16.3 | 4.1 |
| الشكل | slater | Neo4j 5 | Memgraph | FalkorDB | LadybugDB |
|---|
| kNN أفضل 10 Concept | 2.9 | 8.6 | 1.9 | 1.2 | 2.8 |
| kNN أفضل 10 Chunk | 2.4 | 5.7 | 1.9 | 1.5 | 3.2 |
| الخاصية | المُقاس | لماذا يهم |
|---|
| زمن استجابة KNN مقابل الكتابات المعلقة | فهرس RW ~1.5–2 مللي ثانية، ثابت حتى 50 ألف معلق؛ الطبقة المتراكبة بالقوة الغاشمة قبل الفهرس 1.9 → 115 مللي ثانية (خطي في الدلتا) — 61× عند 50 ألف | زمن استجابة الاستعلام لا يتدهور مع تراكم الكتابات بين عمليات الدمج |
| إدراج التضمين | ~1.5–2 مللي ثانية لكل متجه في الفهرس الحي | الكتابة مرئية لـKNN فورًا؛ ميزانية إعادة بناء الدلتا ≈ 2 مللي ثانية × حد الدلتا |
| حذف IO عند استدعاء متساوٍ | 2.9× أقل جلب عقد لكل استعلام عند 67% محذوف، 5.2× عند 80% (استدعاء ≥ 0.90) | الرسم البياني المدمج لا يدفع ضريبة قراءة للمتجهات المحذوفة |
| الدمج، تبديل نقي | O(1) — .vamana مرتبط صلبًا بايتًا بايتًا، فقط عمود المعرّف يُعاد كتابته | طي كتابات المتجهات في القاعدة يتخطى إعادة بناء O(N·R·L) |
| الاستدعاء عبر السلم | المدمج ≥ القاعدة لجيب التمام وL2 والضرب النقطي | سلم الكتابة يحافظ على الاستدعاء في كل درجة |
| الشكل | slater (fan 1) | slater (fan 8) | Neo4j 5 |
|---|
| count(*) كل العقد | 0.41 | 0.41 | 3606 |
| بحث نقطة (مفهرس) | 0.72 | 0.49 | 6.3 |
| الدرجة (عدد قفزة واحدة) | 0.43 | 0.44 | 6.0 |
| جيران قفزة واحدة | 9.8 | 4.5 | 10.1 |
| قفزتان | 37 | 23 | 34.5 |
| ثلاث قفزات | 32 | 25 | 74 |
طول متغير *1..2 مميز | 985 | 1056 | 47 |
| البُعد | slater | أفضل المجال | الحكم |
|---|
| الذاكرة المقيمة، أي مقياس | 11–584 MiB (62 ألف → 91.6 مليون) | في الذاكرة 1.5–2.7 غيغابايت؛ لا يمكن تحميل 1.5 مليار | slater |
| count / البيانات الوصفية / الفحص | ~0.4 مللي ثانية | محركات الخدمة 5–80 مللي ثانية | slater (10–200×) |
| بحث نقطة مفهرس | 0.43 مللي ثانية (MeSH) | Memgraph · FalkorDB 0.48 مللي ثانية | slater (يتفوق على الزوج في الذاكرة) |
| قفزات متعددة بدون مرساة (صفوف) | 1.40 مللي ثانية (MeSH قفزتان) | Neo4j 5.6 مللي ثانية | slater (فحص نوع العلاقة) |
| التجميع (group-by / DISTINCT) | 0.45 مللي ثانية | LadybugDB 5 مللي ثانية (عمودي) | slater (رسم بياني في وقت البناء) |
| kNN | 2.4–2.9 مللي ثانية (دقيق) | FalkorDB 1.2 مللي ثانية (HNSW) | يتفوق على Neo4j/Ladybug؛ ~1.4× خلف Memgraph؛ دقيق |
| 91.6 مليون بيانات وصفية / نقطة / درجة / 3 قفزات | 0.4–32 مللي ثانية | Neo4j 6–3,600 مللي ثانية | slater (2–8800×) |
| 91.6 مليون 1–2 قفزة | 4.5–23 مللي ثانية (fan 8) | Neo4j 10–35 مللي ثانية | ~متساوٍ |
91.6 مليون طول متغير *1..2 مميز | ~1 ثانية | Neo4j 47 مللي ثانية | Neo4j (نقطة ضعف حقيقية في slater) |
count(*) متعدد القفزات على نطاق واسع | 0.3–0.6 غيغابايت | محركات الذاكرة تجسد مجموعة الصفوف | slater، محدود |
| النتيجة | القياس |
|---|
| يصمد حتى 1000 عميل متزامن، صفر فشل | الإنتاجية تبلغ ذروتها ~2.5 ألف طلب/ثانية؛ نقطة انعطاف زمن الاستجابة تبدأ حوالي 750 عميلًا (p99 51 → 750 مللي ثانية) — طابور تحت تنافس النوى، وليس حدًا صلبًا (تشغيل واحد، WSL2) |
| ذاكرة تخزين الكتل محدودة وفعالة | معدل إصابة 100%، صفر إخلاء، 50 ميغابايت مقيم لمجموعة عمل تناسب التخزين المؤقت |
| RSS محتجز تحت حمل مستمر | مُخصص jemalloc يحتفظ بـRSS عند ~0.6 غيغابايت عبر منحدر wiki_cache_churn من 100→500 عميل — مرتبط بالتخزين المؤقت ومستقر، مع عدم ضبط MALLOC_* (الحد السابق MALLOC_ARENA_MAX=2 + عتبة التقليم متقاعد)؛ تنظيفه الخلفي يعيد أيضًا الحد الأقصى بعد الاندفاع بدلًا من تركه مثبتًا |
| الذاكرة الإجمالية محدودة | query.maxIntermediateGlobal على مستوى الخادم + توسيع محسوب بالجوار يمسك فيضان wiki_budget ذي القفزتين عند 1000 عميل دون OOM (RSS ~0.6 غيغابايت؛ الحارس يتخلص من ~60% من استعلامات المحور كأخطاء ميزانية قابلة لإعادة المحاولة) |