تخطَّ إلى المحتوى
getnextpdf.com

استكشاف أخطاء الذاكرة والأداء وإصلاحها

تغطّي هذه المُدخَلات عائلتي فشل تصيبهما تحت الحمل: نفاد ذاكرة ⁨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.
  • الحلّ.
    1. حُدّ خبيئة الصور. يكشف NextPDF\Core\Config عن imageCacheBytes (الافتراضي 52428800، أي 50 MB). أخفضه بالـ⁨wither⁩ المثيليّ $config->withImageCacheBytes($bytes) (التوقيع withImageCacheBytes(int $bytes): self) فيفشل بناء يضمّن صورًا كثيرة سريعًا عند سقف معلوم بدلًا من المبادلة. وهذا يحدّ خبيئة الصور في الذاكرة؛ ولا يعيد أخذ عيّنات الصور نفسها أو ترميزها.
    2. قلّص المدخلات قبل التضمين. لا يخفّض ⁨Core⁩ مقياس الصور أو يعيد ترميزها. أعد تحجيم الفنّ النقطيّ المفرط وترميزه قبل أن تضمّنه، وضمّن الخطوط التي تستخدمها فعلًا فيكون للتقليص مجموعة محارف صغيرة يبقيها (انظر قلّص حجم ملف ⁨PDF⁩).
    3. أبقِ الضغط مشغّلًا. لـConfig جديد compress مضبوط على true. اتركه مشغّلًا للبناءات العادية؛ وwithCompress(false) ليس تحسين حجم (فهو يزيد الخرج عادةً). الجأ إليه لتنقيح خط الأنابيب أو قياسه — فهو يبدّل مقايضة وحدة المعالجة/الذاكرة (بتخطّي خطوة الضغط) بدلًا من تقليل الذاكرة.
    4. ارفع memory_limit عمدًا، لكل عامل. هذا إعداد ⁨PHP⁩ قياسيّ، لا مفتاح ⁨NextPDF.⁩ اضبطه في تهيئة التجمّع أو بـini_set('memory_limit', '256M') لعملية ⁨CLI⁩/الطوابير، وحجّمه مقابل ذروة مقيسة، لا تخمين.
  • ذو صلة. البثّ والذاكرة.

مُدخَلة: تنمو الذاكرة مع عدد الصفحات على المستندات الكبيرة جدًّا

قسم بعنوان «مُدخَلة: تنمو الذاكرة مع عدد الصفحات على المستندات الكبيرة جدًّا»
  • العَرَض. يستنزف مستند من آلاف الصفحات الذاكرة رغم أن كل صفحة صغيرة، وترتفع الذروة تقريبًا بمواكبة عدد الصفحات.
  • السبب الأرجح. يمسك الكاتب المُخزَّن المستند المُسلسَل كله في الكومة. وللمستندات الكبيرة جدًّا تلك الكلفة المهيمنة.
  • الحلّ.
    1. فضّل مسار الكتابة المُبَثّ. استخدم مسار الكتابة المُبَثّ الموثّق الموصوف في البثّ والذاكرة: فهو يُسلسِل كل صفحة حين تُؤلَّف ويحرّر المخزن المؤقّت، فيقلّل نموّ مخزن الصفحات/الخرج؛ ويمكن أن تظلّ البيانات الوصفية الصغيرة لكل كائن (الإزاحات، شجرة الصفحات) تتدرّج مع عدد الصفحات/الكائنات. اتبع نقطة الدخول الموثّقة بدلًا من نسخ أصناف داخلية — فمحرّك البثّ الأساس من طبقة experimental ورموزه ليست السطح العام المستقرّ.
    2. لمحلّل writeHtml() الأصلي، تذكّر أن الذاكرة من جانب المدخل محدودة بحارستي عمق التداخل وعدد العناصر معًا: تحدّ ADR-001 التداخل عند MAX_NESTING_DEPTH = 100 وترفض المستندات فوق MAX_ELEMENT_COUNT = 50000. والمستند الذي يبلغ حدّ العناصر يُخبَر بذلك صراحةً بدلًا من استنزاف الذاكرة بصمت. وتحكم حدود ADR-001 هذه المحلّل الأصلي فقط؛ أما جسر ⁨Chrome⁩ الاختياريّ ‏(writeHtmlChrome()) فيعرض خارج العملية وله حدوده الخاصة المنفصلة للذاكرة/المدخل، لا هذه الحدود.
  • ذو صلة. البثّ والذاكرة.

