KoderKoder.ai
الأسعارالمؤسساتالتعليمللمستثمرين
تسجيل الدخولابدأ الآن

المنتج

الأسعارالمؤسساتللمستثمرين

الموارد

اتصل بناالدعمالتعليمالمدونة

قانوني

سياسة الخصوصيةشروط الاستخدامالأمانسياسة الاستخدام المقبولالإبلاغ عن إساءة

اجتماعي

LinkedInTwitter
Koder.ai
اللغة

© 2026 ‏Koder.ai. جميع الحقوق محفوظة.

الرئيسية›المدونة›ضبط أداء Go وPostgres: دليل عملي مُركّز لواجهات API
14 ديسمبر 2025·7 دقيقة

ضبط أداء Go وPostgres: دليل عملي مُركّز لواجهات API

دليل ضبط أداء Go وPostgres لواجهات API المولّدة آلياً: إدارة التجمعات، قراءة خطط الاستعلام، فهرسة ذكية، ترقيم آمن، وتشكيل JSON بسرعة.

ضبط أداء Go وPostgres: دليل عملي مُركّز لواجهات API

كيف يظهر "البطء" لواجهات API المكتوبة بـ Go على Postgres

قد تبدو واجهات API المُولّدة آلياً سريعة في الاختبار المبكر. تدعو نقطة نهاية عدة مرات، ومجموعة البيانات صغيرة، والطلبات تأتي واحدة تلو الأخرى. ثم يصل تحميل حقيقي: نقاط نهاية مختلطة، حمولة متقطعة، كاشات أبرد، وعدد صفوف أكبر مما توقعت. نفس الشيفرة يمكن أن تبدأ بالظهور بطيئة عشوائياً رغم أن شيئاً لم ينكسر فعلياً.

عادة ما يظهر البطء بعدة طرق: قفزات زمن الاستجابة (معظم الطلبات جيدة، وبعضها يستغرق 5× إلى 50× أطول)، مهلات (نسبة صغيرة تفشل)، أو وحدة المعالجة تعمل بنشاط مرتفع (CPU في Postgres من العمل على الاستعلامات، أو CPU في Go من JSON، goroutines، التسجيل، وإعادة المحاولات).

سيناريو شائع هو نقطة قائمة بها فلتر بحث مرن تعيد استجابة JSON كبيرة. في قاعدة اختبار تفحص بضعة آلاف صفوف وتُنجز بسرعة. في الإنتاج تفحص بضعة ملايين صفوف، ترتّبها، ثم تطبّق LIMIT. الـ API ما يزال "يعمل"، لكن زمن p95 يتفجّر وت timeout تحدث لعدد من الطلبات أثناء الارتفاع.

لفصل بطء قاعدة البيانات عن بطء التطبيق، احتفظ بنموذج ذهني بسيط.

إذا كانت قاعدة البيانات بطيئة، يقضي معالج Go معظم وقته في انتظار الاستعلام. قد ترى أيضاً الكثير من الطلبات عالقة "قيد التنفيذ" بينما يبدو CPU في Go طبيعياً.

إذا كان التطبيق بطيئاً، ينتهي الاستعلام سريعاً، لكن الوقت يُهدر بعد الاستعلام: بناء كائنات استجابة كبيرة، تسلسل JSON، استعلامات إضافية لكل صف، أو عمل كبير لكل طلب. يرتفع CPU في Go، ترتفع الذاكرة، ويزداد زمن الاستجابة مع حجم الاستجابة.

"ما يكفي" من الأداء قبل الإطلاق ليس الكمال. بالنسبة للعديد من نقاط CRUD، استهدف زمن p95 مستقر (ليس المتوسط فقط)، سلوك متوقع أثناء الارتفاعات، وعدم وجود مهلات عند الذروة المتوقعة. الهدف واضح: لا مفاجآت بخصوص طلبات بطيئة عندما تنمو البيانات والحركة، وإشارات واضحة عندما يحدث انحراف.

الأساس أولاً: الأرقام القليلة المهمة

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

ثلاثة أرقام عادةً تروي معظم القصة:

  • زمن الاستجابة p95 (ليس المتوسط)
  • معدل الأخطاء (HTTP 5xx، المهلات، الطلبات الملغاة)
  • زمن قاعدة البيانات لكل طلب (كم ينتظر كل طلب على Postgres)

