การแก้ไขปัญหา: หน่วยความจำและประสิทธิภาพ
รายการเหล่านี้ครอบคลุมตระกูลความล้มเหลวสองแบบที่คุณพบภายใต้ภาระงาน: PHP หน่วยความจำ หมดระหว่างการเรนเดอร์ และ throughput ที่ตกลงจากจุดวิกฤตเมื่อกระบวนการ warm หรือ อิ่มตัว แต่ละรายการระบุอาการ สาเหตุที่เป็นไปได้มากที่สุด และการแก้ไขที่ใช้พื้นผิว NextPDF จริงหรือตัวควบคุม PHP-FPM มาตรฐาน สำหรับโมเดลการสตรีมที่อยู่เบื้องหลังและ บทเรียน worker ให้อ่าน การสตรีมและหน่วยความจำ หน้านี้เป็น คู่หูฝั่งเหตุการณ์ของมัน
วัดก่อน สุ่มตัวอย่าง memory_get_peak_usage(true) ก่อนและหลังการเรนเดอร์ และเรียก
memory_reset_peak_usage() ระหว่างรอบ ในแบบเดียวกับที่ benchmark ของเอนจินแยก
ต้นทุนต่อการเรนเดอร์ การปรับแต่งโดยไม่มี baseline ย้ายจุดวิกฤตแทนที่จะลบมัน
รายการ: “Allowed memory size exhausted” ระหว่างการสร้าง
หัวข้อที่มีชื่อว่า “รายการ: “Allowed memory size exhausted” ระหว่างการสร้าง”- อาการ การเรนเดอร์ยกเลิกด้วย fatal
Allowed memory size of <n> bytes exhaustedจาก PHP runtime มักเกิดบนเอกสาร ขนาดใหญ่หรือมีภาพเยอะ - สาเหตุที่เป็นไปได้ เส้นทางการเขียนเริ่มต้นประกอบทั้งเอกสาร แล้ว serialize
มัน ดังนั้นหน่วยความจำสูงสุดติดตามขนาดเอาต์พุตรวม เอกสารขนาดใหญ่ ภาพที่ฝังขนาดใหญ่
หรือหน้าฟอนต์ที่ฝังขนาดใหญ่สามารถผลักคำขอให้เกิน
memory_limit - การแก้ไข
- จำกัด image cache
NextPDF\Core\ConfigเปิดเผยimageCacheBytes(ค่าเริ่มต้น52428800คือ 50 MB) ลดมันด้วย wither ของ instance$config->withImageCacheBytes($bytes)(signaturewithImageCacheBytes(int $bytes): self) เพื่อให้ build ที่ฝังภาพจำนวนมาก ล้มเหลวเร็วบนเพดานที่ทราบแทนที่จะ swap สิ่งนี้จำกัด image cache ในหน่วยความจำ มันไม่ resample หรือ re-encode ภาพเอง - ย่ออินพุตก่อนการฝัง Core ไม่ downscale หรือ re-encode ภาพ ปรับขนาดและ re-encode งานศิลป์ raster ที่ใหญ่เกิน ก่อน ที่คุณจะฝังมัน และฝังฟอนต์ที่ คุณใช้จริงเพื่อให้การ subset มีชุด glyph ขนาดเล็กที่จะเก็บ (ดู ลดขนาดไฟล์ PDF)
- เปิดการบีบอัดไว้
Configที่สดใหม่มีcompressตั้งเป็นtrueปล่อยมันไว้สำหรับ build ปกติwithCompress(false)ไม่ใช่การ optimize ขนาด (มันมักเพิ่มเอาต์พุต) ใช้มันเพื่อ debug หรือ profile ไปป์ไลน์ — มันเลื่อน tradeoff ของ CPU/หน่วยความจำ (ข้ามขั้นตอนการบีบอัด) แทนที่จะลดหน่วยความจำ - เพิ่ม
memory_limitอย่างจงใจ ต่อ worker นี่เป็นการตั้งค่า PHP มาตรฐาน ไม่ใช่คีย์ NextPDF ตั้งมันใน pool config หรือด้วยini_set('memory_limit', '256M')สำหรับกระบวนการ CLI/queue และกำหนดขนาดมัน เทียบกับจุดสูงสุดที่ profile แล้ว ไม่ใช่การเดา
- จำกัด image cache
- เกี่ยวข้อง การสตรีมและหน่วยความจำ
รายการ: หน่วยความจำเติบโตตามจำนวนหน้าบนเอกสารขนาดใหญ่มาก
หัวข้อที่มีชื่อว่า “รายการ: หน่วยความจำเติบโตตามจำนวนหน้าบนเอกสารขนาดใหญ่มาก”- อาการ เอกสารหลายพันหน้าหน่วยความจำหมดแม้ว่าแต่ละหน้าจะเล็ก และจุดสูงสุด เพิ่มขึ้นโดยประมาณตามจำนวนหน้า
- สาเหตุที่เป็นไปได้ ตัวเขียนแบบ buffered ถือทั้งเอกสารที่ serialize แล้วใน heap สำหรับเอกสารขนาดใหญ่มากนั่นคือต้นทุนหลัก
- การแก้ไข
- เลือกเส้นทางการเขียนแบบสตรีม ใช้เส้นทางการเขียนแบบสตรีมที่บันทึกไว้ซึ่ง
อธิบายใน การสตรีมและหน่วยความจำ:
มัน serialize แต่ละหน้าขณะที่ประกอบและปล่อย buffer ซึ่งลดการเติบโตของ
page-buffer/เอาต์พุต metadata ขนาดเล็กต่อวัตถุ (offset, page tree) ยังคง
scale ตามจำนวนหน้า/วัตถุได้ ทำตามจุดเข้าที่บันทึกไว้แทนที่จะคัดลอกคลาสภายใน
— เอนจินสตรีมที่อยู่เบื้องหลังเป็นระดับ
experimentalและสัญลักษณ์ของมัน ไม่ใช่พื้นผิวสาธารณะที่เสถียร - สำหรับ parser
writeHtml()แบบเนทีฟ จำไว้ว่าหน่วยความจำฝั่งอินพุตถูก จำกัดด้วยทั้งตัวป้องกัน nesting-depth และ element-count: ADR-001 จำกัด nesting ที่MAX_NESTING_DEPTH = 100และปฏิเสธเอกสารที่เกินMAX_ELEMENT_COUNT = 50000เอกสารที่ชน element cap จะถูกบอกอย่างชัดเจน แทนที่จะหน่วยความจำหมดเงียบๆ cap ADR-001 เหล่านี้ควบคุมเฉพาะ parser เนทีฟ Chrome bridge ที่เป็นทางเลือก (writeHtmlChrome()) เรนเดอร์นอกกระบวนการและ มีขีดจำกัดหน่วยความจำ/อินพุตแยกต่างหากของตัวเอง ไม่ใช่ cap เหล่านี้
- เลือกเส้นทางการเขียนแบบสตรีม ใช้เส้นทางการเขียนแบบสตรีมที่บันทึกไว้ซึ่ง
อธิบายใน การสตรีมและหน่วยความจำ:
มัน serialize แต่ละหน้าขณะที่ประกอบและปล่อย buffer ซึ่งลดการเติบโตของ
page-buffer/เอาต์พุต metadata ขนาดเล็กต่อวัตถุ (offset, page tree) ยังคง
scale ตามจำนวนหน้า/วัตถุได้ ทำตามจุดเข้าที่บันทึกไว้แทนที่จะคัดลอกคลาสภายใน
— เอนจินสตรีมที่อยู่เบื้องหลังเป็นระดับ
- เกี่ยวข้อง การสตรีมและหน่วยความจำ
รายการ: worker ที่มีอายุยาวหน่วยความจำหมดหลังหลายงาน
หัวข้อที่มีชื่อว่า “รายการ: worker ที่มีอายุยาวหน่วยความจำหมดหลังหลายงาน”- อาการ การเรนเดอร์เดี่ยวสำเร็จ แต่ queue worker ที่เรนเดอร์ PDF จำนวนมาก ติดต่อกันหน่วยความจำหมดหลังนาทีหรือชั่วโมง
- สาเหตุที่เป็นไปได้ กระบวนการ PHP ที่มีอายุยาวสะสมการจัดสรรข้ามงาน การเติบโต ที่ช้าซึ่งมองไม่เห็นในคำขอเดียวทบต้นข้ามหลายพันครั้ง
- การแก้ไข
- แชร์ registry สร้างเอกสารใหม่ สร้าง
FontRegistryและImageRegistryหนึ่งครั้ง ณ เวลา boot และส่งมันให้DocumentFactoryสร้างDocumentใหม่ ต่อหนึ่งงานด้วย$factory->create($config)การแยกวิเคราะห์ฟอนต์และภาพจึง เกิดหนึ่งครั้งสำหรับกระบวนการ ไม่ใช่หนึ่งครั้งต่อหนึ่งงาน และแผนผังเอกสาร ต่องานถูกเก็บคืนเมื่อมันออกนอกขอบเขต ทำตามexamples/14-worker-factory.php - จำกัด image cache ที่แชร์ด้วย
new ImageRegistry(maxCacheBytes: ...)เพื่อไม่ให้มันเติบโตอย่างไม่มีขีดจำกัดข้ามงาน - รีไซเคิล worker — การควบคุมกระบวนการ ไม่ใช่การรับประกันของเอนจิน ใน
PHP-FPM ตั้ง
pm.max_requestsเพื่อให้แต่ละ child respawn หลังจากจำนวนคำขอ ที่คงที่ ใน Laravel queue ใช้queue:work --max-jobs/--max-time/--memoryใน Symfony Messenger ใช้messenger:consume --limit/--time-limit/--memory-limit
- แชร์ registry สร้างเอกสารใหม่ สร้าง
- เกี่ยวข้อง การสตรีมและหน่วยความจำ
รายการ: จุดวิกฤต throughput บนกระบวนการที่ cold หรือ warm ไม่พอ
หัวข้อที่มีชื่อว่า “รายการ: จุดวิกฤต throughput บนกระบวนการที่ cold หรือ warm ไม่พอ”- อาการ การเรนเดอร์ครั้งแรกในกระบวนการที่สดใหม่ช้า หรือทุกคำขอจ่ายต้นทุน parse ที่คำขอ warm ไม่ควรจ่าย
- สาเหตุที่เป็นไปได้ ต้นทุน cold-start สองอย่างทับซ้อนกัน PHP ที่ไม่มี opcache
recompile ทุกไฟล์ในแต่ละคำขอ และ
FontRegistryที่ไม่ได้ warm แยกวิเคราะห์ แต่ละหน้าฟอนต์ครั้งแรกที่ใช้ - การแก้ไข
- เปิดใช้งาน opcache (และ JIT ที่ใดช่วยได้) ตั้ง
opcache.enable=1และopcache.memory_consumptionที่เผื่อไว้มาก ในการใช้งานจริงตั้งopcache.validate_timestamps=0เพื่อไม่ให้ cache ถูกตรวจสอบซ้ำต่อคำขอ การตั้งค่านั้นต้องการกระบวนการ deploy ที่ restart หรือ reload PHP-FPM (หรือรีเซ็ต opcache ด้วยวิธีอื่น เช่นopcache_reset()/cachetool) ในทุกการ release — มิฉะนั้น opcache ยังคงให้บริการ bytecode เก่าและโค้ดที่ stale รันหลังการ deploy เหล่านี้เป็นการตั้งค่า ini ของ PHP มาตรฐาน ไม่ใช่ คีย์ NextPDF - Warm และ lock font registry ณ เวลา boot บน instance
FontRegistry$fontRegistry->warmup($fontFiles)แยกวิเคราะห์หน้าฟอนต์หนึ่งครั้งระหว่าง boot และ$fontRegistry->lock()แช่แข็ง registry เพื่อไม่ให้โค้ดเวลาคำขอ กลายพันธุ์สถานะที่แชร์$fontRegistry->isLocked()รายงานสถานะ ใน worker หรือ application server ที่มีอายุยาวจริง — queue consumer หรือ RoadRunner/Swoole/Octane worker ที่รักษากระบวนการ PHP เดียวกันให้มีชีวิต ข้ามหลายคำขอ — registry ที่ warm และ lock แล้วคงหน้าฟอนต์ที่แยกวิเคราะห์ของ มันในสถานะวัตถุ เปลี่ยนการแยกวิเคราะห์ฟอนต์ต่อคำขอเป็นต้นทุน boot กระบวนการ ครั้งเดียว ภายใต้โมเดลคำขอ PHP-FPM มาตรฐาน สถานะวัตถุที่ warm นั้น ไม่ อยู่รอดข้ามคำขอ: opcache แคชคลาสและ bytecode ที่คอมไพล์แล้ว ไม่ใช่สถานะ วัตถุ userland ที่ warm ดังนั้นFontRegistryที่ warm จึงถูกสร้างใหม่ ต่อคำขอ (รันใหม่ในแต่ละคำขอจาก bootstrap ของ child) ไม่ได้ถือ warm ข้ามคำขอภายใน child บน PHP-FPM ธรรมดา opcache ส่วนใหญ่ amortize ต้นทุน การ recompile bytecode ยอมรับว่าการแยกวิเคราะห์ฟอนต์ถูกจ่าย ต่อคำขอ ไม่ใช่ถูกกำจัด การ amortize ข้ามคำขอ — การแยกวิเคราะห์แต่ละหน้าฟอนต์หนึ่ง ครั้งตลอดอายุของกระบวนการ — ใช้ได้เฉพาะในกระบวนการที่มีอายุยาวจริงเช่น RoadRunner/Swoole/Octane worker หรือ queue consumer ที่รักษากระบวนการ PHP เดียวกันให้มีชีวิตข้ามหลายคำขอ - อย่าแยกวิเคราะห์ template เดียวกันซ้ำต่อคำขอ แก้ฟอนต์และทรัพยากรที่
ใช้ซ้ำได้หนึ่งครั้ง ณ เวลา boot ผ่าน registry ที่แชร์ มีเพียง
Documentต่องานเท่านั้นที่ควรถูกสร้างในคำขอ
- เปิดใช้งาน opcache (และ JIT ที่ใดช่วยได้) ตั้ง
- เกี่ยวข้อง การสตรีมและหน่วยความจำ
รายการ: เซิร์ฟเวอร์อิ่มตัวและ latency พุ่งภายใต้ concurrency
หัวข้อที่มีชื่อว่า “รายการ: เซิร์ฟเวอร์อิ่มตัวและ latency พุ่งภายใต้ concurrency”- อาการ latency ต่อการเรนเดอร์ดีเมื่ออยู่โดดเดี่ยว แต่ภายใต้ภาระงาน เครื่อง swap, CPU อิ่มตัว หรือคำขอเข้าคิวและ time out
- สาเหตุที่เป็นไปได้ PHP-FPM worker มากเกินไปสำหรับ RAM ที่มี ดังนั้นผลรวมของ จุดสูงสุดของ worker เกินหน่วยความจำกายภาพและโฮสต์ swap หรือ worker น้อยเกินไป ดังนั้นคำขอ serialize หลัง pool ขนาดเล็ก
- การแก้ไข
-
กำหนดขนาด
pm.max_childrenจากจุดสูงสุดที่ profile แล้ว ใช้สูตรมาตรฐานpm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryวัดจุดสูงสุดจริงของ worker ด้วยเอกสารตัวแทน (ดูหมายเหตุการ profile ใน ขอบเขต) เผื่อ headroom สำหรับ OS และบริการที่อยู่ร่วมใดๆ และหาร เผื่อ margin ไว้ อย่ากำหนดขนาดเป็น 100% ของ RAM
-
ตรึงต้นทุนการบีบอัดในงบประมาณของคุณ การบีบอัด Flate สามารถเป็นต้นทุน CPU ที่สำคัญของการเขียน stream และ scale ตามปริมาณไบต์ stream ที่บีบอัดได้ ดังนั้นจำนวนหน้าและปริมาณฟอนต์ที่ฝังมีอิทธิพลต่อ CPU ต่อการเรนเดอร์ การ ประมวลผลภาพ การ subset ฟอนต์ และการแยกวิเคราะห์อินพุตก็สามารถครองงานได้ วัดด้วยเอกสารตัวแทน และคำนึงถึงตัวขับเคลื่อนจริงเมื่อคุณเลือกจำนวน worker และ CPU
-
ตั้ง
pm.max_requestsควบคู่กับpm.max_childrenเพื่อให้ child รีไซเคิล และเรียกคืนการเติบโตที่ช้าใดๆ เหมือนในรายการ worker ด้านบน
-
- เกี่ยวข้อง การสตรีมและหน่วยความจำ
รายการ: อินพุตที่ไม่น่าเชื่อถือขนาดใหญ่ช้าหรือแพงต่อการแยกวิเคราะห์
หัวข้อที่มีชื่อว่า “รายการ: อินพุตที่ไม่น่าเชื่อถือขนาดใหญ่ช้าหรือแพงต่อการแยกวิเคราะห์”- อาการ การเรนเดอร์ช้าหรือหน่วยความจำหนักบนอินพุตที่ใหญ่หรือซ้อนกันลึก โดยเฉพาะ HTML หรือฟอนต์ที่คุณไม่ได้ผลิต
- สาเหตุที่เป็นไปได้ ต้นทุนการแยกวิเคราะห์ scale ตามขนาดและโครงสร้างอินพุต อินพุตที่ผิดปกติ (การซ้อนลึก จำนวน element มหาศาล หรือฟอนต์ที่ผิดรูปแบบ) สามารถ ครองงบประมาณได้
- การแก้ไข
- พึ่งพาขอบเขตของเอนจิน parser HTML
writeHtml()แบบเนทีฟบังคับใช้MAX_NESTING_DEPTH = 100และMAX_ELEMENT_COUNT = 50000(ADR-001) อินพุตที่เกิน cap เหล่านั้นถูกปฏิเสธแทนที่จะถูกปล่อยให้หน่วยความจำของ กระบวนการหมด (Chrome bridge ที่เป็นทางเลือกwriteHtmlChrome()อยู่นอก ขอบเขตของ cap ADR-001 เหล่านี้และบังคับใช้ขีดจำกัดหน่วยความจำ/อินพุตแยก ต่างหากของตัวเอง) - จัดการฟอนต์ที่ผู้เรียกจัดหาเป็นสิ่งที่ไม่น่าเชื่อถือ ฟอนต์ที่ผิดรูปแบบ raise
NextPDF\Exception\FontParsingExceptionแทนที่จะทำให้เอาต์พุตเสียหาย ดังนั้นจับข้อยกเว้นที่ระบุและปฏิเสธอินพุตแทนที่จะลองใหม่ - ตรวจสอบและกำหนดขนาดอินพุตที่ขอบเขตของคุณ และใช้ขีดจำกัดระดับคำขอบนขนาด เอกสารสำหรับเนื้อหาที่ผู้เรียกมีอิทธิพล
- พึ่งพาขอบเขตของเอนจิน parser HTML
- เกี่ยวข้อง การแก้ไขปัญหา: ฟอนต์และการแท็ก
ตารางการตัดสินใจ: อาการสู่ตัวควบคุม
หัวข้อที่มีชื่อว่า “ตารางการตัดสินใจ: อาการสู่ตัวควบคุม”| อาการ | ตัวควบคุมที่เป็นไปได้มากที่สุด |
|---|---|
Allowed memory size … exhausted บนการเรนเดอร์เดี่ยว | ลด $config->withImageCacheBytes(); ย่อภาพก่อนการฝัง; เพิ่ม memory_limit ต่อ worker |
| หน่วยความจำสูงสุดเพิ่มตามจำนวนหน้า | ใช้ เส้นทางการเขียนแบบสตรีมที่บันทึกไว้ |
| หน่วยความจำของ worker ไต่ขึ้นข้ามหลายงาน | แชร์ FontRegistry/ImageRegistry ผ่าน DocumentFactory; ตั้ง pm.max_requests / --max-jobs |
| คำขอแรกช้า ต้นทุน parse ต่อคำขอ | เปิดใช้งาน opcache; $fontRegistry->warmup() แล้ว ->lock() ณ เวลา boot |
| โฮสต์ swap / latency พุ่งภายใต้ภาระงาน | กำหนดขนาด pm.max_children = (RAM − overhead) / per-worker peak |
| ช้าหรือหนักบนอินพุตที่ใหญ่/ไม่น่าเชื่อถือ | พึ่งพา cap ADR-001; ปฏิเสธฟอนต์ที่ผิดรูปแบบบน FontParsingException |
กรณีขอบและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและข้อควรระวัง”imageCacheBytesเป็น เพดานหน่วยความจำ ไม่ใช่ปุ่มปรับขนาด การลดมันจำกัด cache เพื่อให้ build ล้มเหลวเร็ว มันไม่เคย resample หรือ re-encode ภาพที่คุณฝัง Core ไม่มีการควบคุมคุณภาพภาพwithCompress(false)ทำให้ไฟล์ ใหญ่ขึ้น และเป็นเครื่องช่วย debug/profile มันไม่ใช่การ optimize ขนาด มันเลื่อน tradeoff ของ CPU/หน่วยความจำ (มันข้าม ขั้นตอนการบีบอัด) แทนที่จะลดหน่วยความจำ- profile หน่วยความจำที่แน่นอนของเอนจินสตรีมเป็น property ระดับ
experimentalและอาจเลื่อนระหว่างการ release แบบ minor จัดการการวัดเดี่ยวใดๆ เป็นการสังเกต ไม่ใช่ค่าคงที่ที่พกพาได้ memory_limit,opcache.*,pm.max_childrenและpm.max_requestsเป็น การตั้งค่า PHP / PHP-FPM มาตรฐาน NextPDF ไม่เปิดเผยคีย์ของตัวเองสำหรับมัน กำหนดค่ามันใน runtime ของคุณ ไม่ใช่ในConfig
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”- การสตรีมและหน่วยความจำ — โมเดลการสตรีม ขอบเขต ADR-001 และบทเรียน batch-worker เต็ม
- ลดขนาดไฟล์ PDF — การบีบอัด และการ subset ฟอนต์ การควบคุมขนาดจริงสองอย่าง
- การแก้ไขปัญหา: ฟอนต์และการแท็ก — การแก้ฟอนต์ การแยกวิเคราะห์ และความล้มเหลวในการ subset
- ดัชนีฐานความรู้
Glossary: streaming writer · font subsetting