SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-08-28

استضافة mem0 ذاتيًا على VPS لذاكرة الوكلاء

شغّل خادم ذاكرة mem0 على VPS خاص بك، مع الحد الأدنى الواقعي للذاكرة، وملف Compose يربط localhost، وTLS أمام API، ومسار Ollama محلي بالكامل.

ما التكلفة الفعلية لتشغيل mem0 ذاتياً على VPS من حيث الذاكرة

يعني تشغيل mem0 ذاتياً تشغيل ثلاث حاويات: خادم الذاكرة FastAPI، وPostgres مع إضافة pgvector، ولوحة تحكم Next.js. ‏mem0 طبقة ذاكرة للوكلاء. ترسل إليها محادثة، ويستخرج نموذج لغوي الحقائق الدائمة من تلك المحادثة، ثم تُخزَّن هذه الحقائق كمتجهات حتى يتمكن استعلام لاحق من استرجاع الحقائق ذات الصلة.

خطّط لاستهلاك نحو 1 GB من الذاكرة المقيمة للحاويات الثلاث، ولنحو 3 إلى 4 GB من مساحة القرص بعد إنشاء الصور. يعمل VPS بسعة 2 GB بشكل مريح عندما يكون النموذج اللغوي في مكان آخر. أما عندما يعمل النموذج على الخادم نفسه عبر Ollama، فإنه يستهلك معظم الموارد: يحتاج نموذج بسعة 8B، مكمّم إلى 4 بت، إلى نحو 6 GB بمفرده، ولذلك يبدأ الإعداد المحلي بالكامل من 8 GB.

لا تعتمد على هذه الأرقام الواردة في تدوينة، بما في ذلك هذه التدوينة. قِس المكدس الذي أنشأته فعلياً.

docker compose ps
docker stats --no-stream
docker system df -v

يطبع docker stats الذاكرة المقيمة لكل حاوية. ويطبع docker system df -v مساحة القرص التي تشغلها كل صورة وكل volume.

استهلاك الحالة المستقرة ليس ذروة الاستهلاك. يترجم docker compose up -d --build لوحة تحكم Next.js، ويكون إنشاء Node هذا أكثر مراحل التثبيت استهلاكاً للذاكرة. على VPS بسعة 1 GB، يوقفه قاتل نفاد الذاكرة في النواة، وينتهي الإنشاء بالرسالة exit code 137. أكّد السبب قبل أن تبحث عن خلل في Docker:

dmesg -T | grep -i "killed process"

إذا بدا الخادم أكبر من احتياجاتك، فالخيارات الأصغر مناسبة فعلاً. يتجاوز كل من مخزن ذاكرة محلي لوكيل دون خادم إطلاقاً وذاكرة تعمل داخل Claude Code نفسه قاعدة البيانات. ارجع إلى هنا عندما تحتاج عدة وكلاء، أو عدة أجهزة، إلى قراءة الذكريات نفسها.

هل أحتاج إلى Neo4j لذاكرة الرسم البياني في mem0؟

لا. إذا كان أحد الأدلة يطلب منك إضافة حاوية Neo4j، فهذا الدليل أقدم من الشيفرة الحالية.

كانت ذاكرة الرسم البياني في mem0 تعني سابقاً استخدام قاعدة بيانات رسم بياني خارجية، تُضبط ضمن مفتاح graph_store مع تعيين enable_graph إلى true. أزالت خوارزمية الذاكرة الجديدة، التي صدرت في April 2026، كلا المفتاحين من SDK مفتوح المصدر. يُجرى الآن استخراج الكيانات ضمن مسار add العادي، وتُكتب الكيانات في مجموعة pgvector ثانية يُشتق اسمها من اسم مجموعتك الرئيسية مع إلحاق _entities به. لا توجد عملية ترحيل يجب تشغيلها. ويبدأ ربط الكيانات المضمّن بالعمل عند استدعاء add التالي.