p95 هو مقياس "اليوم السيئ". إن كان p95 مرتفعاً لكن المتوسط جيد، مجموعة صغيرة من الطلبات تقوم بعمل زائد، تُحاصر على أقفال، أو تُشغّل خططاً بطيئة.

اجعل الاستعلامات البطيئة مرئية مبكراً. في Postgres فعّل تسجيل الاستعلامات البطيئة بعتبة منخفضة للاختبار قبل الإطلاق (مثلاً 100–200 ملّي ثانية)، وسجّل العبارة كاملة لتتمكن من نسخها إلى عميل SQL. احتفظ بهذا مؤقتاً. تسجيل كل استعلام بطيء في الإنتاج يصير مزعجاً سريعاً.

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

إذا كنت تُولّد نقاط نهاية من مواصفة (مثلاً بأداة ذكية مثل Koder.ai)، شغّل نفس مجموعة الطلبات مراراً مع مدخلات ثابتة. ذلك يجعل تغييرات مثل الفهارس، ضبط الترقيم، وإعادة كتابة الاستعلامات أسهل للقياس.

أخيراً، اختر هدفاً يمكنك قوله بصوت عالٍ. مثال: "معظم الطلبات تبقى تحت 200 ملّي ثانية p95 عند 50 مستخدماً متزامناً، والأخطاء أقل من 0.5%." الأرقام الدقيقة تعتمد على منتجك، لكن هدف واضح يمنع العبث اللامتناهي.

تجمّع الاتصالات الذي يحفظ استقرار Postgres

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

الهدف هو إبقاء Postgres منشغلاً بعمل مفيد، لا بالتنقل بين اتصالات كثيرة. هذا غالباً أول فوز ملموس، خصوصاً لواجهات API المولّدة آلياً التي قد تصبح دردشية بصمت.

إعدادات بداية بسيطة

في Go، عادةً تضبط max open connections، max idle connections، وconnection lifetime. نقطة بداية آمنة للعديد من APIs الصغيرة هي عدة أضعاف عدد أنوية المعالج (غالباً 5 إلى 20 اتصالاً إجمالاً)، مع عدد مماثل محفوظ خالياً، وإعادة تدوير الاتصالات دورياً (مثلاً كل 30 إلى 60 دقيقة).

إذا كنت تشغّل عدة مثيلات للـ API، تذكّر أن التجمع يتضاعف. تجمع من 20 عبر 10 مثيلات يعني 200 اتصال تصيب Postgres، ومن هنا تحدث فرق عن القيود غير المتوقعة.

كيف تعرف إن التجمع هو المشكلة

مشاكل التجمع مختلفة عن SQL البطيء.

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

إذا كان التجمع كبيراً جداً، يبدو Postgres مثقلاً: عدد جلسات نشطة كبير، ضغط على الذاكرة، وزمن استجابة متباين عبر النقاط.

طريقة سريعة للفصل هي توقيت مكالمات DB في جزئين: الوقت المستغرق في انتظار الاتصال مقابل الوقت المستغرق في تنفيذ الاستعلام. إذا كان معظم الوقت "انتظاراً"، فالتجمع هو عنق الزجاجة. إذا كان معظم الوقت "في الاستعلام"، ركّز على SQL والفهارس.

فحوص سريعة مفيدة:

  • سجّل إحصاءات التجمع (open, in-use, idle) وراقب إن بقي in-use عالقاً عند الحد الأقصى.
  • أضف مهلة عند الحصول على اتصال حتى تفشل الانتظارات بسرعة في المرحلة.
  • راقب الاتصالات النشطة في Postgres ومدى قربك من max_connections.
  • تأكد أن كل طلب يغلق الصفوف ويعيد الاتصالات بسرعة.
  • اختبر التحميل بعدد المثيلات التي تخطط لتشغيلها.

pgxpool مقابل database/sql

إذا استخدمت pgxpool، تحصل على تجمع مُصمّم لPostgres مع إحصاءات واضحة وإعدادات افتراضية جيدة لسلوك Postgres. إذا استخدمت database/sql، تحصل على واجهة معيارية تعمل عبر قواعد بيانات، لكن عليك أن تكون صريحاً بشأن إعدادات التجمع وسلوك السائق.