مُدخَلة: عامل طويل العمر يستنزف الذاكرة بعد وظائف كثيرة

قسم بعنوان «مُدخَلة: عامل طويل العمر يستنزف الذاكرة بعد وظائف كثيرة»
  • العَرَض. تنجح العروض المفردة، لكن عامل طوابير يعرض ملفات ⁨PDF⁩ كثيرة متتالية يستنزف الذاكرة بعد دقائق أو ساعات.
  • السبب الأرجح. تراكم عملية ⁨PHP⁩ طويلة العمر تخصيصات عبر الوظائف. ونموّ بطيء غير مرئيّ في طلب واحد يتراكم عبر آلاف.
  • الحلّ.
    1. شارِك السجلّات، وأعد إنشاء المستندات. ابنِ FontRegistry وImageRegistry مرة عند الإقلاع ومرّرهما إلى DocumentFactory؛ وأنشئ Document جديدًا لكل وظيفة بـ $factory->create($config). عندها يحدث تحليل الخطوط والصور مرة للعملية، لا مرة لكل وظيفة، وتُجمَع شجرة المستند لكل وظيفة حين تخرج عن النطاق. اتبع examples/14-worker-factory.php.
    2. حُدّ خبيئة الصور المشتركة بـnew ImageRegistry(maxCacheBytes: ...) فلا تنمو بلا حدّ عبر الوظائف.
    3. أعد تدوير العامل — تحكّم في العملية، لا ضمان من المحرّك. في ⁨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 غير المُسخَّن يحلّل كل وجه خط أول مرة يُستخدم.
  • الحلّ.
    1. مكّن ⁨opcache⁩ (و⁨JIT⁩ حيث ينفع). اضبط opcache.enable=1 و opcache.memory_consumption سخيًّا؛ وفي الإنتاج اضبط opcache.validate_timestamps=0 فلا تُعاد فحص الخبيئة لكل طلب. ويتطلّب ذلك الإعداد عملية نشر تعيد تشغيل ⁨PHP-FPM⁩ أو تعيد تحميله (أو تعيد ضبط ⁨opcache⁩ بطريقة أخرى، مثل opcache_reset() / cachetool) في كل إصدار — وإلا ظلّ ⁨opcache⁩ يخدم أكواد التشغيل القديمة وعملت شيفرة قديمة بعد النشر. هذه إعدادات ⁨ini⁩ قياسية لـ⁨PHP،⁩ لا مفاتيح ⁨NextPDF.⁩
    2. سخّن سجلّ الخطوط واقفله عند الإقلاع. على مثيل FontRegistry، يحلّل $fontRegistry->warmup($fontFiles) الأوجه مرة أثناء الإقلاع، ويُجمِّد $fontRegistry->lock() السجلّ فلا تستطيع شيفرة وقت الطلب تغيير الحالة المشتركة؛ ويبلّغ $fontRegistry->isLocked() عن الحالة. في عامل أو خادم تطبيق طويل العمر حقًّا — مستهلك طوابير أو عامل ⁨RoadRunner⁩/⁨Swoole⁩/⁨Octane⁩ يُبقي عملية ⁨PHP⁩ نفسها حيّة عبر طلبات كثيرة — يُبقي سجلّ مُسخَّن مقفل أوجهه المُحلّلة في حالة الكائن، فيحوّل تحليل الخطوط لكل طلب إلى كلفة إقلاع عملية لمرة واحدة. وتحت نموذج طلب ⁨PHP-FPM⁩ القياسيّ لا تنجو حالة الكائن المُسخَّنة تلك عبر الطلبات: يخبّئ ⁨opcache⁩ الأصناف المُصرَّفة وأكواد التشغيل، لا حالة كائن المستوى المستخدميّ المُسخَّنة، فيُعاد بناء FontRegistry مُسخَّن لكل طلب (يُعاد تشغيله كل طلب من إقلاع الابن)، لا يُبقى دافئًا عبر الطلبات داخل ابن. وعلى ⁨PHP-FPM⁩ العادي، يطفّئ ⁨opcache⁩ بالأساس كلفة إعادة تصريف أكواد التشغيل؛ واقبل أن تحليل الخطوط يُدفَع لكل طلب، لا يُلغى. وتطفئة الكلفة عبر الطلبات — تحليل كل وجه مرة لعمر العملية — لا تنطبق إلا في عملية طويلة العمر حقًّا مثل عامل ⁨RoadRunner⁩/⁨Swoole⁩/⁨Octane⁩ أو مستهلك طوابير يُبقي عملية ⁨PHP⁩ نفسها حيّة عبر طلبات كثيرة.
    3. لا تعيد تحليل القالب نفسه لكل طلب. حُلّ الخطوط والموارد القابلة لإعادة الاستخدام مرة عند الإقلاع عبر السجلّات المشتركة؛ ولا يُنشأ في الطلب إلا Document لكل وظيفة.
  • ذو صلة. البثّ والذاكرة.

