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

ما يقيسه مؤقت psql حقًا

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

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

psql "host=db.internal port=5432 dbname=appdb user=app" \
  -c "\timing on" \
  -c "select count(*) from orders where created_at > now() - interval '1 day';"

لا يزال الرقم يشمل نقل النتيجة، وهو المطلوب هنا. لم يعد يشمل تبادل الاتصال والمصادقة الذي كان استدعاء جديد سيضيفه.

قارن عبارة واحدة عبر ثلاثة مسارات

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

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

psql -h /var/run/postgresql -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h 10.40.0.12 -U app -d appdb -c "\timing on" -c "select count(*) from orders;"
psql -h db.vpn.internal -U app -d appdb -c "\timing on" -c "select count(*) from orders;"

على شبكة محلية يكون الفرق عادة صغيرًا بما يكفي لتجاهله؛ وعبر VPN غالبًا لا يكون كذلك. ترتيب النتائج الثلاث يخبرك بعد ذلك أي انتظار تهاجم.

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

استخرج زمن التنفيذ الخاص بالخادم

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

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

select calls, round(mean_exec_time::numeric, 2) as mean_ms, rows
from pg_stat_statements
order by mean_exec_time desc
limit 5;

تختلف أسماء الأعمدة بحسب الإصدار: يعرض PostgreSQL 13 وما بعده mean_exec_time، بينما تسميه الإصدارات الأقدم mean_time، لذا تحقق بـ \d pg_stat_statements إذا أعطى ذلك الاستعلام خطأ. تستبعد هذه القيم التخطيط وتستبعد الرحلة إلى عميلك. هذا الاستبعاد هو بالضبط سبب فائدتها للمقارنة.

عد رحلات الذهاب والإياب، لا المللي ثانية وحدها

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

قس عدد العبارات بدلًا من تقديره. اقرأ الإجمالي، وشغل التقرير مرة واحدة، ثم اقرأه مرة أخرى واطرح.

select sum(calls) as calls from pg_stat_statements;
-- run the report here
select sum(calls) as calls from pg_stat_statements;

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

افحص النفق بحثًا عن إعادة الإرسال وتوقفات MTU

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

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

ss -tin dst 10.40.0.12
netstat -s | grep -iE "retransmit|segments retransmitted"
ping -M do -s 1400 -c 3 10.40.0.12

إعادة الإرسال التي تتصاعد أثناء النقل، مع ping ينجح عند 1400 بايت ويفشل عند 1472، تشير إلى مشكلة MTU لا إلى مشكلة قاعدة بيانات. إنقاص MTU لواجهة النفق أو تثبيت MSS عند البوابة هو التصحيح المعتاد، والأوامر الثلاثة نفسها تؤكده بعد ذلك. إذا بقيت إعادة الإرسال ثابتة بينما النقل ما زال بطيئًا، فانظر إلى النطاق الترددي المسموح للنفق لا إلى الفقد.

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

مقالات ذات صلة