عندما يحتاج تطبيقك إلى تحديثات آنية، تكون الإجابة الافتراضية في 2025 اللجوء لخدمة مُدارة: Firebase Realtime Database أوSupabase Realtime أوPusher أوAbly أوطبقة WebSocket مستضافة. تخفي هذه الخدمات تعقيد دفع التحديثات إلى العملاء المتصلين. تعمل جيدًا — إلى أن لا تفعل.
في Atlas Verified، بدأنا بمنصة قاعدة بيانات مُدارة تتضمن اشتراكات آنية. ومع نمو نظامنا بلغنا حدود ذلك التجريد. دفعتنا قيود تجميع الاتصالات ومشكلات الموثوقية الآنية والحاجة إلى استعلامات PostGIS المكانية والرغبة في منطق محفزات مخصص جميعها في اتجاه واحد: التحكم بطبقة القاعدة.
انتقلنا إلى PostgreSQL مستضاف ذاتيًا وبنينا النظام الآني بآلية LISTEN/NOTIFY الأصلية مع الأحداث المرسلة من الخادم. يغطي هذا المنشور سبب القرار وكيف تعمل البنية وما تعلمناه.
لماذا تركنا المنصة المُدارة
توفر منصات قواعد البيانات المُدارة قيمة مقنعة: توثيق الهوية وأمنًا على مستوى الصفوف واشتراكات آنية وواجهات API مولّدة تلقائيًا ولوحة — كلها جاهزة. للمنتجات المبكرة، هذا قيّم فعلًا. يمكنك بناء تطبيق عامل دون كتابة سطر واحد من شيفرة الخادم.
تظهر المشكلات مع نضج تطبيقك واختلاف متطلباتك عن افتراضات المنصة.
حدود الاتصالات. توجّه المنصات المُدارة غالبًا اتصالات القاعدة عبر مجمّع اتصالات يفرض حدوده الخاصة. عندما تملك عدة خدمات خلفية تحتاج كل منها إلى مجموعات اتصالاتها — خادم API ونظام عمال خلفية ومستمعًا آنيًا — تستنفد المتاح سريعًا. وجدنا أنفسنا نصمم حلولًا حول قيود المجمّع بدل حل مشكلات المنتج الفعلية.
الموثوقية الآنية. عمل النظام الآني للمنصة المُدارة عبر التقاط تغيرات البيانات في سجل الكتابة المسبقة لـPostgreSQL. هذا أنيق بنيويًا لكنه أدخل أنماط إخفاق لا نتحكم فيها. للتكرار القائم على WAL تأخير متأصل. كانت الخدمة الآنية تتأخر أحيانًا وتقدم تحديثات قديمة. وكانت إعادة الاتصال بعد انقطاع الشبكة غير موثوقة. وتطلب تصحيح هذه المشكلات وصولًا إلى بنية لا نتحكم فيها.
متطلبات الامتدادات. يعالج تطبيقنا مستندات تجارية تشمل بيانات جغرافية: مواقع موانئ ومسارات شحن ومواقع سفن. PostGIS — الامتداد المكاني لـPostgreSQL — أساسي لهذه الاستعلامات. دعمت المنصة المُدارة PostGIS، لكننا احتجنا إصدارات وإعدادات محددة لم تكن متاحة دائمًا.
منطق محفزات مخصص. احتجنا محفزات قاعدة تطلق أحداثًا خاصة بالتطبيق — لا مجرد إشعارات عامة «تغير صف». عندما تتغير حالة معالجة مستند، نحتاج إخطار العميل المتصل المحدد الذي رفعه. كان النظام الآني للمنصة على مستوى الجدول؛ واحتجناه على مستوى الكيان.
لم يكن قرار الانتقال متعلقًا بالتكلفة أو المبدأ. بل عمليًا: كنا نقضي وقتًا في الالتفاف حول قيود المنصة أكثر مما كنا سنقضيه لبناء الميزات بأنفسنا.
LISTEN/NOTIFY: النشر والاشتراك الخفي في PostgreSQL
تتضمن PostgreSQL آلية نشر واشتراك مدمجة منذ الإصدار 9.0، الصادر في 2010. ورغم عمرها الذي يتجاوز 15 عامًا، فإن LISTEN/NOTIFY قليلة الاستخدام بصورة ملحوظة. لا يعرف معظم المطورين وجودها.
الآلية بسيطة. يستطيع أي اتصال قاعدة تنفيذ LISTEN على قناة مسماة للاشتراك. ويمكن لأي اتصال تنفيذ NOTIFY لإرسال رسالة إلى كل المستمعين على القناة. الحمولة نص محدود بـ8,000 بايت.
ما يجعل LISTEN/NOTIFY قوية للتطبيقات الآنية:
دون عبء بنية تحتية إضافي. لا وسيط رسائل منفصل للنشر أو المراقبة أو التوسيع. إذا كانت لديك PostgreSQL، فلديك نشر واشتراك. لا Redis ولا RabbitMQ ولا Kafka — لهذه الحالة المحددة.
ضمانات المعاملات. تُسلَّم رسائل NOTIFY فقط عند تثبيت المعاملة الحاوية لها. إذا أدرجت معاملة صفًا وأرسلت إشعارًا ثم تراجعت، لا يُسلَّم الإشعار أبدًا. لا تحصل على هذه الخاصية من وسطاء رسائل خارجيين دون تنسيق دقيق.
تكامل المحفزات. تستطيع محفزات PostgreSQL استدعاء pg_notify ضمن تنفيذها. أي يمكنك جعل القاعدة تخطر المستمعين تلقائيًا عند تغير بيانات محددة، دون شيفرة تطبيق في مسار الكتابة.
الحدود حقيقية وتستحق الفهم:
لا حفظ دائم. إذا لم يكن أحد يستمع عند إطلاق NOTIFY، تُفقد الرسالة. لا إعادة تشغيل ولا قائمة انتظار رسائل ولا ضمان تسليم للعملاء غير المتصلين. يجب أن يعالج المستمعون إعادة الاتصال واستعادة الحالة.
حجم الحمولة. يعني حد 8,000 بايت لحمولة الإشعارات أنك لا تستطيع إرسال كائنات كبيرة عبر NOTIFY. نرسل حمولة محدودة — اسم الجدول ونوع العملية والمفتاح الأساسي — وندع العميل يجلب البيانات كاملة عند الحاجة.
نطاق قاعدة واحدة. تعمل LISTEN/NOTIFY ضمن نسخة PostgreSQL واحدة. ولا تمتد عبر النسخ المتماثلة أو العناقيد. للبنى التي تذهب كل كتاباتها إلى قاعدة رئيسية واحدة، ليس ذلك قيدًا.
بنيتنا: المحفزات وSSE وتعدد إرسال القنوات
إليك كيف تتكامل الأجزاء.
محفزات قاعدة البيانات تُثبَّت على الجداول التي تهم فيها التحديثات الآنية: المستندات والرسائل والمحادثات والإشعارات وتحققات الشحنات وغيرها. عند إدراج صف أو تحديثه أو حذفه، يعمل المحفز ويستدعي pg_notify بحمولة JSON تحوي اسم الجدول ونوع العملية والمفتاح الأساسي وأي أعمدة تصفية.
اتصال مستمع مخصص في خادم API لدينا ينفذ LISTEN على قناة الإشعارات ويتلقى كل أحداث المحفزات. هو اتصال PostgreSQL مخصص طويل العمر — منفصل عن مجموعة اتصالات الاستعلام — لا يفعل إلا الاستماع للإشعارات.
ناقل أحداث يتلقى الإشعارات من المستمع ويوجهها إلى العملاء المتصلين وفق اشتراكاتهم. العميل المشترك في مستندات يطابق فيها معرّف شحنة معين يتلقى فقط إشعارات مستندات تلك الشحنة.
الأحداث المرسلة من الخادم توصّل الإشعارات إلى المتصفح. يفتح كل عميل متصل اتصال SSE واحدًا بخادم API، ويحدد القنوات والمرشحات المطلوبة. يطابق ناقل الأحداث الإشعارات الواردة باشتراكات العملاء ويدفع الأحداث المطابقة عبر اتصالات SSE المناسبة.
لماذا SSE بدل WebSockets
ظل هذا قرارًا نسعد به باستمرار. الأحداث المرسلة من الخادم أبسط من WebSockets في كل جانب يهم حالة استخدامنا.
أحادية الاتجاه بحكم التصميم. احتياجاتنا الآنية من الخادم إلى العميل: يخبر الخادم العميل بتغير البيانات. لا يحتاج العميل لإرسال رسائل آنية للخادم — يستخدم طلبات HTTP القياسية لذلك. يطابق نموذج SSE أحادي الاتجاه متطلبنا الفعلي.
أصلية في HTTP. اتصالات SSE طلبات HTTP قياسية بنوع محتوى محدد. تعمل عبر كل وكيل HTTP وموازن تحميل وCDN دون إعداد خاص. تتطلب WebSockets ترقية HTTP، التي لا تتعامل معها بعض الوكلاء صحيحًا وتحظرها بعض جدران الشركات النارية بالكامل.
إعادة اتصال تلقائية. تعالج EventSource API في المتصفح إعادة الاتصال أصليًا. إذا انقطع الاتصال، يعيد المتصفح الاتصال تلقائيًا بتباعد أُسّي. مع WebSockets، تنفذ منطق إعادة الاتصال بنفسك — وتخطئ فيه مرة واحدة على الأقل.
المجال الوحيد الذي تتفوق فيه WebSockets — الاتصال ثنائي الاتجاه — لا نحتاجه ببساطة. للتطبيقات التي تتطلب رسائل آنية من العميل للخادم، WebSockets هي الخيار الصحيح. وللتطبيقات التي تدفع إشعارات من الخادم للعميل، SSE هي الخيار الصحيح.
بناء طبقة استعلام دون ORM
عندما تركنا المنصة المُدارة، تركنا أيضًا API الاستعلام المولّدة تلقائيًا لديها. احتجنا طبقة وصول للقاعدة لخادم API.
الخيارات القياسية هي ORM مثل Prisma أوTypeORM أوDrizzle — أو SQL خام مع برنامج تشغيل. اخترنا طريقًا ثالثًا: منشئ استعلامات خفيفًا يوفر واجهة API قابلة للتسلسل ويولّد وينفذ SQL خامًا تحتها.
API مألوفة. كُتبت شيفرتنا الحالية لمكتبة عميل المنصة المُدارة. عنت واجهة متوافقة تغييرات شيفرة قليلة أثناء الانتقال. استطاعت الاستعلامات التي كانت تعمل عبر عميل المنصة العمل عبر منشئنا بصياغة مطابقة.
دون غموض. تولّد ORMs شيفرة SQL لا تراها وأحيانًا لا تتحكم فيها. عندما يكون الاستعلام بطيئًا، تحتاج قراءة SQL المولّدة التي غالبًا لا تشبه كثيرًا استدعاء ORM الذي أنتجها. يولّد منشئنا SQL متوقعة — ترتبط كل طريقة متسلسلة بجملة SQL محددة، ويمكنك دائمًا فحص الاستعلام المولّد.
روابط مدمجة. البيانات التجارية علائقية. للشحنة مستندات، ولها عقد، ولها فحوص تحقق. القدرة على التعبير عن اختيارات متداخلة واسترجاع نتيجة متداخلة — دون كتابة استعلام JOIN معقد يدويًا كل مرة — تحسين كبير لتجربة المطور.
النتيجة طبقة استعلام ببضع مئات من أسطر الشيفرة، تعالج الغالبية العظمى من تفاعلات القاعدة، وتنتج SQL نستطيع دائمًا قراءتها وتحسينها.
ميزانية مجموعات الاتصالات
عندما تستضيف PostgreSQL ذاتيًا، تملك ميزانية الاتصالات. يحدد إعداد max_connections السقف المطلق، ويستهلك كل اتصال ذاكرة.
اكتشفنا أن التفكير في الاتصالات كميزانية — مورد محدود تشترك فيه الخدمات — يمنع فئة مشكلات تصيب الفرق التي لا تخطط لها.
تعلن كل خدمة أقصى عدد اتصالاتها. يجب أن يبقى مجموع الحدود أقل بكثير من حد PostgreSQL، مع مساحة لاتصالات طارئة — جلسات psql ونصوص الانتقال وأدوات الرصد. يبدو هذا بديهيًا، لكن معظم الفرق لا تفعله حتى تستنفد الاتصالات في الإنتاج. بوضع الميزانية مسبقًا، تجنبنا ذلك التنبيه عند 3 صباحًا.
انتقالات آمنة على نطاق واسع
عنى الانتقال من منصة مُدارة إلى PostgreSQL مستضاف ذاتيًا نقل مئات الجداول والدوال والمحفزات والفهارس. لكن التحدي الأكبر كان بناء نظام انتقال يواصل العمل مع تطور التطبيق.
يعمل نظام انتقالنا الآلي هكذا: تُسمى ملفات الانتقال بطوابع زمنية وتوجد في مجلد مخصص. يمسح مشغّل الانتقال المجلد، ويقارنه بجدول تتبّع يسجل ما طُبق، وينفذ الانتقالات المعلقة بالترتيب. يعمل كل انتقال داخل معاملة — إذا فشل، يتراجع بالكامل ويمنع النشر حتى إصلاح المشكلة.
يعمل مشغّل الانتقال تلقائيًا في كل نشر، قبل بدء التطبيق. أي تُطبق تغييرات المخطط ضمن النشر دون خطوات يدوية. ينشئ المطور ملف SQL بطابع زمني، ويدفعه إلى المستودع، ويطبقه النشر التالي.
قرارات تصميم رئيسية تحفظ الأمان: تستخدم الانتقالات حواجز تضمن قابلية التكرار دون أثر إضافي حتى لا تفشل إن طُبقت جزئيًا. يجب أن يكون كل انتقال نص SQL مستقلًا يعمل دون تدخل بشري. وإذا فشل، تتراجع المعاملة بالكامل — فلا يبدأ التطبيق بتغيير مخطط مطبق جزئيًا.
ما كسبناه
كان الانتقال إلى PostgreSQL مستضاف ذاتيًا مع نظام آني مخصص استثمارًا هندسيًا كبيرًا. لكن العوائد كانت كبيرة:
الموثوقية. تصل الإشعارات الآنية خلال أجزاء من الألف من الثانية من تغير البيانات الأساسي. لا تأخر تكرار WAL ولا انقطاعات خدمات طرف ثالث في المسار الحرج.
سهولة تصحيح الأخطاء. عند الخلل، نستطيع تتبّع المسار كاملًا: يعمل المحفز، ويُرسل الإشعار، ويوجّه ناقل الأحداث، وتوصّل SSE. كل خطوة في شيفرتنا وسجلاتنا وبنيتنا.
المرونة. إضافة التحديثات الآنية إلى جدول جديد تتطلب انتقال SQL واحدًا يثبت محفزًا. لا إعداد منصة ولا لوحة طرف ثالث ولا تذكرة دعم.
الأداء. تتفوق اتصالات PostgreSQL المباشرة مع مجموعة اتصالات مضبوطة على الاتصالات الموجّهة عبر بنية منصة مُدارة. انخفض زمن الاستعلام بصورة ملموسة بعد الانتقال.
المقابل هو المسؤولية التشغيلية. ندير القاعدة والنسخ الاحتياطية، ونراقب التكرار، ونخطط للسعة. هذا قابل للإدارة لفريق متمكن من تشغيل PostgreSQL. أما غير المتمكن، فقد تستحق تجريدات المنصة المُدارة حدودها.
يعمل Atlas Verified على PostgreSQL مستضاف ذاتيًا ببنية آنية مخصصة، ويعالج آلاف تحققات الامتثال التجاري يوميًا عبر سلسلة التوريد العالمية.