مُدخَلة: يُشبَع الخادم وترتفع زمن الاستجابة فجأةً تحت التزامن

قسم بعنوان «مُدخَلة: يُشبَع الخادم وترتفع زمن الاستجابة فجأةً تحت التزامن»
  • العَرَض. زمن استجابة العرض الواحد جيّد بمعزل، لكن تحت الحمل يبادل الجهاز، أو تشبع وحدة المعالجة، أو تصطفّ الطلبات وتنتهي مهلتها.
  • السبب الأرجح. عمّال ⁨PHP-FPM⁩ أكثر من ذاكرة الوصول العشوائي المتاحة، فيتجاوز مجموع ذرى العمّال الذاكرة الفيزيائية فيبادل المضيف؛ أو عمّال أقلّ من اللازم، فتتسلسل الطلبات خلف تجمّع صغير.
  • الحلّ.
    1. حجّم pm.max_children من ذروة مقيسة. استخدم الصيغة القياسية:

      pm.max_children = (total RAM - OS/other overhead) / per-worker peak memory

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

    2. ثبّت كلفة الضغط في ميزانيتك. يمكن أن يكون ضغط ⁨Flate⁩ كلفة وحدة معالجة كبيرة لكتابة مجرى ويتدرّج مع حجم بايتات المجرى القابلة للضغط، فيؤثّر عدد الصفحات وحجم الخط المضمّن في وحدة المعالجة لكل عرض؛ ويمكن أن تهيمن أيضًا معالجة الصور وتقليص الخطوط وتحليل المدخل. قِس بمستندات ممثّلة، واحسب حساب المُحرِّك الحقيقيّ حين تختار عدد العمّال ووحدة المعالجة.

    3. اضبط pm.max_requests إلى جانب pm.max_children فيعيد الأبناء التدوير ويستردّون أي نموّ بطيء، كما في مُدخَلة العامل أعلاه.

  • ذو صلة. البثّ والذاكرة.

مُدخَلة: مدخل كبير غير موثوق بطيء أو مكلف التحليل

قسم بعنوان «مُدخَلة: مدخل كبير غير موثوق بطيء أو مكلف التحليل»
  • العَرَض. عرض بطيء أو ثقيل الذاكرة على مدخل كبير أو عميق التداخل، خاصةً ⁨HTML⁩ أو خط لم تنتجه أنت.
  • السبب الأرجح. تتدرّج كلفة التحليل مع حجم المدخل وبنيته. ومدخل مرضيّ (تداخل عميق، أو عدد عناصر هائل، أو خط مشوّه) يمكن أن يهيمن على الميزانية.
  • الحلّ.
    1. اتّكئ على حدود المحرّك. يفرض محلّل ⁨HTML⁩ الأصلي writeHtml()MAX_NESTING_DEPTH = 100 وMAX_ELEMENT_COUNT = 50000 ‏(⁨ADR-001⁩)؛ والمدخلات فوق تلك الحدود تُرفض بدلًا من السماح لها باستنزاف العملية. (جسر ⁨Chrome⁩ الاختياريّ، ‏writeHtmlChrome()، خارج نطاق حدود ⁨ADR-001⁩ هذه ويفرض حدوده الخاصة المنفصلة للذاكرة/المدخل.)
    2. عامِل الخطوط المزوَّدة من المستدعي بوصفها غير موثوقة. يرفع خط مشوّه NextPDF\Exception\FontParsingException بدلًا من إفساد الخرج، فالتقط الاستثناء المحدّد وارفض المدخل بدلًا من إعادة المحاولة.
    3. تحقّق من المدخلات وحجّمها عند حدّك، وطبّق حدودًا على مستوى الطلب على حجم المستند للمحتوى المتأثّر بالمستدعي.
  • ذو صلة. استكشاف الأخطاء: الخطوط والوسم.

جدول القرار: من العَرَض إلى الرافعة

قسم بعنوان «جدول القرار: من العَرَض إلى الرافعة»
العَرَضالرافعة الأرجح
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.

المسرد: الكاتب المُبَثّ · تقليص الخطوط