Home تقنية Postgres LISTEN/NOTIFY يتوسع فعليًا | itg-ar.com

Postgres LISTEN/NOTIFY يتوسع فعليًا | itg-ar.com

7
0
Postgres LISTEN/NOTIFY يتوسع فعليًا
| itg-ar.com

Postgres LISTEN/NOTIFY يتوسع فعليًا

يتمتع Postgres LISTEN/NOTIFY بسمعة سيئة ويرجع الفضل في ذلك جزئيًا إلى منشور مدونة شائع يؤكد أنه لا يتوسع. إذا كان ذلك صحيحًا، فسيكون ذلك عارًا، لأن LISTEN/NOTIFY هي أداة قوية، تتيح لك استخدام قاعدة بيانات Postgres الخاصة بك للإشعارات الدائمة ذات زمن الوصول المنخفض، والتدفقات، والنشر/الفرعية. الاتهامات ليست خاطئة: يتمتع NOTIFY بخصائص أداء غير بديهية وغير موثقة ناشئة عن استخدامه للقفل العالمي. لكن “السلوك غير البديهي” ليس مثل “غير قابل للتطوير”. في منشور المدونة هذا، سنعرض كيف قمنا بتحسين التدفقات المدعومة بالاستماع/الإخطار على نطاق واسع، وتحقيق 60 ألف كتابة في الثانية على خادم Postgres واحد مع زمن استجابة بمقياس مللي ثانية. البث بزمن وصول منخفض مع الاستماع/الإخطار التصميم الأساسي للتدفقات المدعومة بـ Postgres بسيط: قم بإنشاء جدول تدفقات حيث تكون كل مجموعة تدفق (على سبيل المثال، رمز استجابة LLM) عبارة عن صف جديد، ثم الكتابة إلى التدفقات عن طريق الإدراج في الجدول.‍ الجزء الصعب هو القراءة من الدفق لأنك لا تعرف متى ستصل القطعة التالية. أحد الحلول هو الاستقصاء: اطلب من كل قارئ الاستقصاء في نهاية الدفق للأجزاء الجديدة. ومع ذلك، فإن مستوى الاقتراع ضعيف. إذا تم تعيين الفاصل الزمني للاستقصاء على مستوى عالٍ جدًا، فسيكون زمن الاستجابة مرتفعًا جدًا بالنسبة لحالات الاستخدام التفاعلية (على سبيل المثال، الدردشات عبر الإنترنت). ولكن إذا تم تعيين الفاصل الزمني للاستقصاء على مستوى منخفض للغاية، فإن أدوات الاستقصاء المتزامنة ستطغى على قاعدة البيانات. والحل الأفضل هو الاستماع/الإخطار. يتيح ذلك للقراء حظر انتظار إشعار من الكاتب بأنه قد تم نشر جزء جديد في الدفق. بهذه الطريقة، لا يهدر القراء موارد الاستقصاء، ولكنهم يستيقظون فورًا عند وصول مجموعة دفق جديدة. في تطبيقنا الأولي للتدفقات المستندة إلى LISTEN/NOTIFY، أطلق مشغل في جدول التدفقات وظيفة ترسل إشعارًا في كل مرة تتم فيها كتابة مجموعة دفق جديدة. انتظر القراء هذه الإشعارات واستيقظوا على مجموعة تدفق جديدة. كان هذا التنفيذ صحيحًا وقدم زمن استجابة منخفضًا، ولكن على نطاق واسع كان معدل النقل ضعيفًا. حتى باستخدام قاعدة بيانات Postgres الكبيرة، لم تتمكن من الحفاظ على أكثر من 2.9 ألف عملية كتابة للتدفق في الثانية. ومن المثير للاهتمام أنه تم اختناقه دون استهلاك أي موارد Postgres بشكل واضح (وحدة المعالجة المركزية أو الذاكرة أو IOPS). كما كنت قد خمنت، كان السبب الجذري هو المشكلة الأصلية “LISTEN/NOTIFY غير قابلة للتطوير”: القفل العام الذي يأخذه Postgres أثناء NOTIFY. ولكن لماذا يفعل Postgres ذلك، وكيف يمكننا تحسينه دون فقدان فوائد إشعارات Postgres؟ قفل الاستماع/الإخطار الحصري لفهم المشكلة، سنحتاج إلى فحص كيفية عمل Postgres LISTEN/NOTIFY فعليًا. السبب الجذري لضعف الأداء هو أنه في Postgres، يتطلب تنفيذ معاملة تستدعي NOTIFY اتخاذ قفل حصري عالمي. يتم أخذ هذا القفل عندما تبدأ المعاملة في الالتزام، ولا يتم تحريره حتى يتم الالتزام بالمعاملة بالكامل ويتم مسح محتوياتها إلى القرص باستخدام fsync (). يعد هذا القفل ضروريًا لأن Postgres يضمن إرسال الإشعارات بترتيب التزام المعاملة. لفرض ذلك، يقوم بتخزين جميع الإشعارات الصادرة في قائمة انتظار داخلية عامة يجب أن يتطابق ترتيبها تمامًا مع ترتيب الالتزام للمعاملات التي ترسل تلك الإشعارات. يجب أن تتم إضافة الإشعارات إلى قائمة الانتظار هذه بشكل معاملي كجزء من الالتزام. ومع ذلك، لا يقوم Postgres بتعيين أمر التزام للمعاملات حتى يتم تنفيذ هذه المعاملات، حيث يمكن أن يستغرق الالتزام وقتًا متغيرًا. وهذا يخلق مشكلة في الطلب: يجب أن تضيف المعاملات التي تحتوي على إشعارات نفسها إلى قائمة الانتظار بترتيب الالتزام، ولكن لا يتم تحديد ترتيب الالتزام حتى يكتمل الالتزام. الحل هو القفل العام، الذي يقوم بإجراء تسلسل لالتزامات المعاملات التي تحتوي على إشعارات، بحيث يتم تحديد ترتيب الالتزام الخاص بهم مسبقًا ويمكنهم ترتيب أنفسهم بشكل صحيح في قائمة انتظار الإشعارات الداخلية. يشرح هذا القفل الحصري الأداء الضعيف الذي لاحظناه. نظرًا لأننا نستدعي NOTIFY من مشغل في جدول التدفقات، فإن كل كتابة للتدفق تتضمن استدعاء NOTIFY. من أجل الالتزام، تحتاج كل كتابة دفق إلى أخذ القفل العام والاحتفاظ به طوال مدة التزامه بالكامل، بما في ذلك التدفق إلى القرص. هذا يعني أن عمليات الكتابة المتدفقة تحتاج إلى الالتزام بشكل تسلسلي، مما يحول دون تحسينات Postgres المعتادة مثل الالتزام الجماعي (الذي يرتكب العديد من المعاملات معًا في fsync() واحد). ونتيجة لذلك، لا يمكن إكمال عمليات كتابة الدفق بشكل أسرع من قدرة Postgres على تنفيذ المعاملات، مما يؤدي إلى هذا الاختناق. وهذا يفسر أيضًا سبب عدم رؤيتنا لاستهلاك كبير لأي من موارد Postgres مثل وحدة المعالجة المركزية أو القرص: لم يكن هناك أي استهلاك، لأن جميع المعاملات تم تسلسلها بواسطة قفل عام. وبصرف النظر عن ذلك، كانت هناك بعض المناقشات عبر الإنترنت حول تصحيح Postgres المتعلق بهذه المشكلة. لا يؤدي هذا التصحيح (الذي سيتم إصداره في Postgres 19) إلى إزالة القفل العام أو إصلاح عنق الزجاجة الذي لاحظناه. بدلاً من ذلك، فهو يعمل على تحسين الحالة الأضيق حيث يوجد العديد من قنوات الإشعارات وكل مستمع ينتظر فقط على قناة معينة. تحسين الاستماع/الإخطار لجعل التدفقات المدعومة بالاستماع/الإخطار أسرع، يتعين علينا التغلب على عنق الزجاجة هذا. الملاحظة الأساسية هي أنه بالنسبة للتدفقات، وبالنسبة للعديد من تطبيقات الاستماع/الإشعار الأخرى، فإن الإشعارات ليست في حد ذاتها مصدرًا للحقيقة. وبدلاً من ذلك، يقومون فقط باختبار اتصال القارئ للتحقق من جدول قاعدة البيانات (المصدر الحقيقي للحقيقة) بحثًا عن بيانات جديدة. ونتيجة لذلك، ليس من الضروري أن تكون الإشعارات مرتبة عالميًا أو متينة تمامًا، لذا يمكننا تحسين NOTIFY عن طريق تخزين الإشعارات مؤقتًا في الذاكرة ومسحها بشكل دوري في معاملة دفعة واحدة، مما يقلل بشكل كبير من التنافس على القفل العام. يتجنب التخزين المؤقت والإشعارات المجمعة عنق الزجاجة لأن القفل العام يجب أن يتم اتخاذه فقط عند مسح المخزن المؤقت، وليس لكل كتابة دفق فردي. هذا يعني أن عمليات الكتابة الفردية للتدفق يمكن أن تستمر بسرعة، مع الاستفادة من تحسينات Postgres مثل الالتزام الجماعي للحصول على إنتاجية عالية، بينما يتدفق المخزن المؤقت في الخلفية. يؤدي اعتماد المخزن المؤقت إلى تعقيد جديد، وهو أن تعطل العملية أثناء تخزين الإشعارات مؤقتًا يؤدي إلى عدم تسليم تلك الإشعارات أبدًا. لحل هذه المشكلة، أضفنا احتياطيًا لقراء الدفق: بالإضافة إلى انتظار الإشعارات، يقومون أيضًا باستقصاء قاعدة البيانات بشكل دوري للتحقق مما إذا كان الدفق قد تمت كتابته بدون إشعار. يمكن أن يكون تكرار هذا الاستقصاء منخفضًا (لأنه مجرد بديل للإشعارات التي لم يتم تسليمها)، لذلك لا يؤثر بشكل كبير على الأداء. وبمقارنة هذا الحل الأمثل، نرى أداءً محسنًا بشكل كبير: في ظل وجود أجهزة قراءة متزامنة، يمكننا إجراء ما يصل إلى 60 ألف عملية كتابة دفق في الثانية (20 مرة أكثر من ذي قبل) مع الاستمرار في الحصول على زمن استجابة يتراوح بين 15 إلى 100 مللي ثانية. عند الحد الأقصى من الإنتاجية، يتم استخدام وحدة المعالجة المركزية Postgres بالكامل، مما يُظهر أن قاعدة البيانات مشبعة بالفعل بدلاً من أن تكون مقيدًا عند التنافس. تعرف على المزيد جميع التعليمات البرمجية المعيارية متاحة على GitHub: github.com/dbos-inc/dbos-postgres-benchmark إذا كنت ترغب في إنشاء أنظمة موثوقة وقابلة للتطوير، فنحن نود أن نسمع منك. في DBOS، هدفنا هو جعل التنفيذ الدائم المدعوم من Postgres بسيطًا وعالي الأداء قدر الإمكان. اطلع عليها:


تم النشر: 2026-07-24 20:05:00

مصدر: www.dbos.dev