Skip to main content
Django إطار عمل ويب مكتوب بلغة بايثون، يتضمن أداة مدمجة لربط الكائنات بقواعد البيانات (ORM) ونظامًا مدمجًا للترحيل، ويتواصل مع PostgreSQL عبر المشغّل psycopg 3. ستربط في هذا الدليل مشروع Django جديدًا بـ ClickHouse Managed Postgres عبر PgBouncer، وتنشئ جدول todos باستخدام عملية ترحيل، ثم تعالج طلبات الإنشاء والقراءة والتحديث والحذف من خلال عرض JSON بسيط. ويستخدم كل اتصال بروتوكول TLS مع التحقق الكامل من الشهادة.

المتطلبات الأساسية

  • بايثون 3.12 أو إصدار أحدث، كما يتطلب Django 6. اختُبر هذا الدليل باستخدام بايثون 3.13 وDjango 6.1.1 وpsycopg 3.3.6.
  • حساب على ClickHouse Cloud
  • psql، لإنشاء قاعدة البيانات. ويمكنك أيضًا تنفيذ عبارة CREATE DATABASE في SQL console.

إنشاء خدمة ClickHouse Managed Postgres

في وحدة تحكم ClickHouse Cloud، انقر على New service ثم اختر Postgres. سيصبح المثيل جاهزًا في غضون دقائق قليلة. للاطلاع على شرح مفصّل للخطوات، راجع دليل البدء السريع.

احصل على بيانات الاتصال

انقر على Connect في الشريط الجانبي الأيسر لخدمتك. تعرض النافذة المنبثقة اسم المستخدم وكلمة المرور والخادم والمنفذ، كما تحتوي على مفتاح تبديل بين Directly وvia PgBouncer. يربط هذا الدليل Django عبر PgBouncer المضمَّن على المنفذ 6432. عادةً ما يشغّل تطبيق Django في بيئة الإنتاج عدة عمليات عامل على عدة أجهزة، وتفتح كلٌّ منها اتصالاتها الخاصة. يعمل PgBouncer في وضع تجميع المعاملات، فيتيح لجميع هذه العمليات أن تتشارك عددًا محدودًا من اتصالات Postgres. ويتطلب Django بعض الإعدادات ليعمل مع تجميع المعاملات، وقد تضمّنها هذا الدليل وشرحها في قسم إعدادات PgBouncer لـ Django. اختر via PgBouncer لعرض بيانات الاتصال عبر التجميع. سيتغيّر المنفذ إلى 6432، وستتضمن سلسلة الاتصال sslmode=verify-full. في النافذة المنبثقة، انقر على Download CA certificate. ويمكنك أيضًا تنزيلها من Settings → CA Certificate. هذه الشهادة خاصة بمثيلك وحده، لذا يستطيع المشغّل استخدامها للتحقق من أنه يتصل بخادمك فعلًا.

إعداد المشروع

أنشئ دليلًا للمشروع يحتوي على بيئة افتراضية، ثم ثبّت Django وpsycopg 3، وأنشئ مشروع Django يضم تطبيق todos:
تتضمن الحزمة psycopg[binary] إصدارًا حديثًا من libpq، لذا لن تحتاج إلى تثبيت PostgreSQL محليًا.
هل تضيفه إلى تطبيق قائم؟تخطَّ الأوامر السابقة. شغّل pip install "psycopg[binary]" داخل البيئة الافتراضية لمشروعك، ثم انتقل إلى قسم تهيئة الاتصال، الذي يستبدل إعداد DATABASES الخاص بـ SQLite. ولنسخ بيانات SQLite:
  • قبل تغيير DATABASES، شغّل python manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.permission -o data.json.
  • بعد تشغيل python manage.py migrate، شغّل python manage.py loaddata data.json.

تهيئة الاتصال