قاعدة عملية: إذا كنت ملتزماً بـ Postgres وتريد تحكماً مباشراً، غالباً ما يكون pgxpool أبسط. إذا اعتمدت على مكتبات تتوقع database/sql، التزم بها، اضبط التجمع صراحةً، وقِس أوقات الانتظار.

مثال: نقطة قائمة تعرض الطلبات قد تعمل في 20 ملّي ثانية، لكن تحت 100 مستخدم متزامن تقفز إلى 2 ثانية. إذا أظهرت السجلات 1.9 ثانية انتظاراً للحصول على اتصال، فلن يساعد ضبط الاستعلام حتى يتم ضبط التجمع وإجمالي اتصالات Postgres بشكل صحيح.

تخطيط الاستعلام: قراءة سريعة لإخراج EXPLAIN

عندما تبدو نقطة النهاية بطيئة، تحقق مما يفعله Postgres فعلاً. قراءة سريعة لـ EXPLAIN غالباً تشير إلى الحل خلال دقائق.

شغّل هذا على SQL الدقيق الذي يرسله API:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, created_at
FROM orders
WHERE user_id = $1 AND status = $2
ORDER BY created_at DESC
LIMIT 50;

بضعة أسطر تهمك أكثر. انظر إلى العقدة العلوية (ما اختاره الـ planner) والمجاميع في الأسفل (كم استغرق). ثم قارن الصفوف المقدرة بالفعلية. الفجوات الكبيرة عادةً تعني أن المخطط خمن خطأ.

ماذا تعني الأسطر الأساسية عادةً

إن رأيت Index Scan أو Index Only Scan، فـPostgres يستخدم فهرساً، وهو عادةً جيد. Bitmap Heap Scan قد يكون مقبولاً للنتائج متوسطة الحجم. Seq Scan يعني أنه قرأ الجدول بأكمله، وهو مقبول فقط عندما يكون الجدول صغيراً أو عندما تتطابق معظم الصفوف.

علامات حمراء شائعة:

  • Seq Scan على جدول كبير
  • فرق كبير بين الصفوف المقدرة والفعلية (مثلاً 10 مقدرة مقابل 10,000 فعلية)
  • الفرز يأخذ معظم الوقت (غالباً مع ORDER BY)
  • "Filter:" يزيل الكثير من الصفوف بعد المسح
  • ارتفاع shared read blocks في BUFFERS (قراءة بيانات كثيرة)

لماذا تفشل المخططات (وحلول سهلة)

الخطط البطيئة عادةً تأتي من أنماط قليلة:

  • فهرس مفقود لنمط WHERE + ORDER BY الخاص بك (مثلاً (user_id, status, created_at))
  • أنواع غير متطابقة (مثلاً مقارنة عمود UUID بمعامل نصي)، ما يمنع استخدام الفهرس
  • دوال في WHERE (مثلاً WHERE lower(email) = $1)، ما قد يجبر على المسح ما لم تضف فهرس تعبير مناسب

إذا بدا المخطط غريباً وكانت التقديرات متباعدة، فعادةً الإحصاءات قديمة. شغّل ANALYZE (أو دع autovacuum ي catch up) حتى يتعلم Postgres عدّ الصفوف وتوزيع القيم الحالي. هذا مهم بعد استيرادات كبيرة أو عندما تبدأ نقاط نهاية جديدة بالكتابة بكثافة.

الفهرسة وفق الاستعلامات التي تنفذها فعلياً

احصل على مكافآت للمشاركة
أنشئ محتوى أو أحِل غيرك واكسب أرصدة لتستمر في البناء على Koder.ai.
اكسب رصيد

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

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

بناء الفهارس حول الفلاتر + ترتيب الفرز

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

قواعد عامة تصمد غالباً:

  • أفهرس الأعمدة التي تُفلتر عليها غالباً، ثم أضف عمود الفرز.
  • اجعل الفهارس المركبة صغيرة. عمودان شائعان؛ ثلاثة أحياناً؛ أكثر من ذلك غالباً علامة تحذير.
  • ضع الفلتر الأكثر انتقائية أولاً (الذي يضيق النتائج أكثر).
  • تجنّب الفهارس المنفصلة المغطاة بالكامل بواسطة فهرس مركب أفضل.
  • فضّل فهرساً واحداً مناسباً على عدة فهارس "قد تفيد".

