Skip to main content
يُعدّ pgx مشغّل PostgreSQL الأكثر استخدامًا في لغة Go، ويمثّل pgxpool مجمّع الاتصالات الخاص به. ستتعلّم في هذا الدليل كيفية ربط خدمة مكتوبة بلغة Go بـ ClickHouse Managed Postgres عبر اتصال TLS مع التحقق من الشهادة، وإنشاء جدول todos من خلال عملية ترحيل باستخدام golang-migrate، ثم معالجة طلبات الإنشاء والقراءة والتحديث والحذف عبر خادم net/http.

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

  • Go الإصدار 1.25 أو أحدث. اختُبر هذا الدليل باستخدام Go 1.27.1 وpgx 5.11.0 وgolang-migrate 4.20.1.
  • حساب على ClickHouse Cloud
  • psql، لإنشاء قاعدة البيانات. ويمكنك أيضًا تنفيذ عبارة CREATE DATABASE في SQL console.

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

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

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

انقر على Connect في الشريط الجانبي الأيسر لخدمتك. أبقِ الخيار Directly محددًا وفعّل Use SSL. سيصبح عنوان URL للاتصال في علامة التبويب url مستخدمًا للمنفذ 5432 ومنتهيًا بـ sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem. انقر على Download CA certificate. ويمكنك أيضًا تنزيلها لاحقًا من Settings → CA Certificate. هذه الشهادة فريدة لمثيلك، ولذلك يستطيع pgx استخدامها للتحقق من أنه يتصل بالخادم الخاص بك فعلًا. يتصل هذا الدليل مباشرةً بـ Postgres عبر المنفذ 5432، سواء لعمليات الترحيل أو للتطبيق. فخدمة Go عملية طويلة الأمد، وتحتفظ pgxpool فيها أصلًا بمجمّع اتصالات، لذا لا حاجة إلى وسيط تجميع اتصالات ثانٍ. أما إذا كنت تشغّل مثيلات عديدة من الخدمة وتحتاج إلى عدد من اتصالات العملاء يتجاوز ما يسمح به Postgres، فاستخدم PgBouncer المضمّن (via PgBouncer، المنفذ 6432) للتطبيق؛ راجع استخدام PgBouncer، إذ يتطلب ذلك إعداد اتصال إضافيًا واحدًا.

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

أنشئ وحدة Go، وأضف pgx، ثم ثبّت واجهة سطر الأوامر migrate مع مشغّل pgx:
تعمل العلامة -tags 'pgx5' على تصريف واجهة سطر الأوامر migrate مع مشغّل pgx v5 الخاص بها، والذي يستخدم مخطط URL ‏pgx5://. يضع الأمر go install الملف التنفيذي في $GOBIN، أو في $(go env GOPATH)/bin إذا لم يكن GOBIN معيّنًا، لذا تأكد من إضافة هذا الدليل إلى PATH لديك.
هل تضيفه إلى تطبيق قائم؟تخطَّ الأمرين mkdir وgo mod init. شغّل في الوحدة الخاصة بك الأمر go get github.com/jackc/pgx/v5 وأمر go install المذكور أعلاه، ثم انتقل إلى تهيئة الاتصال. إذا كانت شيفرتك تستخدم database/sql، فإن stdlib.OpenDBFromPool تغلّف مجمّع الاتصالات نفسه في صورة *sql.DB، مما يتيح لك ترحيل الاستعلامات الحالية تدريجيًا؛ مع استبدال العناصر النائبة ? بـ $1 و$2 وهكذا.

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

انقل شهادة CA التي نزّلتها إلى دليل المشروع، ثم أعد تسميتها إلى ca-certificate.pem. أنشئ قاعدة بيانات للتطبيق، ثم استبدل <PASSWORD> والمضيف بالقيم الظاهرة في النافذة المنبثقة Connect:
عيّن متغيرَي بيئة في الـ shell. يشير كلاهما إلى قاعدة البيانات guide_go نفسها، ولا يختلفان إلا في مخطط URL؛ إذ يقرأ التطبيق المتغير DATABASE_URL، بينما تستخدم واجهة سطر الأوامر migrate المتغير MIGRATE_URL:
يحلّل pgx عنوان URL بالطريقة نفسها التي يتبعها libpq. فعند ضبط sslmode=verify-full، يحمّل ملف CA من sslrootcert ويتحقق من سلسلة الشهادات واسم المضيف معًا، لذا لا حاجة إلى كتابة أي شيفرة خاصة بـ TLS. يُحسب مسار sslrootcert نسبةً إلى الدليل الذي تشغّل منه الأوامر. وإذا احتوت كلمة المرور على محارف خاصة مثل @ أو / أو #، فرمّزها بترميز URL.
اضبط دائمًا sslmode=verify-fullعند عدم تحديد sslmode، يستخدم pgx القيمة prefer التي لا تتحقق من شهادة الخادم حتى لو ضبطت sslrootcert. أما عند ضبط sslmode=verify-full مع ملف CA غير صحيح، فيفشل pgx بالخطأ x509: certificate signed by unknown authority، وإذا لم يكن الملف موجودًا، يفشل بالخطأ unable to read CA file. وإذا اتصلت باستخدام عنوان IP بدلًا من اسم المضيف، يفشل بالخطأ x509: cannot validate certificate for <IP> because it doesn't contain any IP SANs.