انقل شهادة CA التي نزّلتها إلى دليل المشروع، بجوار manage.py، وأعد تسميتها إلى ca-certificate.pem. أنشئ قاعدة بيانات للتطبيق، مع استبدال <PASSWORD> والمضيف بالقيم الظاهرة في النافذة المنبثقة Connect:
عيّن المضيف وكلمة المرور على هيئة متغيرات بيئة، حتى لا تُضمَّن في الشيفرة البرمجية:
افتح الملف mysite/settings.py، وأضف import os أسفل from pathlib import Path، ثم استبدل الإعداد DATABASES بما يلي:
mysite/settings.py
يمرّر Django المفاتيح الموجودة في OPTIONS إلى psycopg، الذي يسلّمها بدوره إلى libpq. وعند ضبط sslmode على verify-full، يتحقق libpq من أن شهادة الخادم موقّعة من الـ CA الخاص بخدمتك وأنها تطابق اسم المضيف. أما BASE_DIR فيجعل مسار الشهادة مطلقًا، فيعمل الإعداد أيًّا كان الدليل الذي تشغّل Django منه.
إذا كان sslrootcert يشير إلى CA غير صحيح، يفشل الاتصال برسالة الخطأ SSL error: certificate verify failed. وفي غياب sslrootcert، يفشل برسالة الخطأ root certificate file ".../.postgresql/root.crt" does not exist، لأن libpq يلجأ حينها إلى موقع الـ CA الافتراضي الخاص به. وإذا اتصلت باستخدام عنوان IP بدلًا من اسم المضيف، يفشل برسالة الخطأ server certificate for "..." does not match host name.
إذا كان تطبيقك يقرأ DATABASE_URL باستخدام dj-database-url، فإن هذه المكتبة تمرّر sslmode وsslrootcert من سلسلة الاستعلام في الـ URL إلى OPTIONS. استدعِ dj_database_url.config(conn_max_age=600, conn_health_checks=True, disable_server_side_cursors=True) للحصول على الإعدادات نفسها. ويُحدَّد مسار sslrootcert النسبي في الـ URL انطلاقًا من الدليل الذي تشغّل Django منه.

إنشاء نموذج وتنفيذ عمليات الترحيل

أضف التطبيق todos إلى INSTALLED_APPS في الملف mysite/settings.py:
mysite/settings.py
عرِّف نموذج Todo في الملف todos/models.py:
todos/models.py
أنشئ ترحيلاً للنموذج، ثم طبّق جميع عمليات الترحيل:
إلى جانب todos_todo، ينشئ الأمر migrate جداول تطبيقات Django المدمجة، مثل auth_user وdjango_session، ويسجّل عمليات الترحيل المطبّقة في django_migrations. أما في تطبيق قائم، فتخطَّ إنشاء نموذج todos واكتفِ بتنفيذ python manage.py migrate، الذي ينشئ جميع جداول تطبيقك في قاعدة البيانات الجديدة. لا يحصل migrate على قفل استشاري على مستوى الـ session، لذا يعمل عبر PgBouncer دون مشكلات. لكنه في المقابل لا يمنع التشغيل المتزامن، لذا نفّذ عمليات الترحيل من مكان واحد فقط، كخطوة واحدة ضمن عملية النشر.

إضافة نقطة نهاية JSON

استبدل محتوى todos/views.py بدالتَي عرض (views) تتوليان إنشاء المهام واستعراضها وتحديثها وحذفها باستخدام Django أداة ربط الكائنات بقواعد البيانات:
todos/views.py
استبدل محتوى mysite/urls.py لتوجيه المسارين /todos و/todos/<id> إلى دوال العرض:
mysite/urls.py

التشغيل والتحقق

شغّل خادم التطوير:
في طرفية ثانية، أنشئ مهمتين، ثم حدِّد الأولى على أنها مُنجَزة، واحذف الثانية:
يُعيد طلب PATCH الصفَّ المُحدَّث:
افتح http://localhost:3111/todos في المتصفح لعرض المهام المتبقية. ستظهر الاستجابة على النحو التالي:
لعرض البيانات في وحدة التحكم، افتح SQL console من الشريط الجانبي الأيسر لخدمتك، ثم وسّع guide_django يليه public، وانقر على الجدول todos_todo. سيظهر لك أن الجدول يحتوي على الصف المتبقي:

إعدادات PgBouncer لـ Django