مثال: إن كانت استعلاماتك GET /orders?status=paid وتعرض الأحدث أولاً، فهرس مثل (status, created_at DESC) مناسب. إن كانت معظم الاستعلامات أيضاً تُفلتر حسب customer، فـ (customer_id, status, created_at) قد يكون أفضل، لكن فقط إذا كان هذا نمط الاستعلام الفعلي في الإنتاج.

الفهارس الجزئية للفلاتر الشائعة

إذا كانت معظم الحركة تضرب شريحة ضيقة من الصفوف، يمكن أن يكون الفهرس الجزئي أرخص وأسرع. مثلاً، إن كان التطبيق يقرأ غالباً السجلات النشطة، ففهرستها فقط WHERE active = true يبقي الفهرس أصغر وأكثر احتمالاً للبقاء في الذاكرة.

لتأكيد أن الفهرس يساعد، قم بفحوص سريعة:

  • شغّل EXPLAIN (أو EXPLAIN ANALYZE في بيئة آمنة) وابحث عن index scan يطابق استعلامك.
  • قارن التوقيت والصفوف المقروءة مع وبدون الفهرس.
  • راقب "Rows Removed by Filter" إن بقي مرتفعاً؛ غالباً يعني أن الفهرس لا يطابق الفلتر.

أزل الفهارس غير المستخدمة بحذر. تحقق من إحصاءات الاستخدام (مثلاً إن كان الفهرس قد فُحص). احذف واحداً في كل مرة خلال نوافذ منخفضة المخاطر، واحتفظ بخطة استرجاع. الفهارس غير المستخدمة ليست بلا تأثير؛ فهي تبطئ الإدخالات والتحديثات في كل كتابة.

أنماط الترقيم التي لا تبطئ مع الوقت

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

لماذا LIMIT/OFFSET يبطئ

LIMIT/OFFSET يبدو بسيطاً، لكن الصفحات العميقة تكلف أكثر. Postgres لا يزال يضطر لتجاوز (وغالباً فرز) الصفوف التي تتخطاها. الصفحة 1 قد تلمس بضعة عشرات من الصفوف. الصفحة 500 قد تجبر قاعدة البيانات على مسح والتخلص من عشرات الآلاف فقط لإرجاع 20 نتيجة.

كما أنه يخلق نتائج غير مستقرة عند إدراج أو حذف صفوف بين الطلبات. قد يرى المستخدمون تكرارات أو يفقدون عناصر لأن معنى "الصف 10,000" يتغير مع تغير الجدول.

ترقيم keyset (cursor) مع مثال "آخر مرئي"

ترقيم keyset يطرح سؤالاً مختلفاً: "أعطني الـ 20 صفاً التالية بعد آخر صف رأيته." هذا يحافظ على عمل القاعدة على شريحة صغيرة ومتسقة.

نسخة بسيطة تستخدم id متزايد:

SELECT id, created_at, title
FROM posts
WHERE id > $1
ORDER BY id
LIMIT 20;

ترجع API next_cursor يساوي آخر id في الصفحة. الطلب التالي يستخدم تلك القيمة كقيمة $1.

للفرز القائم على الوقت، استخدم ترتيباً ثابتاً وكسر التعادل. created_at وحده غير كافٍ إذا شارك صفان نفس الطابع الزمني. استخدم cursor مركب:

WHERE (created_at, id) < ($1, $2)
ORDER BY created_at DESC, id DESC
LIMIT 20;

قواعد تمنع التكرارات والصفحات المفقودة:

  • دائماً أدرج كاسراً فريداً في ORDER BY (غالباً id).
  • اجعل ترتيب الفرز مماثلاً عبر الطلبات.
  • اجعل الـ cursor غير شفاف للعملاء (رمّز created_at وid معاً).
  • إن كان بإمكان المستخدمين الفلترة، أدرج نفس الفلاتر في كل صفحة.
  • فضّل حقول فرز غير قابلة للتغيير (وقت الإنشاء) على القابلة للتغيير (الحالة، الدرجة) إن أمكن.

تشكيل JSON: استجابات أسرع بحمولات أصغر

تحديد حجم تجمع DB الصحيح
ولّد شيفرة تتضمن حدود تجمّع معقولة ومهلات، ثم اختبر التحميل بثقة.
ابدأ المشروع

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

