استكشاف أخطاء الذاكرة والأداء وإصلاحها
النطاق
قسم بعنوان «النطاق»تغطّي هذه المُدخَلات عائلتي فشل تصيبهما تحت الحمل: نفاد ذاكرة PHP أثناء عرض، وإنتاجية تهوي من حافّة بمجرد أن تكون عملية دافئة أو مُشبَعة. وتسمّي كل مُدخَلة عَرَضًا، والسبب الأرجح، وإصلاحًا يستخدم سطح NextPDF حقيقيًا أو ضوابط PHP-FPM القياسية. ولنموذج البثّ الأساس ولدرس عامل، اقرأ البثّ والذاكرة؛ هذه الصفحة هي رفيق هذا النموذج من جانب الحوادث.
قِس أولًا. خذ عيّنة من memory_get_peak_usage(true) قبل العرض وبعده واستدعِ
memory_reset_peak_usage() بين التكرارات، بالطريقة التي يعزل بها قياس المحرّك كلفة العرض
الواحد. الضبط بلا خطّ أساس يحرّك الحافّة بدلًا من إزالتها.
مُدخَلة: “Allowed memory size exhausted” أثناء التوليد
قسم بعنوان «مُدخَلة: “Allowed memory size exhausted” أثناء التوليد»- العَرَض. يُجهَض عرض بخطأ مميت
Allowed memory size of <n> bytes exhaustedمن بيئة تشغيل PHP، غالبًا على مستند كبير أو ثقيل الصور. - السبب الأرجح. يؤلّف مسار الكتابة الافتراضيّ المستند كله، ثم يُسلسِله، فتتبع الذروة
في الذاكرة إجمالي حجم الخرج. ومستند كبير أو صور مضمّنة كبيرة أو وجه خط مضمّن كبير قد
يدفع الطلب عبر
memory_limit. - الحلّ.
- حُدّ خبيئة الصور. يكشف
NextPDF\Core\ConfigعنimageCacheBytes(الافتراضي52428800، أي 50 MB). أخفضه بالـwither المثيليّ$config->withImageCacheBytes($bytes)(التوقيعwithImageCacheBytes(int $bytes): self) فيفشل بناء يضمّن صورًا كثيرة سريعًا عند سقف معلوم بدلًا من المبادلة. وهذا يحدّ خبيئة الصور في الذاكرة؛ ولا يعيد أخذ عيّنات الصور نفسها أو ترميزها. - قلّص المدخلات قبل التضمين. لا يخفّض Core مقياس الصور أو يعيد ترميزها. أعد تحجيم الفنّ النقطيّ المفرط وترميزه قبل أن تضمّنه، وضمّن الخطوط التي تستخدمها فعلًا فيكون للتقليص مجموعة محارف صغيرة يبقيها (انظر قلّص حجم ملف PDF).
- أبقِ الضغط مشغّلًا. لـ
Configجديدcompressمضبوط علىtrue. اتركه مشغّلًا للبناءات العادية؛ وwithCompress(false)ليس تحسين حجم (فهو يزيد الخرج عادةً). الجأ إليه لتنقيح خط الأنابيب أو قياسه — فهو يبدّل مقايضة وحدة المعالجة/الذاكرة (بتخطّي خطوة الضغط) بدلًا من تقليل الذاكرة. - ارفع
memory_limitعمدًا، لكل عامل. هذا إعداد PHP قياسيّ، لا مفتاح NextPDF. اضبطه في تهيئة التجمّع أو بـini_set('memory_limit', '256M')لعملية CLI/الطوابير، وحجّمه مقابل ذروة مقيسة، لا تخمين.
- حُدّ خبيئة الصور. يكشف
- ذو صلة. البثّ والذاكرة.
مُدخَلة: تنمو الذاكرة مع عدد الصفحات على المستندات الكبيرة جدًّا
قسم بعنوان «مُدخَلة: تنمو الذاكرة مع عدد الصفحات على المستندات الكبيرة جدًّا»- العَرَض. يستنزف مستند من آلاف الصفحات الذاكرة رغم أن كل صفحة صغيرة، وترتفع الذروة تقريبًا بمواكبة عدد الصفحات.
- السبب الأرجح. يمسك الكاتب المُخزَّن المستند المُسلسَل كله في الكومة. وللمستندات الكبيرة جدًّا تلك الكلفة المهيمنة.
- الحلّ.
- فضّل مسار الكتابة المُبَثّ. استخدم مسار الكتابة المُبَثّ الموثّق الموصوف في
البثّ والذاكرة: فهو يُسلسِل كل صفحة حين
تُؤلَّف ويحرّر المخزن المؤقّت، فيقلّل نموّ مخزن الصفحات/الخرج؛ ويمكن أن تظلّ
البيانات الوصفية الصغيرة لكل كائن (الإزاحات، شجرة الصفحات) تتدرّج مع عدد
الصفحات/الكائنات. اتبع نقطة الدخول الموثّقة بدلًا من نسخ أصناف داخلية — فمحرّك البثّ
الأساس من طبقة
experimentalورموزه ليست السطح العام المستقرّ. - لمحلّل
writeHtml()الأصلي، تذكّر أن الذاكرة من جانب المدخل محدودة بحارستي عمق التداخل وعدد العناصر معًا: تحدّ ADR-001 التداخل عندMAX_NESTING_DEPTH = 100وترفض المستندات فوقMAX_ELEMENT_COUNT = 50000. والمستند الذي يبلغ حدّ العناصر يُخبَر بذلك صراحةً بدلًا من استنزاف الذاكرة بصمت. وتحكم حدود ADR-001 هذه المحلّل الأصلي فقط؛ أما جسر Chrome الاختياريّ (writeHtmlChrome()) فيعرض خارج العملية وله حدوده الخاصة المنفصلة للذاكرة/المدخل، لا هذه الحدود.
- فضّل مسار الكتابة المُبَثّ. استخدم مسار الكتابة المُبَثّ الموثّق الموصوف في
البثّ والذاكرة: فهو يُسلسِل كل صفحة حين
تُؤلَّف ويحرّر المخزن المؤقّت، فيقلّل نموّ مخزن الصفحات/الخرج؛ ويمكن أن تظلّ
البيانات الوصفية الصغيرة لكل كائن (الإزاحات، شجرة الصفحات) تتدرّج مع عدد
الصفحات/الكائنات. اتبع نقطة الدخول الموثّقة بدلًا من نسخ أصناف داخلية — فمحرّك البثّ
الأساس من طبقة
- ذو صلة. البثّ والذاكرة.
مُدخَلة: عامل طويل العمر يستنزف الذاكرة بعد وظائف كثيرة
قسم بعنوان «مُدخَلة: عامل طويل العمر يستنزف الذاكرة بعد وظائف كثيرة»- العَرَض. تنجح العروض المفردة، لكن عامل طوابير يعرض ملفات PDF كثيرة متتالية يستنزف الذاكرة بعد دقائق أو ساعات.
- السبب الأرجح. تراكم عملية PHP طويلة العمر تخصيصات عبر الوظائف. ونموّ بطيء غير مرئيّ في طلب واحد يتراكم عبر آلاف.
- الحلّ.
- شارِك السجلّات، وأعد إنشاء المستندات. ابنِ
FontRegistryوImageRegistryمرة عند الإقلاع ومرّرهما إلىDocumentFactory؛ وأنشئDocumentجديدًا لكل وظيفة بـ$factory->create($config). عندها يحدث تحليل الخطوط والصور مرة للعملية، لا مرة لكل وظيفة، وتُجمَع شجرة المستند لكل وظيفة حين تخرج عن النطاق. اتبعexamples/14-worker-factory.php. - حُدّ خبيئة الصور المشتركة بـ
new ImageRegistry(maxCacheBytes: ...)فلا تنمو بلا حدّ عبر الوظائف. - أعد تدوير العامل — تحكّم في العملية، لا ضمان من المحرّك. في PHP-FPM، اضبط
pm.max_requestsفيعيد كل ابن إنتاج نفسه بعد عدد ثابت من الطلبات. في طوابير Laravel استخدمqueue:work --max-jobs/--max-time/--memory؛ وفي Symfony Messenger استخدمmessenger:consume --limit/--time-limit/--memory-limit.
- شارِك السجلّات، وأعد إنشاء المستندات. ابنِ
- ذو صلة. البثّ والذاكرة.
مُدخَلة: حافّة إنتاجية على عملية باردة أو غير دافئة كفايةً
قسم بعنوان «مُدخَلة: حافّة إنتاجية على عملية باردة أو غير دافئة كفايةً»- العَرَض. أول العروض في عملية جديدة بطيئة، أو يدفع كل طلب كلفة تحليل ينبغي ألا تدفعها الطلبات الدافئة.
- السبب الأرجح. تتراكم كلفتا بدء بارد. فـPHP بلا opcache يعيد تصريف كل ملف في كل
طلب، و
FontRegistryغير المُسخَّن يحلّل كل وجه خط أول مرة يُستخدم. - الحلّ.
- مكّن opcache (وJIT حيث ينفع). اضبط
opcache.enable=1وopcache.memory_consumptionسخيًّا؛ وفي الإنتاج اضبطopcache.validate_timestamps=0فلا تُعاد فحص الخبيئة لكل طلب. ويتطلّب ذلك الإعداد عملية نشر تعيد تشغيل PHP-FPM أو تعيد تحميله (أو تعيد ضبط opcache بطريقة أخرى، مثلopcache_reset()/cachetool) في كل إصدار — وإلا ظلّ opcache يخدم أكواد التشغيل القديمة وعملت شيفرة قديمة بعد النشر. هذه إعدادات ini قياسية لـPHP، لا مفاتيح NextPDF. - سخّن سجلّ الخطوط واقفله عند الإقلاع. على مثيل
FontRegistry، يحلّل$fontRegistry->warmup($fontFiles)الأوجه مرة أثناء الإقلاع، ويُجمِّد$fontRegistry->lock()السجلّ فلا تستطيع شيفرة وقت الطلب تغيير الحالة المشتركة؛ ويبلّغ$fontRegistry->isLocked()عن الحالة. في عامل أو خادم تطبيق طويل العمر حقًّا — مستهلك طوابير أو عامل RoadRunner/Swoole/Octane يُبقي عملية PHP نفسها حيّة عبر طلبات كثيرة — يُبقي سجلّ مُسخَّن مقفل أوجهه المُحلّلة في حالة الكائن، فيحوّل تحليل الخطوط لكل طلب إلى كلفة إقلاع عملية لمرة واحدة. وتحت نموذج طلب PHP-FPM القياسيّ لا تنجو حالة الكائن المُسخَّنة تلك عبر الطلبات: يخبّئ opcache الأصناف المُصرَّفة وأكواد التشغيل، لا حالة كائن المستوى المستخدميّ المُسخَّنة، فيُعاد بناءFontRegistryمُسخَّن لكل طلب (يُعاد تشغيله كل طلب من إقلاع الابن)، لا يُبقى دافئًا عبر الطلبات داخل ابن. وعلى PHP-FPM العادي، يطفّئ opcache بالأساس كلفة إعادة تصريف أكواد التشغيل؛ واقبل أن تحليل الخطوط يُدفَع لكل طلب، لا يُلغى. وتطفئة الكلفة عبر الطلبات — تحليل كل وجه مرة لعمر العملية — لا تنطبق إلا في عملية طويلة العمر حقًّا مثل عامل RoadRunner/Swoole/Octane أو مستهلك طوابير يُبقي عملية PHP نفسها حيّة عبر طلبات كثيرة. - لا تعيد تحليل القالب نفسه لكل طلب. حُلّ الخطوط والموارد القابلة لإعادة الاستخدام
مرة عند الإقلاع عبر السجلّات المشتركة؛ ولا يُنشأ في الطلب إلا
Documentلكل وظيفة.
- مكّن opcache (وJIT حيث ينفع). اضبط
- ذو صلة. البثّ والذاكرة.
مُدخَلة: يُشبَع الخادم وترتفع زمن الاستجابة فجأةً تحت التزامن
قسم بعنوان «مُدخَلة: يُشبَع الخادم وترتفع زمن الاستجابة فجأةً تحت التزامن»- العَرَض. زمن استجابة العرض الواحد جيّد بمعزل، لكن تحت الحمل يبادل الجهاز، أو تشبع وحدة المعالجة، أو تصطفّ الطلبات وتنتهي مهلتها.
- السبب الأرجح. عمّال PHP-FPM أكثر من ذاكرة الوصول العشوائي المتاحة، فيتجاوز مجموع ذرى العمّال الذاكرة الفيزيائية فيبادل المضيف؛ أو عمّال أقلّ من اللازم، فتتسلسل الطلبات خلف تجمّع صغير.
- الحلّ.
-
حجّم
pm.max_childrenمن ذروة مقيسة. استخدم الصيغة القياسية:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryقِس ذروة عامل الحقيقية بمستند ممثّل (انظر ملاحظة القياس في النطاق)، واحجز هامشًا لنظام التشغيل وأي خدمات مشتركة، واقسم. اترك هامشًا؛ ولا تحجّم إلى 100% من ذاكرة الوصول العشوائي.
-
ثبّت كلفة الضغط في ميزانيتك. يمكن أن يكون ضغط Flate كلفة وحدة معالجة كبيرة لكتابة مجرى ويتدرّج مع حجم بايتات المجرى القابلة للضغط، فيؤثّر عدد الصفحات وحجم الخط المضمّن في وحدة المعالجة لكل عرض؛ ويمكن أن تهيمن أيضًا معالجة الصور وتقليص الخطوط وتحليل المدخل. قِس بمستندات ممثّلة، واحسب حساب المُحرِّك الحقيقيّ حين تختار عدد العمّال ووحدة المعالجة.
-
اضبط
pm.max_requestsإلى جانبpm.max_childrenفيعيد الأبناء التدوير ويستردّون أي نموّ بطيء، كما في مُدخَلة العامل أعلاه.
-
- ذو صلة. البثّ والذاكرة.
مُدخَلة: مدخل كبير غير موثوق بطيء أو مكلف التحليل
قسم بعنوان «مُدخَلة: مدخل كبير غير موثوق بطيء أو مكلف التحليل»- العَرَض. عرض بطيء أو ثقيل الذاكرة على مدخل كبير أو عميق التداخل، خاصةً HTML أو خط لم تنتجه أنت.
- السبب الأرجح. تتدرّج كلفة التحليل مع حجم المدخل وبنيته. ومدخل مرضيّ (تداخل عميق، أو عدد عناصر هائل، أو خط مشوّه) يمكن أن يهيمن على الميزانية.
- الحلّ.
- اتّكئ على حدود المحرّك. يفرض محلّل HTML الأصلي
writeHtml()MAX_NESTING_DEPTH = 100وMAX_ELEMENT_COUNT = 50000(ADR-001)؛ والمدخلات فوق تلك الحدود تُرفض بدلًا من السماح لها باستنزاف العملية. (جسر Chrome الاختياريّ، writeHtmlChrome()، خارج نطاق حدود ADR-001 هذه ويفرض حدوده الخاصة المنفصلة للذاكرة/المدخل.) - عامِل الخطوط المزوَّدة من المستدعي بوصفها غير موثوقة. يرفع خط مشوّه
NextPDF\Exception\FontParsingExceptionبدلًا من إفساد الخرج، فالتقط الاستثناء المحدّد وارفض المدخل بدلًا من إعادة المحاولة. - تحقّق من المدخلات وحجّمها عند حدّك، وطبّق حدودًا على مستوى الطلب على حجم المستند للمحتوى المتأثّر بالمستدعي.
- اتّكئ على حدود المحرّك. يفرض محلّل HTML الأصلي
- ذو صلة. استكشاف الأخطاء: الخطوط والوسم.
جدول القرار: من العَرَض إلى الرافعة
قسم بعنوان «جدول القرار: من العَرَض إلى الرافعة»| العَرَض | الرافعة الأرجح |
|---|---|
Allowed memory size … exhausted على عرض واحد | أخفض $config->withImageCacheBytes()؛ قلّص الصور قبل التضمين؛ ارفع memory_limit لكل عامل |
| ترتفع الذروة في الذاكرة مع عدد الصفحات | استخدم مسار الكتابة المُبَثّ الموثّق |
| تتسلّق ذاكرة العامل عبر وظائف كثيرة | شارِك FontRegistry/ImageRegistry عبر DocumentFactory؛ اضبط pm.max_requests / --max-jobs |
| أول الطلبات بطيء، كلفة تحليل لكل طلب | مكّن opcache؛ $fontRegistry->warmup() ثم ->lock() عند الإقلاع |
| يبادل المضيف / ترتفع زمن الاستجابة تحت الحمل | حجّم pm.max_children = (RAM − النفقات) / ذروة كل عامل |
| بطيء أو ثقيل على مدخل كبير/غير موثوق | اتّكئ على حدود ADR-001؛ ارفض الخطوط المشوّهة على FontParsingException |
الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»-
imageCacheBytesسقف ذاكرة، لا مقبض حجم. خفضه يحدّ الخبيئة فيفشل بناء سريعًا؛ ولا يعيد أخذ عيّنات الصور التي تضمّنها أو ترميزها أبدًا. ليس لـCore تحكّم في جودة الصورة. -
withCompress(false)يجعل الملفات أكبر وهو معين تنقيح/قياس. وليس تحسين حجم؛ فهو يبدّل مقايضة وحدة المعالجة/الذاكرة (يتخطّى خطوة الضغط) بدلًا من تقليل الذاكرة. - ملف الذاكرة الدقيق لمحرّك البثّ خاصية من طبقة
experimentalوقد يتبدّل بين الإصدارات الثانوية. عامِل أي قياس مفرد بوصفه ملاحظة، لا ثابتًا قابلًا للنقل. -
memory_limitوopcache.*وpm.max_childrenوpm.max_requestsإعدادات PHP / PHP-FPM قياسية. لا يكشف NextPDF عن مفاتيحه الخاصة لها؛ هيّئها في بيئة تشغيلك، لا فيConfig.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- البثّ والذاكرة — نموذج البثّ، وحدود ADR-001، ودرس عامل الدفعة الكامل.
- قلّص حجم ملف PDF — الضغط وتقليص الخطوط، ضابطا الحجم الحقيقيان.
- استكشاف الأخطاء: الخطوط والوسم — حلّ الخطوط وتحليلها وحالات فشل التقليص.
- فهرس قاعدة المعرفة
المسرد: الكاتب المُبَثّ · تقليص الخطوط