يؤدي حذف مخزن الرسم البياني إلى الاستغناء عن حاوية JVM وذاكرة heap الخاصة بها وعن عدة مئات من الميغابايتات من صورة الحاوية. وعلى VPS بسعة 2 GB، قد يكون ذلك الفارق بين تشغيل النظام واضطراره إلى استخدام swap.

إليك ما ستفقده بوضوح. كانت نتائج البحث تتضمن سابقاً حقلاً relations يسرد الحواف بين الكيانات. اختفى هذا الحقل. ترفع مطابقة الكيانات الآن ترتيب الذاكرة في النتيجة الموحّدة، ولا توجد بنية يمكنك اجتيازها. إذا كان تطبيقك يستعرض تلك العلاقات، فلن يحتفظ mem0 بها بعد الآن. وعليك الاحتفاظ بقاعدة بيانات رسم بياني خاصة بك خارج mem0 وتغذيتها من شيفرتك.

ملف Compose الموجود في المستودع مخصّص لبيئة التطوير

يعرّف server/docker-compose.yaml ‏name: mem0-dev، وهو يفعل ذلك فعلاً. اقرأه قبل تشغيله، لأن فيه خمسة إعدادات غير مناسبة للخادم.

  • يبني من server/dev.Dockerfile ويربط نسخة العمل لديك فوق الصورة باستخدام .:/app، لذلك تشغّل الحاوية ما يوجد في ذلك الدليل، بدلاً من ما بنيته.
  • الأمر المستخدم هو rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. يعيد ذلك تثبيت mem0ai من PyPI عند كل تشغيل، لذلك قد تتغير النسخة التي يشغّلها الخادم أثناء إعادة تشغيل لم تكن تقصد بها إجراء ترقية.
  • تعني خطوة pip نفسها أن إعادة التشغيل من دون اتصال صادر بالشبكة تفشل قبل تشغيل uvicorn. يتوقف خادم الذاكرة لديك لأن PyPI غير قابل للوصول.
  • يشغّل --reload مراقب ملفات uvicorn. وُجد هذا المراقب لإعادة تشغيل العملية عند تعديل الشيفرة، لكنه يستهلك الذاكرة ويشغّل عملية ثانية من دون فائدة في بيئة الإنتاج. ويتضمن Dockerfile الخاص بالإنتاج --reload في CMD أيضاً، لذلك عليك تجاوز الأمر في كلتا الحالتين.
  • المنافذ المنشورة هي "8888:8000" و"8432:5432" و"3000:3000". عندما لا يسبق المنفذ المنشور عنوان، فإنه يرتبط بـ0.0.0.0، لذلك يستجيب Postgres للإنترنت العام على المنفذ 8432 فور بدء المكدس.

تستحق النقطة الأخيرة تحذيراً مستقلاً. ينشر Docker المنفذ بكتابة قواعده الخاصة قبل السلسلة التي يديرها ufw، لذلك لا يغلق ufw deny 8432 منفذ الحاوية المنشور. يوضّح نشر Docker للمنافذ متجاوزاً ufw القواعد المعنية.

ملف Compose لخادم فعلي

اعمل داخل server/، واترك init-db.sh في موضعه، واستبدل docker-compose.yaml بهذا.

name: mem0

services:
  mem0:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:8888:8000"
    networks: [mem0_network]
    volumes:
      - mem0_history:/app/history
    depends_on:
      postgres:
        condition: service_healthy
    command: >
      sh -c "alembic upgrade head &&
             uvicorn main:app --host 0.0.0.0 --port 8000"
    environment:
      - PYTHONUNBUFFERED=1
      - DASHBOARD_URL=https://mem0.example.com
      - APP_DB_NAME=mem0_app
      - AUTH_DISABLED=false
      - MEM0_TELEMETRY=false

  postgres:
    image: pgvector/pgvector:pg17
    restart: unless-stopped
    shm_size: "128mb"
    networks: [mem0_network]
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -q -U ${POSTGRES_USER:-postgres}"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_db:/var/lib/postgresql/data
      - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh

  mem0-dashboard:
    build: ./dashboard
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    networks: [mem0_network]
    environment:
      - NEXT_PUBLIC_API_URL=https://mem0.example.com
      - API_INTERNAL_URL=http://mem0:8000
    depends_on:
      mem0:
        condition: service_started