ابدأ بـ SELECT. إن كانت نقطة النهاية تحتاج فقط id, name, وstatus، اطلب تلك الأعمدة ولا شيء غيرها. SELECT * يثقل الصمت مع مرور الوقت مع إضافة نص طويل، كائنات JSON، وأعمدة تدقيق.

تباطؤ آخر شائع هو بناء الاستجابات بنمط N+1: تجلب قائمة من 50 عنصراً، ثم تشغّل 50 استعلاماً إضافياً لإرفاق بيانات مرتبطة. قد يجتاز الاختبارات ثم ينهار تحت حركة حقيقية. فضّل استعلاماً واحداً يعيد ما تحتاج (انضمامات محسوبة بحذر)، أو استعلامين حين يجمع الثاني حسب المعرفات.

طرق للحفاظ على الحمولات أصغر دون كسر العملاء:

  • استخدم علم include= (أو قناع fields=) حتى تبقى استجابات القوائم خفيفة وتضمّن التفاصيل عند الطلب.
  • حدّ المصفوفات المتداخلة (مثلاً أحدث 10 أحداث) ووفّر واجهة منفصلة للسجل الكامل.
  • لا تعِد أعمدة JSON داخلية خام إن كان العميل يحتاج لمفتاحين فقط.
  • استخدم رموز قصيرة بدلاً من تكرار تسميات طويلة.

بناء JSON في Postgres أم في Go؟

كلاهما قد يكون سريعاً. اختر بناءً على ما تحاول تحسينه.

دوال JSON في Postgres (jsonb_build_object, json_agg) مفيدة عند رغبتك في تقليل عدد الرحلات والحصول على أشكال متوقعة من استعلام واحد. التشكيل في Go مفيد عند حاجتك إلى منطق شرطي، إعادة استخدام structs، أو الإبقاء على SQL أسهل للصيانة. إذا أصبح SQL الخاص بتشكيل JSON صعب القراءة، يصبح من الصعب ضبطه.

قاعدة جيدة: دَع Postgres يقوم بالتصفية والفرز والتجميع. ثم دع Go يتولى العرض النهائي.

إذا كنت تُولّد واجهات بسرعة (مثلاً مع Koder.ai)، فإن إضافة أعلام include مبكراً يساعد على تجنب نقاط نهاية تنتفخ مع الزمن. كما يوفّر طريقة آمنة لإضافة حقول دون جعل كل استجابة أثقل.

تمريرة ضبط خطوة بخطوة قبل أول المستخدمين

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

قبل تغيير أي شيء، دوّن خط أساس صغير:

  • p95 و p99 زمن الاستجابة لنقاط النهاية الأكثر نشاطاً
  • معدل الأخطاء والمهلات
  • CPU قاعدة البيانات والاتصالات النشطة
  • أبطأ 5 استعلامات بحسب إجمالي الزمن (ليس فقط الأسوأ منفرداً)

تمريرة الضبط

ابدأ صغيراً، غيّر شيئاً واحداً في كل مرة، وأعد الاختبار بعد كل تغيير.

  1. شغّل اختبار تحميل لمدة 10 إلى 15 دقيقة يشبه الاستخدام الحقيقي. اضرب نفس النقاط التي سيستخدمها أول زبائنك (تسجيل دخول، صفحات قائمة، بحث، إنشاء). ثم رتب المسارات حسب زمن p95 والوقت الإجمالي.

  2. افحص ضغط الاتصالات قبل ضبط SQL. تجمع كبير جداً يرهق Postgres؛ تجمع صغير جداً يخلق انتظاراً طويلاً. ابحث عن ارتفاع وقت انتظار الحصول على اتصال وعدد اتصالات يقفز أثناء الارتفاعات. عدّل حدود التجمع والidle أولاً، ثم أعد الاختبار.

  3. EXPLAIN لأبطأ الاستعلامات وأصلح أكبر علامة حمراء. المذنبون المعتادون: المسح الكامل للجداول الكبيرة، الفرز على مجموعات نتائج ضخمة، وانضمامات تؤدي إلى انفجار عدد الصفوف. اختر أسوأ استعلام واجعله مملّاً.

  4. أضف أو عدّل فهرساً واحداً، ثم أعد الاختبار. الفهارس تساعد عندما تطابق WHERE وORDER BY. لا تضف خمسة دفعة واحدة. إذا كانت نقطة النهاية البطيئة "قائمة الطلبات حسب user_id مرتبة بcreated_at"، ففهرس مركب على (user_id, created_at) قد يكون الفرق بين فوري ومؤلم.

  5. قِص الاستجابات وعدّل الترقيم، ثم أعد الاختبار مجدداً. إن كانت نقطة النهاية تُرجع 50 صفاً بحقول JSON كبيرة، ستدفع قاعدة البيانات والشبكة والعميل الثمن. أعد فقط الحقول التي يحتاجها الواجهة، وفضّل ترقيماً لا يبطئ مع نمو الجداول.

