ضبط أداء Hyperscale (Citus)

ينطبق على: قاعدة بيانات Azure لـ PostgreSQL - المقياس الفائق (Citus)

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

تجميع الاتصال من جانب العميل

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

تعد إضافة تجمع اتصال من جانب العميل طريقة سهلة لتعزيز أداء التطبيق بأقل قدر من تغييرات التعليمات البرمجية. في قياساتنا، يعمل تشغيل عبارات إدراج صف واحد بشكل أسرع بنسبة 24x على مجموعة خوادم Hyperscale (Citus) مع تمكين التجميع.

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

ملاحظة

يوفر Hyperscale (Citus) أيضًا تجمع الاتصال من جانب الخادم باستخدام pgbouncer، ولكنه يعمل بشكل أساسي على زيادة حد اتصال العميل. يستفيد أداء التطبيق الفردي أكثر من التجميع من جانب العميل بدلًا من التجميع من جانب الخادم. (على الرغم من أنه يمكن استخدام كلا الشكلين من التجميع في وقت واحد دون ضرر.)

نطاق الاستعلامات الموزعة

التحديثات

عند تحديث جدول موزع، حاول تصفية الاستعلامات في عمود التوزيع - على الأقل عندما يكون من المنطقي، عندما لا تغير عوامل التصفية الجديدة معنى الاستعلام.

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

على سبيل المثال، في البرنامج التعليمي متعدد المستأجرين لدينا جدول ads موزع بواسطة company_id. الطريقة الساذجة لتحديث الإعلان هي أن يتم فرده على النحو التالي:

-- slow

UPDATE ads
   SET impressions_count = impressions_count+1
 WHERE id = 42; -- missing filter on distribution column

على الرغم من أن الاستعلام يعرف صفًا بشكل فريد ويحدثه، إلا أن Hyperscale (Citus) لا يعرف، في وقت التخطيط، الجزء الذي سيتم تحديث الاستعلام. يأخذ Citus ShareUpdateExclusiveLock على جميع الأجزاء لتكون آمنة، ما يمنع الاستعلامات الأخرى التي تحاول تحديث الجدول.

على الرغم من أن id كان كافيًا لتحديد صف، يمكننا تضمين عامل تصفية إضافي لجعل الاستعلام أسرع:

-- fast

UPDATE ads
   SET impressions_count = impressions_count+1
 WHERE id = 42
   AND company_id = 1; -- the distribution column

يرى مخطط استعلام Hyperscale (Citus) عامل تصفية مباشر على عمود التوزيع ويعرف بالضبط أي جزء واحد يجب تأمينه. في اختباراتنا، زادت إضافة عوامل التصفية لعمود التوزيع من أداء التحديث المتوازي بمقدار 100x.

الصلات وCTEs

لقد رأينا كيف يجب أن يتم تحديد نطاق عبارات UPDATE حسب عمود التوزيع لتجنب تأمين الأجزاء غير الضرورية. تستفيد الاستعلامات الأخرى من النطاق أيضًا، عادة لتجنب حمل الشبكة لخلط البيانات دون داع بين العقد العاملة.

-- logically correct, but slow

WITH single_ad AS (
  SELECT *
    FROM ads
   WHERE id=1
)
SELECT *
  FROM single_ad s
  JOIN campaigns c ON (s.campaign_id=c.id);

يمكننا تسريع الاستعلام عن طريق التصفية على عمود التوزيع، company_id، في CTE وعبارة SELECT الرئيسية.

-- faster, joining on distribution column

WITH single_ad AS (
  SELECT *
    FROM ads
   WHERE id=1 and company_id=1
)
SELECT *
  FROM single_ad s
  JOIN campaigns c ON (s.campaign_id=c.id)
 WHERE s.company_id=1 AND c.company_id = 1;

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

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

تسجيل فعال لقاعدة البيانات

تسجيل جميع عبارات SQL طوال الوقت يضيف حملًا. في قياساتنا، باستخدام مستوى سجل أكثر حكمة حسن المعاملات في الثانية بمقدار 10x مقابل التسجيل الكامل.

للعملية اليومية الفعالة، يمكنك تعطيل التسجيل باستثناء الأخطاء والاستعلامات طويلة الأمد بشكل غير طبيعي:

