الدفق والذاكرة: دليل عملي لقياس الأداء وعمال المعالجة على دفعات
لمحة سريعة
قسم بعنوان «لمحة سريعة»يُجري NextPDF العرض في تمريرة واحدة ولا يحتفظ مطلقًا بنموذج كائنات المستند (DOM) على مستوى المستند؛ لذلك تكون ذاكرة جانب الإدخال محدودة بعمق التداخل، لا بعدد العناصر. تشرح هذه الصفحة نموذج الدفق، والقيود الواردة في سجل قرار المعمارية (ADR)-001، وكيف تشغّل المحرّك بأمان داخل عامل طابور طويل العمر.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3نظرة عامة مفاهيمية
قسم بعنوان «نظرة عامة مفاهيمية»لدى NextPDF مسارا كتابة يختلفان في نمط استهلاك الذاكرة.
يُنشئ كاتب الذاكرة الافتراضي المستند بأكمله، ثم يُسلسله. ويرتبط الحد الأقصى للذاكرة بإجمالي حجم المُخرَج. يعمل ذلك جيدًا مع المستندات المعتادة، لكنه قد يكون مكلفًا مع المستندات الكبيرة جدًا.
يُسلسل كاتب الدفق كل صفحة أثناء إنشائها، ثم يُفرغها قبل بدء الصفحة التالية. المحرّك المُسلَّم — StreamingPdfWriter وStreamingCursor وDevNullWriter، والتعداد WriterState في src/Writer/Streaming/ — حقيقي ونهائي ومُختبَر ومُسلَّم منذ الإصدار 3.1.0. وهو متاح عبر العقدين StreamingWriterInterface وCursorInterface من الفئة experimental. أصناف المحرّك داخلية، لذا اعتمد على العقود ودع Core يوفّر التنفيذ. (وصف تعليق سابق في .ai/contracts-map.md الدفق خطأً بأنه “عقد فقط / دون تنفيذ”؛ ويُتعقَّب عيب ذلك التعليق القديم في المسألة #610 وقد صُحِّح في وثائق عقد B1 — فالمحرّك مُسلَّم منذ الإصدار 3.1.0.)
صُمِّم محرّك الدفق بحيث لا تنمو الذاكرة المقيمة مع عدد الصفحات. يُسلَّم المخزن المؤقت لكل صفحة مكتملة إلى الكاتب ثم يُحرَّر. يُكتَب جدول الإحالات المتقاطعة ومراجع شجرة الصفحات /Kids إلى أدفاق مؤقتة من نوع php://temp/maxmemory:0 تنسكب إلى القرص فورًا بدلًا من التراكم في كومة PHP. والنتيجة المُسلسَلة هي شجرة صفحات قياسية يكون مُدخَل Count فيها عددَ العُقد الورقية (كائنات الصفحات) المتفرّعة من عُقدة ما (ISO 32000-2 §7.7.3.3)، ويكون مُدخَل Kids فيها مصفوفةً من المراجع غير المباشرة إلى الأبناء المباشرين لتلك العُقدة (ISO 32000-2 §7.7.3.2). نمط الذاكرة الدقيق خاصية من الفئة experimental وقد يتغيّر بين الإصدارات الثانوية، لذا لا تُثبّت افتراضًا مستمدًا من قياس واحد في شِفرتك.
يحكم ADR-001 نموذج الذاكرة لخط أنابيب عرض HTML. يُنتج المُجزِّئ قائمة رموز في تمريرة واحدة. يستهلكها المُحلِّل من اليسار إلى اليمين ويُصدِر معاملات دفق المحتوى في مخزن سلاسل مؤقت. لا تُبنى أي شجرة عناصر دائمة: يحتفظ المُحلِّل بحالة HtmlStyleState واحدة على الأكثر لكل مستوى تداخل، محدودةً بـ MAX_NESTING_DEPTH = 100، ويفرض حدًا أقصى صارمًا قدره MAX_ELEMENT_COUNT = 50_000. العمليتان اللتان تتطلّبان نظرًا مسبقًا — تحديد أحجام أعمدة الجداول وعائلة المُحدِّدات :has() / :last-child — تستخدمان مصفوفات مفهرسة ومحدودة للمسح المسبق فوق قائمة الرموز المسطّحة، لا شجرة DOM محتفَظًا بها. قاس معيار المرحلة 0 (docs/architecture/adr-001-memory-benchmark.md، نُفِّذ بتاريخ 2026-04-06، على PHP 8.5.3، بإعداد memory_limit=1G) مستندًا مكوّنًا من 50,000 عنصر عند حد أقصى قدره 50 MB لمسار الدفق مقابل محاكاة احتفاظ بعمل جزئي بلغت 4 MB. يَعزو التقرير نحو 50 MB من ذلك إلى دفق المحتوى المتراكم الثابت معماريًا، ويعزل ميزة على جانب الإدخال تتراوح بين 4–5 أضعاف لنموذج الدفق على ذلك المُعطى الاختباري. رُصِدت هذه الأرقام على ذلك العتاد والمُعطى الاختباري وحدهما، وليست مضمونة.
قِس الذاكرة قبل أن تضبط الأداء
قسم بعنوان «قِس الذاكرة قبل أن تضبط الأداء»قِس قبل أن تُغيّر أي شيء. يخضع خط أنابيب HTML لبوابة tools/perf-benchmark.php (تُشغَّل عبر composer ai:perf-check)، التي تُبلِّغ عن peak_memory_delta_bytes — وهو الحد الأقصى التزايدي لكل هدف والمستخدَم محورًا للانحدار، لا الحد الأقصى المطلق للعملية. رصد أساس الدورة 36 (docs/architecture/PERFORMANCE-BUDGETS.md §6.3، الذي التُقِط بتاريخ 2026-05-17 على معالج i9-13900K بذاكرة 64 GB، على PHP 8.5.3 مع تعطيل opcache) فارقًا في الحد الأقصى قدره 0 بايت على 12 من أصل 16 زوجًا من أزواج target/mode. عُزِيت الفوارق الأربعة غير الصفرية إلى تخصيصات ذاكرة مخبأ الخطوط ومخزن التتبّع المؤقت عند الاستخدام الأول، وهي تبقى ثابتة في عمليات العرض اللاحقة. اقرأ هذه القيم على أنها قيم مرصودة لذلك العتاد، لا ثوابت قابلة للنقل. لإجراء قياس عابر لمستندك الخاص، خذ عيّنة من memory_get_peak_usage(true) قبل العرض وبعده، وأعد ضبط الحد الأقصى بـ memory_reset_peak_usage() بين التكرارات، بالطريقة نفسها التي يعزل بها المعيار التكلفة لكل هدف.
تشغيل NextPDF داخل عامل معالجة دفعية
قسم بعنوان «تشغيل NextPDF داخل عامل معالجة دفعية»عامل الطابور هو عملية PHP طويلة العمر: يُقلِع إطار العمل مرة واحدة، ويبقى مقيمًا، ويعالج المهام في حلقة. هذا ما يجعله سريعًا، وهو أيضًا ما يجعل نظافة الذاكرة مهمة. قد يتراكم تسرّب بطيء لا يظهر في طلب واحد عبر آلاف المهام. يذكر PERFORMANCE-BUDGETS §1 هذا النمط من الإخفاق صراحةً: قد يستنفد عامل يعرض ملفات PDF كثيرة على التوالي الذاكرةَ بعد ساعات، حتى عندما تبدو عمليات العرض الفردية سليمة.
يدعم NextPDF بيئات العمال. يتيح DocumentFactory للعامل إنشاء مستند جديد لكل مهمة مع مشاركة FontRegistry وImageRegistry طوال عمر العملية، فيحدث تحليل الخطوط والصور مرة واحدة بدلًا من مرة لكل مهمة. يُسجِّل ADR-001 أن مُحلِّل HTML يُنشَأ لكل طلب دون حالة ثابتة قابلة للتغيير، وأن كائنات سياق التنسيق المستقبلية يجب أن تتّبع نطاق “لكل طلب” نفسه. هيّئ العامل بأمان عبر الخطوات التالية.
الخطوة 1 — مشاركة السجلّات بين المهام
قسم بعنوان «الخطوة 1 — مشاركة السجلّات بين المهام»أنشئ السجلّات مرة واحدة عند إقلاع العملية وأعد استخدامها في كل مهمة، وفقًا لـ examples/14-worker-factory.php:
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Core\PdfFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
// Created once at process boot — not per job.$fontRegistry = new FontRegistry();$imageRegistry = new ImageRegistry(maxCacheBytes: 50 * 1024 * 1024);$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$factory = PdfFactory::new() ->withCompress(true) ->withDocumentFactory($documentFactory);
// Per job: a fresh document, shared registries.$doc = $factory->create();$doc->addPage();$doc->setFont('helvetica', '', 11);$doc->cell(0, 8, 'Rendered inside a worker.', newLine: true);$doc->save('/path/to/output.pdf');يحدّ maxCacheBytes الخاص بسجل الصور من المخبأ المُشترَك، فلا يستطيع النمو بلا حد عبر المهام.
الخطوة 2 — تقييد عمر العامل
قسم بعنوان «الخطوة 2 — تقييد عمر العامل»هذه ممارسة عامة للتحكم في العمليات لأي عامل PHP، وليست ضمانًا من محرّك NextPDF: أعد تشغيل العمال دوريًا حتى لا تتمكّن العملية طويلة العمر من مراكمة الذاكرة أو الاستمرار في تشغيل شِفرة قديمة إلى ما لا نهاية. يوفّر نظاما طوابير PHP الرئيسيان حدودًا مدمجة وعمليات إعادة تشغيل سلسة.
في طوابير Laravel (https://laravel.com/docs/12.x/queues)، يُشغِّل الأمر queue:work العاملَ كعملية طويلة العمر. الخيارات الموثَّقة هي --memory (الافتراضي 128 MB؛ يخرج العامل عندما تتجاوز ذاكرته الحد)، و--max-jobs (الخروج بعد عدد من المهام)، و--max-time (الخروج بعد عدد من الثواني). يُشير الأمر queue:restart إلى العمال بالخروج بسلاسة بعد المهمة الحالية، فيستطيع نشر جديد أو مؤقّت دوري إعادة تدويرهم دون مقاطعة عملية عرض جارية. يُشرِف Laravel Horizon (https://laravel.com/docs/12.x/horizon) على عمال Redis باستراتيجية موازنة auto وأمر سلِس php artisan horizon:terminate، الذي يُنهي المهام الجارية قبل أن يُعيد مراقب العمليات تشغيل المُشرِف.
في Symfony Messenger (https://symfony.com/doc/current/messenger.html)، يعمل الأمر messenger:consume إلى ما لا نهاية افتراضيًا. خيارات الحد الموثَّقة هي --limit (معالجة N رسالة، ثم الخروج)، و--memory-limit (مثلًا 128M؛ الخروج عندما تبلغ الذاكرة الحد)، و--time-limit (مثلًا 3600؛ الخروج بعد الفترة). توصي وثائق Symfony بتشغيل العامل تحت Supervisor أو systemd حتى تُعاد العملية التي خرجت تلقائيًا، ويضبط messenger:stop-workers راية مخبأ تُخبر كل عامل بإنهاء رسالته الحالية والخروج بنظافة.
الخطوة 3 — إعادة التشغيل عند النشر
قسم بعنوان «الخطوة 3 — إعادة التشغيل عند النشر»عند كل نشر، أرسل إشارة بإعادة تشغيل سلسة كي يلتقط العمال الشِفرة الجديدة: php artisan queue:restart (أو php artisan horizon:terminate) لـ Laravel، وphp bin/console messenger:stop-workers لـ Symfony. يبدأ مدير العمليات عندئذ — Supervisor أو systemd أو مُشرِف Horizon/Octane — عملية جديدة من قاعدة الشِفرة الجديدة. هذه ممارسة نشر عامة لعمال PHP طويلي العمر ومستقلة عن NextPDF.
الأداء
قسم بعنوان «الأداء»صُمِّم مسار الدفق ليحدّ من الحد الأقصى للذاكرة عبر إفراغ كل صفحة مكتملة وترحيل سجلات الإحالات المتقاطعة وشجرة الصفحات إلى أدفاق مؤقتة مدعومة بالقرص. ونتيجةً لذلك، يُقصد ألّا تنمو المجموعة المقيمة مع عدد الصفحات. يُرصَد هذا السلوك في محرّك الإصدار 3.1.0 المُسلَّم وتُثبِّته اختبارات قابلية إعادة الإنتاج ذات الأساس الذهبي، لكنه يُذكَر بوصفه سلوكًا تصميميًا لا رقمًا ثابتًا لأن النمط خاصية من الفئة experimental. ذاكرة جانب الإدخال لخط أنابيب HTML محدودة بـ MAX_NESTING_DEPTH = 100، لا بعدد العناصر (ADR-001). جميع الأرقام الملموسة في هذه الصفحة مرتبطة بأثر مؤرَّخ — معيار ADR-001 بتاريخ 2026-04-06 وأساس الدورة 36 لـ PERFORMANCE-BUDGETS بتاريخ 2026-05-17 — ورُصِدت على العتاد الذي تذكره هاتان الوثيقتان؛ فعامِلها على أنها مشاهدات لا ضمانات قابلة للنقل. إن performance_budget البالغ 1500 ms / 64 MB هو غلاف اللوحة، لا حد أقصى تعاقدي.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»يُلحِق writeContent() الخاص بمؤشّر الدفق البايتات بدفق محتوى الصفحة حرفيًا. وهو لا يتحقّق من صحة بناء جملة المعاملات. في عامل يعرض محتوى متأثّرًا بالمُستدعي، لا تمرّر مطلقًا مدخلات غير موثوقة إلى writeContent()؛ استخدم writeText()، الذي يُهرِّبه المؤشّر المُسلَّم وفق قواعد سلاسل PDF الحرفية. يملك المُستدعي دفق المُخرَج: يكتب فيه المحرّك لكنه لا يُغلِقه أو يُعيد فتحه مطلقًا، فلا يمكنه إعادة توجيه المُخرَج. يجب على العامل أن يُغلِق المِقبض بنفسه بعد عودة close() الخاص بالكاتب، وإلا سرّب واصف ملف عبر المهام. مشاركة السجلّات بين المهام تحسين للأداء لا حدّ ثقة: يُخبِّئ ImageRegistry المُشترَك الصورَ المُحلَّلة، لذا حدّد حجم maxCacheBytes الخاص به عمدًا ولا تفترض عزل المخبأ بين المستأجرين في عامل متعدّد المستأجرين.
المطابقة
قسم بعنوان «المطابقة»| الادّعاء | المعيار | البند | الدليل |
|---|---|---|---|
يُصدِر كاتب الدفق شجرة صفحات يكون مُدخَل Kids فيها مصفوفةً من المراجع غير المباشرة إلى الأبناء المباشرين للعُقدة. | ISO 32000-2 | §7.7.3.2 | |
يُصدِر كاتب الدفق مُدخَل Count مساويًا لعدد كائنات الصفحات الورقية المتفرّعة من عُقدة شجرة الصفحات. | ISO 32000-2 | §7.7.3.3 |
البنود مُعاد صياغتها ومُثبَّتة بالمسرد؛ ولا يُعاد إنتاج أي نص معياري هنا.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- العقود / الدفق — عقدا
experimentalStreamingWriterInterfaceوCursorInterface، إضافةً إلى آلتي الحالة الخاصتين بهما. - HTML / قيود الدفق (ADR-001) — قرار التمريرة الواحدة دون الاحتفاظ بشجرة DOM وعتبات إعادة النظر.
- الأداء — بوابة انحدار زمن الاستجابة والذاكرة لخط أنابيب HTML.
- التخطيط — محرّكات تأثيث الصفحة التي لا تحتفظ بأي حالة لكل صفحة.
- PERFORMANCE-BUDGETS — نمط إخفاق العامل المُسرِّب للذاكرة وأساس بوابة الانحدار.