[c] اقرأ خطط PostgreSQL الموازية بشكل صحيح

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



لذلك ، داخل كل عملية منفصلة لا توجد مشاكل "غريبة" تقليدية مع تنفيذ الكود المتوازي ، والأقفال ، وظروف السباق ، ... وتطوير نظام إدارة قواعد البيانات نفسه ممتع وبسيط.



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



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





يمكن العثور على بعض العقد المتوازية في مقالة "Parallelism in PostgreSQL" التي كتبها إبرار أحمد ، حيث تم أخذ هذه الصورة.
ومع ذلك ، في هذه الحالة يصبح ... قراءة الخطط ليست تافهة.



باختصار ، يبدو التسلسل الزمني لتنفيذ التنفيذ الموازي لعمليات الخطة كما يلي:





لذلك ، إذا كنت تستخدم أحد أحدث إصدارات PostgreSQL ، فإن فرص رؤيته في الخطة Parallel ...عالية جدًا. ويأتي معه و ...



الشذوذ بمرور الوقت



لنأخذ خطة من PostgreSQL 9.6 :





[انظر إلىشرح.tensor.ru]



تم Parallel Seq Scanتنفيذ واحدة فقط 153.621 مللي ثانية داخل شجرة فرعية ، Gatherومع جميع العقد الفرعية - فقط 104.867 مللي ثانية.



كيف ذلك؟ هل أصبح الوقت الإجمالي "للطابق العلوي" أقل؟ ..



دعنا نلقي نظرة على Gatherالعقدة بمزيد من التفصيل:



Gather (actual time=0.969..104.867 rows=333333 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  Buffers: shared hit=4425


Workers Launched: 2يخبرنا أنه بالإضافة إلى العملية الرئيسية أسفل الشجرة ، تم إشراك Gatherعمليتين إضافيتين - بإجمالي 3. لذلك ، كل ما حدث داخل الشجرة الفرعية هو الإبداع الكلي لجميع العمليات الثلاث في وقت واحد.



الآن دعنا نرى ما يوجد هناك Parallel Seq Scan:



Parallel Seq Scan on tst (actual time=0.024..51.207 rows=111111 loops=3)
  Filter: ((i % 3) = 0)
  Rows Removed by Filter: 222222
  Buffers: shared hit=4425


آها! loops=3هو ملخص لجميع العمليات الثلاث. وفي المتوسط ​​، استغرقت كل دورة 51.207 مللي ثانية. أي ، استغرق الخادم 51.207 x 3 = 153.621مللي ثانية من وقت المعالج لإكمال هذه العقدة . أي ، إذا أردنا فهم "ما كان يفعله الخادم" - فهذا الرقم هو الذي سيساعدنا على فهم.

لاحظ أنه لفهم وقت التنفيذ "الحقيقي" ، تحتاج إلى تقسيم الوقت الإجمالي على عدد العمال - أي [actual time] x [loops] / [Workers Launched].


في مثالنا ، أجرى كل عامل دورة واحدة فقط من خلال العقدة ، لذلك 153.621 / 3 = 51.207. ونعم ، الآن لا يوجد شيء غريب أن الشخص الوحيد Gatherفي عملية الرأس قد اكتمل "كما كان ، في وقت أقل".



المجموع: انظر إلىشرح.tensor.ru إجمالي وقت العقدة (لجميع العمليات) لفهم نوع التحميل الذي كان خادمك مشغولاً به ، ولتحسين أي جزء من الاستعلام يستحق قضاء الوقت.



بهذا المعنى ، فإن سلوك نفس الشرح . depesz.com ، الذي يظهر "المتوسط ​​الحقيقي" للوقت في وقت واحد ، يبدو أقل فائدة لأغراض تصحيح الأخطاء:







ألا توافق؟ مرحبًا بك في التعليقات!



جمع دمج يفقد كل شيء



الآن دعنا ننفذ نفس الاستعلام على إصدارات PostgreSQL 10 :





[انظر إلىشرح.tensor.ru]



لاحظ أن لدينا Gatherالآن عقدة بدلاً من عقدة في الخطة Gather Merge. إليك ما يقوله الدليل عن هذا :

عندما تكون العقدة أعلى الجزء الموازي من الخطة Gather Merge، فبدلاً من ذلك Gather، فهذا يعني أن جميع العمليات التي تنفذ أجزاء من الخطة المتوازية تُخرج مجموعات tuple بترتيب مُفرز ، وأن العملية الرائدة تقوم بإجراء دمج للحفاظ على النظام. Gatherمن ناحية أخرى ، تستقبل العقدة مجموعات من العمليات التابعة بترتيب تعسفي مناسب لها ، منتهكًا ترتيب الفرز الذي يمكن أن يكون موجودًا.


لكن ليس كل شيء على ما يرام في المملكة الدنماركية:



Limit (actual time=110.740..113.138 rows=10000 loops=1)
  Buffers: shared hit=888 read=801, temp read=18 written=218
  I/O Timings: read=9.709
  ->  Gather Merge (actual time=110.739..117.654 rows=10000 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        Buffers: shared hit=2943 read=1578, temp read=24 written=571
        I/O Timings: read=17.156


بينما يمر سمات Buffersو I/O Timingsحتى الشجرة، بعض البيانات فقد المفاجئة . يمكننا تقدير حجم هذه الخسارة بحوالي 2/3 تقريبًا ، والتي يتم تشكيلها بواسطة عمليات مساعدة.



للأسف ، في الخطة نفسها ، لا يوجد مكان للحصول على هذه المعلومات - ومن هنا جاءت "السلبيات" في العقدة العلوية. وإذا نظرت إلى التطور الإضافي لهذه الخطة في PostgreSQL 12 ، فلن تتغير بشكل أساسي ، باستثناء إضافة بعض الإحصائيات لكل عامل على Sortالعقدة -node:



Limit (actual time=77.063..80.480 rows=10000 loops=1)
  Buffers: shared hit=1764, temp read=223 written=355
  ->  Gather Merge (actual time=77.060..81.892 rows=10000 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        Buffers: shared hit=4519, temp read=575 written=856
        ->  Sort (actual time=72.630..73.252 rows=4278 loops=3)
              Sort Key: i
              Sort Method: external merge  Disk: 1832kB
              Worker 0:  Sort Method: external merge  Disk: 1512kB
              Worker 1:  Sort Method: external merge  Disk: 1248kB
              Buffers: shared hit=4519, temp read=575 written=856
              ->  Parallel Seq Scan on tst (actual time=0.014..44.970 rows=111111 loops=3)
                    Filter: ((i % 3) = 0)
                    Rows Removed by Filter: 222222
                    Buffers: shared hit=4425
Planning Time: 0.142 ms
Execution Time: 83.884 ms


الإجمالي: لا تثق في بيانات العقدة أعلاه Gather Merge.



All Articles