إعداد القيمة السبب
log_statement_stats إيقاف التشغيل تجنب حمل جمع المعلومات
log_duration إيقاف التشغيل لا تحتاج إلى معرفة مدة الاستعلامات العادية
log_statement لا عدم تسجيل الاستعلامات دون سبب أكثر تحديدًا
log_min_duration_statement قيمة أطول مما تعتقد أنه يجب أن تأخذه الاستعلامات العادية إظهار الاستعلامات الطويلة بشكل غير طبيعي

ملاحظة

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

قفل الخلاف

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

حماية النظام وتأمينه

قبل الغوص في أوجه القصور الشائعة في التأمين، دعونا نرى كيفية عرض التأمين والنشاط في جميع أنحاء نظام مجموعة قاعدة البيانات. توفر طريقة العرض citus_stat_activity طريقة عرض مفصلة.

تظهر طريقة العرض، من بين أمور أخرى، كيفية حظر الاستعلامات بواسطة "أحداث الانتظار"، بما في ذلك التأمينات. التجميع حسب wait_event_type يرسم صورة لصحة النظام:

-- general system health

SELECT wait_event_type, count(*)
  FROM citus_stat_activity
 WHERE state != 'idle'
 GROUP BY 1
 ORDER BY 2 DESC;

تعني القيمة NULL wait_event_type أن الاستعلام لا ينتظر أي شيء.

إذا رأيت تأمينات في إخراج نشاط الإحصائيات، يمكنك عرض الاستعلامات المحظورة المحددة باستخدام citus_lock_waits:

SELECT * FROM citus_lock_waits;

على سبيل المثال، إذا تم حظر استعلام واحد على استعلام آخر يحاول تحديث الصف نفسه، فسترى عبارات الحظر والحظر تظهر:

-[ RECORD 1 ]-------------------------+--------------------------------------
waiting_gpid                          | 10000011981
blocking_gpid                         | 10000011979
blocked_statement                     | UPDATE numbers SET j = 3 WHERE i = 1;
current_statement_in_blocking_process | UPDATE numbers SET j = 2 WHERE i = 1;
waiting_nodeid                        | 1
blocking_nodeid                       | 1

لمشاهدة عمليات التأمين التي تحدث ليس فقط في الوقت الحالي، ولكن الأنماط التاريخية، يمكنك التقاط الأقفال في سجلات PostgreSQL. لمعرفة المزيد، راجع إعداد خادم log_lock_waits في وثائق PostgreSQL. مورد رائع آخر هو سبعة نصائح للتعامل مع الأقفال على مدونة بيانات Citus.

المشاكل والحلول الشائعة

أوامر DDL

تأخذ أوامر DDL مثل truncate وdrop وcreate index وجميعها تأمين الكتابة، وتحظر الكتابة على الجدول بأكمله. يقلل تقليل مثل هذه العمليات من مشكلات التأمين.

تلميحات:

  • حاول دمج DDL في نوافذ الصيانة، أو استخدامها بشكل أقل في كثير من الأحيان.

  • يدعم PostgreSQL إنشاء المؤشرات بشكل متزامن، لتجنب أخذ تأمين الكتابة على الجدول.

  • ضع في اعتبارك إعداد lock_timeout في جلسة عمل SQL قبل تشغيل أمر DDL ثقيل. باستخدام lock_timeout، سيقوم PostgreSQL بإجهاض الأمر DDL إذا انتظر الأمر وقتًا طويلًا لتأمين الكتابة. يمكن أن يتسبب أمر DDL في انتظار تأمين الاستعلامات اللاحقة في قائمة الانتظار خلف نفسه.

الخمول في اتصالات المعاملات

في بعض الأحيان، تحظر المعاملات الخاملة (غير الملتزم بها) الاستعلامات الأخرى دون داعٍ. على سبيل المثال:

BEGIN;

UPDATE ... ;

-- Suppose the client waits now and doesn't COMMIT right away.
--
-- Other queries that want to update the same rows will be blocked.

COMMIT; -- finally!