احتفظ بسجل تغييرات بسيط: ماذا تغير، لماذا، وما الذي تحرك في p95. إن لم يحسّن تغييرٌ ما خط الأساس، ارجعه وامضِ قدماً.

أخطاء شائعة وفخاخ تجنبها

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

معظم مشكلات الأداء في واجهات Go على Postgres من صنعنا. الخبر الجيد أن بعض الفحوصات تلتقط كثيراً منها قبل وصول الحركة الحقيقية.

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

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

دين الترقيم يتسلل بهدوء. الترقيم بالـ offset يبدو جيداً مبكراً، ثم يرتفع p95 مع الزمن لأن قاعدة البيانات تضطر لتجاوز المزيد والمزيد من الصفوف.

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

إن راقبت المتوسط فقط، ستفوت مكان بدأ ألم المستخدم الحقيقي. p95 (وأحياناً p99) هو المكان الذي يظهر فيه احتقان التجمع، انتظار الأقفال، والخطط البطيئة أولاً.

فحص سريع قبل الإطلاق:

  • راقب وقت انتظار التجمع وعدد اتصالات Postgres خلال اختبار تحميل صغير.
  • قارن المتوسط مقابل p95 لنفس النقطة.
  • تحقق أن الترقيم لا يتدهور عندما يكبر الجدول 10×.
  • فحص أحجام الاستجابات لنقاط القوائم (البايت مهمة).
  • أعد تشغيل EXPLAIN بعد إضافة فهارس أو تغيير فلاتر.

قائمة تحقق سريعة وخطوات تالية قبل الإطلاق

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

شغّل الفحوص في بيئة staging تشبه الإنتاج (حجم DB مماثل، نفس الفهارس، نفس إعدادات التجمع): قِس p95 لكل نقطة رئيسية تحت الحمولة، التقط أبطأ الاستعلامات حسب الوقت الكلي، راقب وقت انتظار التجمع، شغّل EXPLAIN (ANALYZE, BUFFERS) لأوسع استعلام لتتأكد أنه يستخدم الفهرس الذي تتوقعه، وتحقق عقلانياً من أحجام الحمولات على المسارات الأكثر نشاطاً.

ثم نفّذ تجربة أسوأ حالة تحاكي كيف تنكسر المنتجات: اطلب صفحة عميقة، طبّق أعرض فلتر، وجرب ذلك في بداية باردة (أعد تشغيل الـ API واضرب نفس الطلب أولاً). إذا كان الترقيم العميق يزداد بطئاً كل صفحة، انتقل إلى ترقيم بالـ cursor قبل الإطلاق.

دون قواعد افتراضية حتى يختار الفريق بتناسق لاحقاً: حدود التجمع والمهلات، قواعد الترقيم (أقصى حجم صفحة، إن كان الـ offset مسموحاً أم لا، صيغة الـ cursor)، قواعد الاستعلام (اختر الأعمدة اللازمة فقط، تجنّب SELECT *, حد الفلاتر المكلفة)، وقواعد التسجيل (عتبة الاستعلام البطيء، كم تدوم العينات، كيفية وسم النقاط).

إذا كنت تُنشئ وتصدّر خدمات Go + Postgres مع Koder.ai، فإن تمريرة تخطيط قصيرة قبل النشر تساعد على الحفاظ على الفلاتر، الترقيم، وأشكال الاستجابات مقصودة. بمجرد أن تبدأ بضبط الفهارس وأشكال الاستعلامات، تجعل اللقطات والاسترجاع من السهل التراجع عن "تحسين" يفيد نقطة ويؤذي أخرى. إن أردت مكاناً واحداً لتكرار تلك الدورة، فإن Koder.ai على koder.ai مصممة حول توليد وتنقيح تلك الخدمات عبر الدردشة، ثم تصدير المصدر عندما تكون جاهزاً.