volumes:
  postgres_db:
  mem0_history:

networks:
  mem0_network:
    driver: bridge

هناك خمسة تغييرات مهمة هنا، ولكل تغيير سبب.

يبدأ كل إدخال ports بـ 127.0.0.1، لذلك لا تقبل النواة هذه الاتصالات إلا من الخادم نفسه. تصل جميع الاتصالات الخارجية عبر الـReverse Proxy، وهو المكوّن الوحيد الذي يحتفظ بشهادة.

لا يحتوي Postgres على كتلة ports مطلقاً. يصل حاوي mem0 إليه عبر mem0_network باستخدام اسم الخدمة، لذلك لا يحقق نشر المنفذ 8432 أي فائدة، بل يفتح منفذاً إضافياً. استخدم docker compose exec postgres psql -U postgres عندما تحتاج إلى shell.

ينتقل السجل من bind mount في ./history إلى named volume. يربط bind mount البيانات بمسار واحد وuid واحد على هذا الخادم، بينما يُعد named volume كائناً يمكن لـDocker إنشاء snapshot له ونقله. يوضّح named volumes مقابل bind mounts متى يكون كل خيار مناسباً.

يحذف الأمر --reload ويُبقي على alembic upgrade head. احتفظ بخطوة الترحيل هذه. من دونها، يبدأ التطبيق باستخدام قاعدة بيانات بلا جداول، ويفشل كل طلب عند تنفيذ الاستعلام الأول.

NEXT_PUBLIC_API_URL هو عنوان URL الذي يستدعيه متصفحك، لذلك يجب أن يكون عنوان HTTPS العام، وليس http://mem0:8000. يضمّن Next.js كل قيمة NEXT_PUBLIC_ في وقت البناء، لذلك يتطلب تغييرها تنفيذ docker compose up -d --build mem0-dashboard. تؤدي إعادة التشغيل العادية إلى إبقاء القيمة القديمة مضمّنة في JavaScript، فيستدعي dashboard المضيف الخطأ.

توجد الأسرار في .env، ويبقى .env خارج الإنترنت

cd server
cp .env.example .env
openssl rand -hex 32    # paste into JWT_SECRET
openssl rand -hex 32    # paste into ADMIN_API_KEY
chmod 600 .env

اضبط POSTGRES_PASSWORD وJWT_SECRET وADMIN_API_KEY. واترك AUTH_DISABLED=false دون ضبط. يعبّر الاسم بوضوح عمّا يفعله هذا الخيار: عند تفعيله، يسلّم الخادم كل الذاكرة التي يحتفظ بها إلى أي جهة يمكنها الوصول إلى المنفذ. اضبط MEM0_TELEMETRY=false إذا كنت لا تريد إرسال حدث الإعداد الأولي إلى الجهة upstream.

تُقارَن قيمة ADMIN_API_KEY بترويسة X-API-Key باستخدام secrets.compare_digest، وتؤدي المطابقة إلى تجاوز جميع عمليات البحث في قاعدة البيانات. هذه بيانات اعتماد root لواجهة API بأكملها. تعامل معها على هذا الأساس: لا تضعها في سجل shell، ولا في git، ولا تلصقها في موجّه الأوامر. ينطبق كل من ملفات بيئة Compose والطرق التي تتسرب بها الأسرار منها وإبقاء مفاتيح API خارج سياق الوكيل مباشرة هنا، لأن مستهلكي هذا الخادم هم وكلاء.

توجد القيم المحمّلة من env_file في بيئة الحاوية، ويطبعها docker inspect بالكامل. يمكن لأي شخص في مجموعة docker قراءتها، وأي شخص في مجموعة docker يملك عملياً صلاحيات root على المضيف.

ضع TLS أمام واجهة API بدلاً من فتح المنفذ 8888