لتنظيف أي استعلامات الخامة طويلة يدويًا على عقدة المنسق، يمكنك تشغيل أمر مثل هذا:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'citus'
 AND pid <> pg_backend_pid()
 AND state in ('idle in transaction')
 AND state_change < current_timestamp - INTERVAL '15' MINUTE;

يقدم PostgreSQL أيضًا إعداد idle_in_transaction_session_timeout لأتمتة إنهاء جلسة العمل الخاملة.

حالات التوقف التام

يكتشف Citus حالات التوقف التام الموزعة ويلغى استعلاماته، ولكن الوضع أقل أداء من تجنب حالات التوقف التام في المقام الأول. مصدر شائع من حالات التوقف التام يأتي من تحديث نفس مجموعة الصفوف بترتيب مختلف عن المعاملات المتعددة في وقت واحد.

على سبيل المثال، تشغيل هذه المعاملات بالتوازي:

الجلسة أ:

BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;

الجلسة ب:

BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;

-- ERROR:  canceling the transaction since it was involved in a distributed deadlock

الجلسة أ المعرف المحدث 1 ثم 2، بينما حدثت الجلسة بـ 2 ثم 1. اكتب تعليمة SQL البرمجية للمعاملات بعناية لتحديث الصفوف بنفس الترتيب. (يسمى ترتيب التحديث أحيانًا "التسلسل الهرمي للقفل".)

في قياسنا، ذهب التحديث المجمع لمجموعة من الصفوف مع العديد من المعاملات أسرع 3 أضعاف عند تجنب التوقف التام.

الإدخال/الإخراج في أثناء الاستيعاب

عادة ما يكون ازدحام الإدخال/الإخراج أقل من مشكلة Hyperscale (Citus) مقارنة بـ PostgreSQL ذات العقدة الواحدة بسبب التقسيم. الأجزاء هي جداول أصغر بشكل فردي، مع معدلات ضغط أفضل للفهرس وذاكرة التخزين المؤقت، مما يؤدي إلى أداء أفضل.

ومع ذلك، حتى مع Hyperscale (Citus)، مع زيادة الجداول والمؤشرات، يمكن أن يصبح إدخال/إخراج القرص مشكلة لاستيعاب البيانات. الأشياء التي يجب البحث عنها هي عدد متزايد من إدخالات "IO" wait_event_type التي تظهر في citus_stat_activity:

SELECT wait_event_type, wait_event count(*)
  FROM citus_stat_activity
 WHERE state='active'
 GROUP BY 1,2;

قم بتشغيل الاستعلام أعلاه بشكل متكرر لالتقاط المعلومات المتعلقة بحدث الانتظار. لاحظ كيفية تغيير عدد أنواع أحداث الانتظار المختلفة.

انظر أيضًا إلى المقاييس في مدخل Microsoft Azure، خاصة الحد الأقصى لمقياس IOPS.

تلميحات:

  • إذا كانت بياناتك مرتبة بشكل طبيعي، كما هو الحال في سلسلة زمنية، فاستخدم تقسيم جدول PostgreSQL. راجع هذا الدليل لمعرفة كيفية تقسيم الجداول الموزعة في Hyperscale (Citus).

  • إزالة المؤشرات غير المستخدمة. تتسبب صيانة الفهرس في تضخيم الإدخال/الإخراج في أثناء الاستيعاب. للعثور على المؤشرات غير المستخدمة، استخدم هذا الاستعلام.

  • إذا كان ذلك ممكنًا، تجنب فهرسة البيانات العشوائية. على سبيل المثال، لا تتبع بعض خوارزميات إنشاء UUID أي ترتيب. تؤدي فهرسة مثل هذه القيمة إلى الكثير من النفقات العامة. جرب تسلسلًا كبيرًا بدلًا من ذلك، أو قم بزيادة UUIDs بشكل رتيبة.

ملخص النتائج

في معايير الاستيعاب البسيط مع INSERTs، وحديثات، وكتل المعاملات، لاحظنا سرعات الاستعلام التالية للتقنيات في هذه المقالة.

التقنية تسريع الاستعلام
استعلامات النطاق 100x
تجمّعات الاتصال 24x
تسجيل فعال 10x
تجنب حالة التوقف التام 3x

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