الأسئلة الشائعة

كيف أميز بسرعة إن كان بطء API في Go بسبب Postgres أم بسبب الكود؟

ابدأ بفصل زمن الانتظار في قاعدة البيانات عن وقت عمل التطبيق.

  • إذا كانت قاعدة البيانات بطيئة، فالمعالج في Go يقضي معظم الوقت في انتظار الاستعلام. غالباً تلاحظ أن CPU في Go طبيعي بينما الطلبات تتكدس "قيد التنفيذ".
  • إذا كان التطبيق بطيئاً، فالاستعلامات تنتهي بسرعة لكن الوقت يضيع بعد الاستعلام: بناء كائنات كبيرة، استعلامات إضافية لكل صف، تسلسل JSON كبير، أو تسجيل كثيف. عادةً يرتفع CPU والذاكرة مع حجم الاستجابة.

أضف توقيت بسيط حول "انتظار الحصول على اتصال" و"تنفيذ الاستعلام" لترى أي جانب يهيمن.

ما المقاييس التي أتابعها أولاً قبل أي ضبط؟

استخدم خط أساس صغير قابل للتكرار:

  • زمن الاستجابة p95 لكل نقطة نهاية مهمة (ليس المتوسط)
  • معدل الأخطاء (5xx، مهلات، إلغاءات)
  • زمن DB لكل طلب (الزمن المُستغرق في انتظار Postgres)

اختر هدفاً واضحاً مثلاً: «p95 أقل من 200 ملّي ثانية عند 50 مستخدماً متزامناً، والأخطاء أقل من 0.5%». غيّر شيئاً واحداً في كل مرة وأعد اختبار نفس مزيج الطلبات.

هل أُفعّل تسجيل الاستعلامات البطيئة في Postgres وما هي العتبة العملية؟

فعل تسجيل الاستعلامات البطيئة بعتبة منخفضة أثناء اختبارات ما قبل الإطلاق (مثلاً 100–200 ملّي ثانية) وسجّل الجملة بالكامل لتتمكن من نسخها إلى عميل SQL.

احتفظ بذلك مؤقتاً:

  • يصبح ضوضاءً سريعاً في الإنتاج.
  • يمكن أن يضيف حملاً إذا سجلت الكثير.

بعد العثور على المسببات الأبرز، انتقل إلى أخذ عينات أو ارفع العتبة.

ما إعدادات تجمّع الاتصالات الجيدة كبداية لAPI بـ Go على Postgres؟

قاعدة عملية: عدة أضعاف صغيرة لعدد أنوية CPU لكل مثيل API، غالباً من 5 إلى 20 اتصالاً مفتوحاً كحد أقصى، مع عدد مماثل من الاتصالات الخاملة، وإعادة تدوير الاتصالات كل 30–60 دقيقة.

حالتان شائعتان للفشل:

  • تجمع صغير جداً: الطلبات تنتظر الحصول على اتصال رغم أن زمن الاستعلام في Postgres جيد.
  • تجمع كبير جداً: Postgres يتعرض للتحميل بمجموعة جلسات نشطة ويظهر زمن استجابة متباين.

تذكّر أن التجمعات تتكاثر عبر المثيلات (20 اتصال × 10 مثيلات = 200 اتصال).

كيف أتأكد أن تجمّع الاتصالات هو عنق الزجاجة وليس SQL؟

قِس المكالمات لقاعدة البيانات بجزئين:

  • وقت الانتظار للحصول على اتصال (انتظار التجمع)
  • وقت تنفيذ الاستعلام (عمل Postgres)

إذا كان معظم الوقت في انتظار التجمع، عدل حجم التجمع والمهلات وعدد المثيلات. إذا كان معظم الوقت في التنفيذ، ركّز على EXPLAIN والفهارس.

وتأكد دائماً من إغلاق الصفوف promptly حتى تعود الاتصالات إلى التجمع.

ما الذي أبحث عنه أولاً في EXPLAIN عندما تصبح نقطة نهاية بطيئة؟

شغّل EXPLAIN (ANALYZE, BUFFERS) على SQL الذي يرسله API تماماً وابحث عن:

  • Seq Scan على جدول كبير
  • فرق كبير بين الصفوف المقدرة والصفوف الفعلية
كيف أختار الفهرس الصحيح لنقطة قوائم مع فلاتر وفرز؟

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