تستجيب واجهة API على 127.0.0.1:8888، وتعمل لوحة المعلومات على 127.0.0.1:3000. ينهي nginx طبقة TLS (أمان طبقة النقل) على المنفذ 443، ثم يوجّه الطلبات إلى كلتيهما.

server {
    listen 443 ssl;
    server_name mem0.example.com;

    ssl_certificate     /etc/letsencrypt/live/mem0.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mem0.example.com/privkey.pem;

    location ~ ^/(memories|search|configure|auth|api-keys|docs|openapi.json) {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 180s;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

تؤثر proxy_read_timeout أكثر مما يبدو. تتوقف عملية الإضافة أثناء قراءة نموذج اللغة للمحادثة واستخراج الحقائق منها. يستغرق نموذج محلي بحجم 8B يعمل على CPU وقتاً أطول من المهلة الافتراضية البالغة 60 ثانية في nginx بانتظام، وعندها يرى المستدعي 504 Gateway Time-out بينما يواصل النموذج عمله وتُحفَظ الذاكرة فعلياً. والنتيجة ذاكرة أُبلغت بفشل إنشائها رغم أنها حُفظت.

أغلق بقية المنافذ باستخدام سياسة ufw افتراضية تمنع كل شيء، مع إبقاء المنفذين 22 و443 مفتوحين. أصدر الشهادة باستخدام certbot على Ubuntu 24.04 خلف nginx. إذا كان الخادم يوجّه تطبيقات أخرى مسبقاً باستخدام Traefik لتوجيه عدة تطبيقات Compose، فأضف mem0 إلى ذلك الموجّه بدلاً من تثبيت Proxy ثانٍ.

اختبار بسيط: أضف ذاكرة واحدة واقرأها مجدداً

export MEM0_KEY='<the ADMIN_API_KEY from .env>'

curl -sS -X POST http://127.0.0.1:8888/memories \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"messages":[{"role":"user","content":"I deploy with Docker Compose and I run Postgres 17."}],"user_id":"smoke"}'

تكون الاستجابة السليمة كائن JSON يحتوي على قائمة results، ويضم كل إدخال قيمة id، والنص المستخرج memory، و"event": "ADD". تعيد الخوارزمية الحالية أحداث ADD فقط. أزيلت أحداث UPDATE وDELETE، لذلك لا يمثل غيابها خطأً.

curl -sS -X POST http://127.0.0.1:8888/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"query":"which database do I run?","filters":{"user_id":"smoke"},"top_k":5}'

يجب أن تعود المعلومة المتعلقة بـPostgres 17 مع درجة. مرّر المعرّف داخل filters كما هو موضح. لا يزال استخدام user_id في المستوى الأعلى صالحاً، ويسجّل الخادم Top-level user_id in /search is deprecated. Use filters={...} instead. في كل مرة تستخدمه.

نظّف ما أنشأته حتى لا تؤثر بيانات الاختبار في عمليات البحث الفعلية:

curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
  -H "X-API-Key: $MEM0_KEY"

إذا أعاد البحث صفوفاً أقل مما توقعت، فتحقق من القيم الافتراضية قبل إلقاء اللوم على الاسترجاع. في الإصدار الحالي، تكون القيمة الافتراضية لـtop_k هي 20، بعد أن كانت 100، وتكون القيمة الافتراضية لـthreshold هي 0.1 بدلاً من عدم وجود قيمة، لذلك تُستبعد المطابقات الضعيفة تلقائياً. بعد نجاح ذلك باستخدام curl، تكون نقاط النهاية نفسها هي التي تربطها بوكيل، سواء مباشرة أو عبر خادم MCP يعمل على VPS نفسه.

شغّل mem0 من دون أي مفتاح OpenAI

ابدأ بالعائق، لأنك ستصطدم به خلال الدقائق الخمس الأولى. تتضمن صورة الخادم مجموعة ثابتة من مكتبات المزوّدين، ويرفض /configure أي شيء خارجها:

LLM provider 'ollama' is not bundled in this image. Bundled providers: openai, anthropic, gemini. To use another provider, install its Python package, rebuild the container, and extend BUNDLED_LLM_PROVIDERS in server/main.py.

لا تحتاج إلى إعادة بناء أي شيء. يوفّر Ollama واجهة API متوافقة مع OpenAI على /v1، وتغطي /v1/chat/completions و/v1/embeddings، كما يقبل مزوّد openai في mem0 قيمة openai_base_url. وجّه هذا المفتاح إلى Ollama، وسينجح الفحص المضمّن لأن المزوّد هو فعلياً openai. العنوان فقط هو الذي يتغير.

أضف Ollama إلى مشروع Compose نفسه:

  ollama:
    image: ollama/ollama
    restart: unless-stopped
    networks: [mem0_network]
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_models:/root/.ollama

أضف ollama_models: ضمن مفتاح المستوى الأعلى volumes:، ثم اسحب نموذج محادثة واحداً ونموذج تضمين واحداً:

docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-text

إذا كان Ollama يعمل مسبقاً على المضيف كوحدة systemd، كما في تشغيل Ollama مباشرة على VPS، فلا توجّه الحاوية إلى 127.0.0.1:11434. داخل حاوية mem0، يكون 127.0.0.1 هو حاوية mem0. امنح خدمة mem0 قيمة extra_hosts: ["host.docker.internal:host-gateway"]، واضبط Environment="OLLAMA_HOST=0.0.0.0:11434" في ملف drop-in لـsystemd حتى يستمع Ollama على عنوان يمكن لشبكة bridge الوصول إليه، واترك المنفذ 11434 مغلقاً في الجدار الناري.

اسأل النموذج عن بُعد التضمين قبل إعداد أي شيء

تحدد هذه الخطوة وحدها ما إذا كان الاسترجاع سيعمل أصلاً.

ينشئ مخزن pgvector في mem0 جدوله بعرض متجه ثابت، هو vector vector(1536)، لأن embedding_model_dims تكون قيمته الافتراضية 1536، وهو عرض text-embedding-3-small من OpenAI. يعيد nomic-embed-text عدد 768 من القيم. لا يقارن أي جزء داخل mem0 هذين الرقمين، لذلك يظهر عدم التطابق من Postgres عند أول إدراج:

expected 1536 dimensions, not 768

لا تثق أيضاً بالرقم الوارد في هذه الفقرة. اسأل النموذج:

curl -sS http://127.0.0.1:11434/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"model":"nomic-embed-text","input":"dimension check"}' \
  | python3 -c "import json,sys; print(len(json.load(sys.stdin)['data'][0]['embedding']))"

يطبع ذلك العرض الذي يجب أن تستخدمه مجموعتك. اكتب الإعداد في ملف، لأن تمرير كلمة مرور Postgres ضمن اقتباس shell هو الطريقة التي تدخل بها الأخطاء المطبعية إلى بيئة الإنتاج.

{
  "vector_store": {
    "provider": "pgvector",
    "config": {
      "host": "postgres",
      "port": 5432,
      "dbname": "postgres",
      "user": "postgres",
      "password": "<POSTGRES_PASSWORD from .env>",
      "collection_name": "memories_local_768",
      "embedding_model_dims": 768
    }
  },
  "llm": {
    "provider": "openai",
    "config": {
      "model": "llama3.1:8b",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1",
      "temperature": 0.2
    }
  },
  "embedder": {
    "provider": "openai",
    "config": {
      "model": "nomic-embed-text",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1"
    }
  }
}
curl -sS -X POST http://127.0.0.1:8888/configure \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d @config.json

curl -sS http://127.0.0.1:8888/configure -H "X-API-Key: $MEM0_KEY"

يقرأ الاستدعاء الثاني الإعداد مجدداً. وهذا هو الفحص الذي يؤكد نجاح الكتابة. بعد ذلك كرر اختبار smoke test السابق.

هناك أربع تفاصيل في JSON هذا ليست واضحة، وكل واحدة منها تتسبب في تعطل شيء ما إذا أخطأت فيها.

api_key هي السلسلة ollama، ويتجاهل Ollama قيمتها. لا يمكن أن تكون فارغة، لأن مكتبة عميل OpenAI ترفع خطأ قبل خروج أي طلب من العملية عند عدم ضبط مفتاح. تعمل أي سلسلة غير فارغة.

يجب وضع embedding_model_dims في مخزن المتجهات، ولا يوجد عمداً embedding_dims في embedder. يرسل mem0 معامل OpenAI dimensions فقط عند ضبط embedding_dims، كما ترفض الخلفيات التي لا تطبق اقتطاع Matryoshka هذا المعامل مباشرة. اضبط العرض عند إنشاء الجدول، واترك embedder من دون تغيير.

collection_name جديد. ينشئ mem0 جدوله باستخدام CREATE TABLE IF NOT EXISTS، لذلك لا يؤدي توجيه عرض مختلف إلى مجموعة موجودة إلى أي تغيير: يبقى عمود vector(1536) القديم، ويفشل كل إدراج. يتطلب تغيير العرض اسم مجموعة جديداً، أو حذف الجدول القديم يدوياً.

المضيف في openai_base_url هو اسم خدمة Compose، أي ollama، وليس localhost. تحل الحاويات أسماء بعضها بعضاً باستخدام اسم الخدمة على الشبكة المشتركة.

تكلفة المسار المحلي بالكامل

كن صريحاً مع نفسك بشأن الجودة. قِيست نتائج benchmark المنشورة لـmem0 باستخدام نماذج متقدمة لتنفيذ الاستخراج، لذلك تعامل معها كسقف لا كتوقع لنموذج 8B على VPS لديك. يكتب النموذج الصغير حقائق أكثر غموضاً، وقد يعيد أحياناً نصاً نثرياً عندما يُطلب منه JSON، ويظهر ذلك في أن استدعاء add يعيد قائمة results فارغة من دون خطأ.

السرعة تكلفة أخرى. يستغرق الاستخراج باستخدام CPU فقط ثواني في كل استدعاء add، وتدفع هذه التكلفة مع كل رسالة تخزنها. يزيد النموذج الذي يسترسل بعد JSON المطلوب من المشكلة، لذلك فإن تحديد الرد باستخدام num_predict يضع حداً للمدة التي يمكن أن يستغرقها استدعاء add واحد. إذا كان هذا التأخير مهماً، فإن استخدام VPS مزود بـGPU هو الحل الفعلي. إن إضافة مزيد من أنوية CPU إلى نموذج 8B تساعد بدرجة أقل بكثير مما يتوقعه الناس. تغيير النموذج أقل تكلفة من تغيير الجهاز، ويوفر لك Nemotron 3.5 Lightning على VPS الوسم المطلوب سحبه، وذاكرة RAM التي يحتاج إليها، وما إذا كان استخدام CPU فقط سريعاً بما يكفي لقبوله.

تسري قاعدة واحدة مهما كان اختيارك: لا تخلط أبداً نماذج التضمين داخل مجموعة واحدة. ينتج نموذجان مختلفان يصادف أنهما يستخدمان العرض نفسه متجهات غير قابلة للمقارنة. ينجح الإدراج، ويعيد البحث صفوفاً، لكن الصفوف تكون خاطئة، من دون أن يبلغ أي جزء عن خطأ.

النسخ الاحتياطية: توجد قاعدتا بيانات، وليست قاعدة واحدة

أكثر أخطاء النسخ الاحتياطي شيوعاً في mem0 هو تفريغ قاعدة بيانات واحدة فقط. ينشئ init-db.sh قاعدة mem0_app إلى جانب قاعدة postgres الافتراضية، وتحتوي كل منهما على بيانات مختلفة. تحتوي قاعدة postgres على مجموعات pgvector، وهي الذكريات. أما mem0_app فتحتوي على المستخدمين والجلسات ومفاتيح API وسجلات الطلبات. تقسّم كل خدمة مستضافة ذاتياً حالتها بطريقة خاصة بها. لذلك، حتى خادمي صور ينفذان المهمة نفسها يحتاجان إلى أوامر نسخ احتياطي مختلفة. اقرأ ما تخزّنه خدمتك قبل أن تعتمد على ملف تفريغ. في الطرف الآخر من هذا النطاق توجد خدمة مثل مكتبة Jellyfin أُعيد بناؤها كمتجر فيديو من تسعينيات القرن الماضي، إذ تقرأ كامل فهرسها من خدمة أخرى، ولذلك لا تحتاج إلا إلى نسخ إعداداتها غالباً. أما mem0 فيحتاج إلى كلتا قاعدتي البيانات، وإلا فلن تكون عملية الاستعادة مفيدة.

إذا استعدت postgres فقط، فستعود الذكريات، لكن ستختفي كل الحسابات ومفاتيح API. لذلك لن يتمكن أي شيء من المصادقة لقراءتها. فرّغ القاعدتين، مع الأدوار، باستخدام أمر واحد:

docker compose exec -T postgres pg_dumpall -U postgres --clean \
  | gzip > "mem0-$(date +%F).sql.gz"

وحدة تخزين السجل منفصلة عن Postgres وتحتاج إلى نسخة خاصة بها:

docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
  alpine tar czf /backup/mem0-history.tgz -C /data .

تضيف Docker اسم المشروع إلى أسماء وحدات التخزين. لذلك تحقّق من اسم وحدتك باستخدام docker volume ls قبل افتراض mem0_mem0_history.

استعد البيانات في حاوية مؤقتة، وتحقق من أعداد الصفوف قبل الوثوق بأي منها:

gunzip -c mem0-2026-08-03.sql.gz \
  | docker compose exec -T postgres psql -U postgres -d postgres

النسخة الاحتياطية التي لم تستعدها من قبل ليست إلا تخميناً. بعد التأكد من صحة ملفات التفريغ، ادفعها خارج الخادم باستخدام لقطات restic إلى تخزين خارج الموقع، لأن النسخة الاحتياطية الموجودة على الخادم الذي تحميه لا تحمي شيئاً.

أوضاع الفشل والنصوص الدقيقة التي ستظهر

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} يعني أن الترويسة مفقودة أو مكتوبة بشكل خاطئ. الاسم هو X-API-Key، وcurl يرسل أسماء الترويسات حرفياً.

يشير {"detail":"At least one identifier (user_id, agent_id, run_id) is required."} في عملية الإضافة إلى أن الطلب لم يتضمن أياً منها. يجب تحديد نطاق الذاكرة لشيء ما، لأن البحث يطبّق عوامل التصفية على هذه الحقول تحديداً.

يعني LLM provider 'ollama' is not bundled in this image مع HTTP 400 أنك أرسلت "provider": "ollama". استخدم "provider": "openai" مع توجيه openai_base_url إلى Ollama.

يعني expected 1536 dimensions, not 768 من Postgres أن المجموعة أُنشئت بعرض معيّن، بينما يعيد embedder عرضاً مختلفاً. اضبط embedding_model_dims في مخزن المتجهات، واستخدم collection_name جديداً.

يعيد البحث صفوفاً غير منطقية بعد تغيير النموذج، من دون ظهور أي خطأ. ما زال العرض متطابقاً، لذلك لا ترى قاعدة البيانات مشكلة، لكن النموذجين يضعان الجملة نفسها في موضعين مختلفين. ابدأ مجموعة جديدة، ثم أعد الإضافة.

يعني Connection refused في سجلات mem0 أثناء الوصول إلى Ollama عادةً أن 127.0.0.1 في openai_base_url. داخل الحاوية، يشير ذلك العنوان إلى الحاوية نفسها. استخدم اسم الخدمة، أو بوابة المضيف عندما يعمل Ollama على المضيف.

يعني 504 Gateway Time-out من nginx أثناء الإضافة أن النموذج استغرق وقتاً أطول من proxy_read_timeout. ارفع القيمة، وتحقق مما إذا كانت الذاكرة قد كُتبت فعلاً قبل إعادة محاولة الطلب.

يعني exit code 137 أثناء docker compose up --build أن قاتل نفاد الذاكرة أوقف عملية بناء لوحة المعلومات. أضف swap، أو ابنِ الصورة على جهاز أكبر ثم ادفعها إلى سجل.

يصدر error: port 3000 is already in use من هدف make up في المستودع، الذي يرفض البدء عندما يكون المنفذان 3000 أو 8888 مستخدمين. اعثر على العملية المالكة باستخدام lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

هل ما زلت بحاجة إلى Neo4j لتشغيل mem0 مع ذاكرة الرسم البياني؟

لا. أزالت خوارزمية الذاكرة الجديدة، التي أُطلقت في April 2026، مفتاحَي الإعداد graph_store وenable_graph من SDK مفتوح المصدر. يُجرى الآن استخراج الكيانات أثناء عملية add عادية، وتُكتب النتائج في مجموعة pgvector ثانية باسم <collection_name>_entities، لذلك لا تحتاج إلى قاعدة بيانات رسوم بيانية خارجية، أو حاوية إضافية، أو خطوة ترحيل. لكن الحقل relations لم يعد موجوداً في نتائج البحث. ترفع الكيانات الآن ترتيب الذاكرة بدلاً من توفير حواف يمكنك اجتيازها. لذلك يحتاج التطبيق الذي كان يتنقل عبر تلك العلاقات إلى مخزن رسوم بيانية خاص به خارج mem0.

ما أصغر VPS يمكنه تشغيل خادم mem0 مستضاف ذاتياً؟

إذا كان نموذج اللغة مستضافاً في مكان آخر، تكفي ذاكرة RAM بسعة 2 GB ونحو 4 GB من مساحة القرص الحرة لحاوية API وPostgres ولوحة المعلومات. تحدث المشكلة الأكبر أثناء أول عملية build، لأن تجميع لوحة Next.js يستهلك ذاكرة أكبر من تشغيلها، ويتوقف build على جهاز بسعة 1 GB بسبب exit code 137. إذا شغّلت Ollama على الخادم نفسه، فحدّد الموارد وفقاً للنموذج بدلاً من ذلك. يحتاج نموذج 8B بكمّية 4-bit إلى نحو 6 GB بمفرده، لذلك خطط لاستخدام 8 GB.

هل يمكنني تشغيل mem0 من دون مفتاح OpenAI API؟

نعم، عبر نقطة النهاية المتوافقة مع OpenAI في Ollama. يفشل ضبط "provider": "ollama" لأن صورة الخادم تتضمن فقط مكتبات openai وanthropic وgemini، وتعيد HTTP 400. بدلاً من ذلك، اترك "provider": "openai" كما هو واضبط "openai_base_url": "http://ollama:11434/v1" على أي api_key غير فارغ، لكل من llm وembedder. يتجاهل Ollama المفتاح، ويمر فحص الموفر المضمّن لأن الموفر هو openai فعلاً.

لماذا لا يعيد mem0 أي نتائج بعد التبديل إلى نموذج تضمين محلي؟

لأن جدول pgvector أُنشئ بعرض ثابت. تكون القيمة الافتراضية لـembedding_model_dims هي 1536، بينما يعيد nomic-embed-text القيمة 768، ويرفض Postgres عملية الإدراج مع expected 1536 dimensions, not 768. ينشئ mem0 الجدول باستخدام CREATE TABLE IF NOT EXISTS، لذلك لا يؤدي تغيير الرقم وحده إلى أي شيء في مجموعة موجودة. اضبط embedding_model_dims على العرض الفعلي لنموذجك، وتحقق من هذا العرض باستدعاء /v1/embeddings وعدّ القيم التي يعيدها، وأنشئ في الوقت نفسه collection_name جديداً لمخزن المتجهات.