يعمل PgBouncer في وضع تجميع المعاملات: إذ يمكن تنفيذ كل معاملة، أو كل عبارة خارج معاملة، على اتصال Postgres مختلف. وفيما يلي كيف يؤثر ذلك في الإعدادات السابقة وفي ميزات Django الأخرى:
  • DISABLE_SERVER_SIDE_CURSORS: يقرأ QuerySet.iterator الصفوف على شكل مقاطع عبر مؤشر من جهة الخادم. وخارج المعاملة، يُعرِّف Django المؤشر بصيغة WITH HOLD ويجلب كل مقطع على حدة، لذا قد يصل طلب الجلب عند المرور عبر PgBouncer إلى اتصال Postgres لا يحتوي على المؤشر، فيفشل مع الخطأ cursor "_django_curs_..." does not exist. ولا يحدث ذلك إلا عندما يستخدم عملاء آخرون مجمّع الاتصالات في الوقت نفسه، ولهذا كثيرًا ما لا يظهر أثناء التطوير. وعند تفعيل هذا الإعداد، يستخدم .iterator() مؤشرًا عاديًا، ويُحمِّل psycopg النتيجة كاملةً في الذاكرة. ولبثّ جدول كبير جدًا، شغِّل تلك الشيفرة عبر الاتصال المباشر على المنفذ 5432 مع تعطيل الإعداد.
  • CONN_MAX_AGE: افتراضيًا (0)، يفتح Django اتصالًا جديدًا لكل طلب. ويتطلب فتح اتصال TLS عدة رحلات ذهاب وإياب عبر الشبكة، سواء عبر PgBouncer أو مباشرةً، لذا فإن إعادة استخدام الاتصالات تُسرّع الطلبات كثيرًا. وفي اختبار أُجري من حاسوب محمول بأربعة عمّال Gunicorn، أدى ضبط CONN_MAX_AGE على 600 إلى خفض الزمن الوسيط للطلب من نحو 670 ms إلى 95 ms. وعبر PgBouncer، لا يحتجز اتصال Django المفتوح اتصال Postgres بين المعاملات، لذا لا تستنفد الاتصالات الدائمة الحد max_connections. ويجعل CONN_HEALTH_CHECKS نظام Django يتحقق من الاتصال المُعاد استخدامه في بداية كل طلب، ويعيد الاتصال إذا كان قد أُغلق.
  • مجمّع الاتصالات: يوفّر المجمّع المدمج في Django ("pool": True ضمن OPTIONS، ويتطلب pip install "psycopg[binary,pool]") تسريعًا مماثلًا، ويعمل كذلك عبر PgBouncer. ويشترط ضبط CONN_MAX_AGE على 0، ويُبقي أربعة اتصالات مفتوحة على الأقل لكل عملية. ومع PgBouncer، يكفي CONN_MAX_AGE لمعظم التطبيقات؛ أما الخوادم متعددة الخيوط أو خوادم ASGI فيُستحسن أن تستخدم المجمّع.
  • العبارات المُحضَّرة: لا حاجة إلى أي إعداد. إذ يضبط Django قيمة prepare_threshold في psycopg على None ويربط معلمات الاستعلام لدى العميل افتراضيًا، فلا يُنشئ عبارات مُحضَّرة من جهة الخادم. وإذا فعّلت "server_side_binding": True وضبطت "prepare_threshold" ضمن OPTIONS، فستعمل العبارات المُحضَّرة أيضًا عبر PgBouncer، لأن psycopg[binary] يتضمن libpq 18 ويحرّر العبارات برسالة Close على مستوى البروتوكول. أما مع psycopg المبني على libpq 16 أو أقدم، فيفشل تحرير العبارة داخل المعاملة مع الخطأ prepared statement "_pg3_0" does not exist. وللتحقق من إصدارك، شغِّل python -c "import psycopg; print(psycopg.pq.version())"؛ إذ يلزمك الإصدار 170000 أو أحدث.
  • حالة الجلسة: لا تسري عبارة SET، مثل SET search_path، إلا على اتصال Postgres الذي نفّذها، لذا قد لا تراها الاستعلامات اللاحقة، بينما قد يراها عملاء آخرون. ويرفض PgBouncer إعداد Django "options": "-c search_path=..." مع الخطأ unsupported startup parameter in options: search_path. لذا اضبط القيم الافتراضية على مستوى قاعدة البيانات بدلًا من ذلك، مثلًا باستخدام ALTER DATABASE guide_django SET search_path = myschema, public;. ولا تستخدم الخيار assume_role لأنه ينفّذ SET ROLE. أما معالجة Django الخاصة لـ TIME_ZONE والخيار isolation_level فيعملان عبر PgBouncer دون مشكلات.
وللاتصال مباشرةً بدلًا من ذلك، اضبط PORT على 5432 واحذف DISABLE_SERVER_SIDE_CURSORS. عندئذٍ يحتفظ كل عامل Django باتصال Postgres خاص به، لذا احرص على أن يبقى العدد الإجمالي للعمّال أقل من max_connections.

الخطوات التالية