إنشاء المخطط باستخدام الترحيل

أنشئ ملفات الترحيل:
أكمِل محتوى ملف الترحيل up:
migrations/000001_create_todos.up.sql
وإليك عملية الترحيل down التي تُلغي هذه التغييرات:
migrations/000001_create_todos.down.sql
طبّق الترحيل:
تسجّل أداة migrate الإصدار الحالي في جدول schema_migrations، ولا تطبّق في المرات اللاحقة إلا عمليات الترحيل الأحدث. ثبّت دليل migrations مع الشيفرة الخاصة بك.
شغّل migrate دائمًا عبر الاتصال المباشر (المنفذ 5432)، إذ تحتفظ الأداة طوال تشغيلها بقفل استشاري على مستوى الـ session، كي لا تنفّذ عمليتا نشر الترحيل في الوقت نفسه. أما عبر PgBouncer، فقد يُنفَّذ القفل وفكّ القفل على اتصالَي Postgres مختلفين، فيظل القفل قائمًا، وتتوقف عمليات تشغيل migrate اللاحقة عن الاستجابة أو تفشل مع الخطأ timeout: can't acquire database lock.

الاستعلام من قاعدة البيانات

أنشئ الملف db.go. يُنشئ هذا الملف مجمّع الاتصالات انطلاقًا من DATABASE_URL، ويتحقق من الاتصال عند بدء التشغيل بالاستعلام من Postgres عن إصدار TLS الذي تستخدمه الـ session:
db.go
أنشئ الملف todos.go الذي يتضمن معالجات HTTP. ينفّذ كل معالج استعلامًا واحدًا ذا معلمات عبر مجمّع الاتصالات، وتتولى الدالتان pgx.CollectRows وpgx.CollectExactlyOneRow فحص الصفوف وتعبئتها في البنية Todo بحسب اسم العمود:
todos.go
يؤدي تمرير القيم كوسائط ($1) بدلاً من دمجها داخل string الـ SQL إلى منع هجمات حقن SQL، إذ يرسلها pgx بمعزل عن نص الاستعلام. ولا حاجة إلى التحقق من error الذي تُعيده pool.Query على حدة، لأن pgx.CollectRows وpgx.CollectExactlyOneRow تُعيدانه. أنشئ الملف main.go. يُنشئ هذا الملف مجمّع اتصالات واحداً للعملية بأكملها، ويخدم المسارات على المنفذ 3114، ويُغلق مجمّع الاتصالات عند الضغط على Ctrl+C. أما إذا كان لديك تطبيق قائم، فاستدعِ newPool مرة واحدة عند بدء التشغيل، ثم استدعِ registerTodoRoutes على http.ServeMux الخاص بك بدلاً من ذلك:
main.go

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

أضف التبعيات المتبقية الخاصة بـ pgxpool إلى الملفين go.mod وgo.sum، ثم شغّل الخادم:
تأتي القيمة TLSv1.3 من pg_stat_ssl على الخادم، ما يؤكد أن الـ session مشفّرة. وإذا وجّهت sslrootcert إلى أي شهادة أخرى، فلن يبدأ الخادم وسيسجّل الخطأ x509: certificate signed by unknown authority، ما يُثبت أن الشهادة تخضع للتحقق فعلًا. في طرفية ثانية، أنشئ مهمتين، ثم علّم الأولى على أنها مكتملة، واحذف الثانية:
يُعيد طلب PATCH الصفَّ المُحدَّث:
افتح http://localhost:3114/todos في متصفحك لعرض المهام المتبقية. تُعيد pgx قيم timestamptz وفق المنطقة الزمنية المحلية لديك. وتبدو الاستجابة كما يلي:
أوقف الخادم بالضغط على Ctrl+C. سيسجّل الرسالة Shutting down ثم يُغلق مجمّع الاتصالات. لعرض البيانات في وحدة التحكم، افتح SQL console من الشريط الجانبي الأيسر لخدمتك، ووسّع guide_go ثم public، ثم انقر على جدول todos. أما جدول schema_migrations المجاور له، فيحتفظ بإصدار الترحيل. ويحتوي جدول todos على الصف المتبقي:

استخدام PgBouncer

لتمرير استعلامات التطبيق عبر PgBouncer المضمَّن، اختر via PgBouncer في نافذة Connect، وغيّر المنفذ في DATABASE_URL إلى 6432، ثم أضف default_query_exec_mode=cache_describe. واحرص على إبقاء MIGRATE_URL على المنفذ 5432:
تقرأ pgx المعامل default_query_exec_mode من URL ولا ترسله إلى الخادم. لا حاجة إلى أي تغييرات في الشيفرة، وتظل شهادة CA نفسها صالحة. يعرض سجل بدء التشغيل الآن TLS: none، لأن pg_stat_ssl يُبلغ عن اتصال PgBouncer نفسه بـ Postgres. أما اتصال تطبيقك بـ PgBouncer فلا يزال يخضع للتحقق، إذ يفشل الاتصال بالطريقة نفسها عند استخدام شهادة CA خاطئة.
لا تستخدم وضع الاستعلام الافتراضي في pgx عبر PgBouncerتُحضّر pgx افتراضيًا كل استعلام كعبارة مُسمّاة مرة واحدة لكل اتصال، ثم تعيد استخدام أنواع الأعمدة التي حصلت عليها. ويُحضّر PgBouncer هذه العبارات على أي اتصال Postgres يتولى تنفيذ الاستعلام، ويشاركها بين العملاء. لذا بعد أن تُغيّر عملية ترحيل نوع عمود، أو تضيف عمودًا إلى جدول يُستعلم عنه باستخدام SELECT *، قد تظل الاستعلامات عبر PgBouncer تفشل مع الخطأ cached plan must not change result type حتى بعد إعادة تشغيل التطبيق، أو قد تُعيد، في pgx 5.9 والإصدارات الأحدث، قيمًا خاطئة دون ظهور أي خطأ. فمثلًا، يُعاد عمود من النوع bigint غُيّر إلى double precision على هيئة البتات الخام لقيمة float64، مثل 4631178160564600832 بدلًا من 42.5.أما الوضع cache_describe فلا يُخزّن مؤقتًا سوى أنواع المعلمات والأعمدة، ويُنفّذ كل استعلام كعبارة غير مُسمّاة في رحلة ذهاب وإياب واحدة، ويفك ترميز الصفوف باستخدام أنواع الأعمدة التي يُعيدها الخادم مع كل نتيجة، لذا لا يتأثر بهذه المشكلة. ويعمل الوضع exec أيضًا. لا تستخدم describe_exec عبر PgBouncer، فهو يُقسّم كل استعلام على رحلتي ذهاب وإياب قد يُرسلهما PgBouncer إلى اتصالي Postgres مختلفين. ولا حاجة إلى ضبط statement_cache_capacity=0.
يعمل PgBouncer في وضع تجميع المعاملات، لذا قد تُنفَّذ كل معاملة على اتصال Postgres مختلف:
  • تعمل المعاملات المُنشأة باستخدام pool.Begin أو pgx.BeginFunc كالمعتاد.
  • لا تبقى إعدادات session المضبوطة باستخدام SET مرتبطة باتصالك، وقد تتسرّب إلى عملاء آخرين. استخدم SET LOCAL داخل معاملة بدلًا من ذلك. كما يرفض PgBouncer المعامل search_path في URL الاتصال مع الخطأ unsupported startup parameter: search_path؛ لذا اكتب أسماء الجداول مسبوقة باسم المخطط بدلًا من ذلك.
  • لا تعمل الأوامر PREPARE وEXECUTE على مستوى SQL، ولا LISTEN، ولا الأقفال الاستشارية على مستوى session.

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

  • الاتصال: سلاسل الاتصال، وPgBouncer، وTLS
  • الإعدادات: معلمات Postgres وPgBouncer، مثل max_connections
  • النسخ المتماثلة للقراءة: وجّه الاستعلامات المخصصة للقراءة فقط إلى نسخة متماثلة باستخدام pgxpool.Pool ثانٍ
  • الأمان: قوائم التحكم في الوصول حسب عناوين IP، والشبكات الخاصة