القاعدة العملية:

  • أنشئ فهارس مركبة حول الفلاتر + ترتيب العرض.
  • حافظ على الفهرس صغيراً (عمودان غالباً، ثلاثة أحياناً).
  • ضع الفلتر الأكثر انتقائية أولاً، ثم عمود الفرز.
  • تفضّل فهرساً واحداً جيد الاختيار على عدة فهارس "قد تفيد".

مثال: إذا كنت تستعلم وتعرض الأحدث أولاً، فهرس مثل مناسب.

متى يستحق الفهرس الجزئي الاستخدام في Postgres؟

استخدم فهرساً جزئياً عندما يستهدف معظم الحركة شريحة متوقعة من الصفوف.

نمط مثال:

  • قراءات كثيرة على active = true
  • قليل من الاستعلامات للصفوف غير النشطة

فهرس جزئي مثل ... WHERE active = true يبقى أصغر، أكثر احتمالاً للبقاء في الذاكرة، ويقلل تحميل الكتابة مقارنة بفهرسة كل شيء.

تحقق عبر أن Postgres فعلاً يستخدمه لطلبات المرور العالي.

لماذا يصبح LIMIT/OFFSET أبطأ مع الوقت، وماذا أستخدم بدلاً منه؟

LIMIT/OFFSET يزداد بطئاً في الصفحات العميقة لأن Postgres يظل يمر على (وغالباً يرتّب) الصفوف التي تتخطاها. الصفحة 1 قد تلمس عدد صفوف قليل؛ الصفحة 500 قد تجبر قاعدة البيانات على مسح واستبعاد عشرات الآلاف لإرجاع 20 نتيجة.

فضلًا عن ذلك استخدم ترقيم keyset (cursor):

استعلامات قاعدة البيانات سريعة لكن الاستجابات ما تزال بطيئة—هل أُقلّص JSON؟

عادةً نعم، خصوصاً لنقاط القوائم. أسرع استجابة هي التي لا ترسلها.

خدمات عملية:

  • استعلم فقط عن الأعمدة المطلوبة (تجنّب SELECT *).
  • أضف include= أو fields= ليختار العميل الحقول الثقيلة.
المحتويات
كيف يظهر "البطء" لواجهات API المكتوبة بـ Go على Postgresالأساس أولاً: الأرقام القليلة المهمةتجمّع الاتصالات الذي يحفظ استقرار Postgresتخطيط الاستعلام: قراءة سريعة لإخراج EXPLAINالفهرسة وفق الاستعلامات التي تنفذها فعلياًأنماط الترقيم التي لا تبطئ مع الوقتتشكيل JSON: استجابات أسرع بحمولات أصغرتمريرة ضبط خطوة بخطوة قبل أول المستخدمينأخطاء شائعة وفخاخ تجنبهاقائمة تحقق سريعة وخطوات تالية قبل الإطلاقالأسئلة الشائعة
مشاركة
Koder.ai
أنشئ تطبيقك الخاص مع Koder اليوم!

أفضل طريقة لفهم قوة Koder هي تجربتها بنفسك.

ابدأ مجاناًاحجز عرضاً توضيحياً
  • Sort يسيطر على الوقت (غالباً مع ORDER BY)
  • "Rows Removed by Filter" مرتفعة جداً
  • الكثير من shared read blocks في BUFFERS (قراءات ثقيلة)
  • اصلح أكبر علامة حمراء أولاً؛ لا تحاول ضبط كل شيء دفعة واحدة.

    GET /orders?status=paid
    (status, created_at DESC)
    EXPLAIN
  • استخدم ترتيباً ثابتاً مع فاصل فريد (غالباً id).
  • اجعل ORDER BY مماثلاً عبر الطلبات.
  • رمّز (created_at, id) أو ما شابه إلى cursor.
  • بهذه الطريقة تبقى تكلفة كل صفحة تقريباً ثابتة مع نمو الجداول.

  • حدّ المصفوفات المتداخلة (مثلاً أحدث 10 عناصر) ووفّر واجهة منفصلة للتاريخ الكامل.
  • تجنّب نمط N+1 (قائمة 50 صف + 50 استعلام إضافي). استخدم joins أو استعلامات مجمّعة بالمعرفات.
  • ستقلّ غالباً CPU وذاكرة Go وزمن الذيل فقط بتقليص الحمولات.