ข้ามไปยังเนื้อหา
getnextpdf.com

การแก้ไขปัญหา: หน่วยความจำและประสิทธิภาพ

รายการเหล่านี้ครอบคลุมตระกูลความล้มเหลวสองแบบที่คุณพบภายใต้ภาระงาน: PHP หน่วยความจำ หมดระหว่างการเรนเดอร์ และ throughput ที่ตกลงจากจุดวิกฤตเมื่อกระบวนการ warm หรือ อิ่มตัว แต่ละรายการระบุอาการ สาเหตุที่เป็นไปได้มากที่สุด และการแก้ไขที่ใช้พื้นผิว NextPDF จริงหรือตัวควบคุม PHP-FPM มาตรฐาน สำหรับโมเดลการสตรีมที่อยู่เบื้องหลังและ บทเรียน worker ให้อ่าน การสตรีมและหน่วยความจำ หน้านี้เป็น คู่หูฝั่งเหตุการณ์ของมัน

วัดก่อน สุ่มตัวอย่าง memory_get_peak_usage(true) ก่อนและหลังการเรนเดอร์ และเรียก memory_reset_peak_usage() ระหว่างรอบ ในแบบเดียวกับที่ benchmark ของเอนจินแยก ต้นทุนต่อการเรนเดอร์ การปรับแต่งโดยไม่มี baseline ย้ายจุดวิกฤตแทนที่จะลบมัน

  • อาการ การเรนเดอร์ยกเลิกด้วย fatal Allowed memory size of <n> bytes exhausted จาก PHP runtime มักเกิดบนเอกสาร ขนาดใหญ่หรือมีภาพเยอะ
  • สาเหตุที่เป็นไปได้ เส้นทางการเขียนเริ่มต้นประกอบทั้งเอกสาร แล้ว serialize มัน ดังนั้นหน่วยความจำสูงสุดติดตามขนาดเอาต์พุตรวม เอกสารขนาดใหญ่ ภาพที่ฝังขนาดใหญ่ หรือหน้าฟอนต์ที่ฝังขนาดใหญ่สามารถผลักคำขอให้เกิน memory_limit
  • การแก้ไข
    1. จำกัด image cache NextPDF\Core\Config เปิดเผย imageCacheBytes (ค่าเริ่มต้น 52428800 คือ 50 MB) ลดมันด้วย wither ของ instance $config->withImageCacheBytes($bytes) (signature withImageCacheBytes(int $bytes): self) เพื่อให้ build ที่ฝังภาพจำนวนมาก ล้มเหลวเร็วบนเพดานที่ทราบแทนที่จะ swap สิ่งนี้จำกัด image cache ในหน่วยความจำ มันไม่ resample หรือ re-encode ภาพเอง
    2. ย่ออินพุตก่อนการฝัง Core ไม่ downscale หรือ re-encode ภาพ ปรับขนาดและ re-encode งานศิลป์ raster ที่ใหญ่เกิน ก่อน ที่คุณจะฝังมัน และฝังฟอนต์ที่ คุณใช้จริงเพื่อให้การ subset มีชุด glyph ขนาดเล็กที่จะเก็บ (ดู ลดขนาดไฟล์ PDF)
    3. เปิดการบีบอัดไว้ Config ที่สดใหม่มี compress ตั้งเป็น true ปล่อยมันไว้สำหรับ build ปกติ withCompress(false) ไม่ใช่การ optimize ขนาด (มันมักเพิ่มเอาต์พุต) ใช้มันเพื่อ debug หรือ profile ไปป์ไลน์ — มันเลื่อน tradeoff ของ CPU/หน่วยความจำ (ข้ามขั้นตอนการบีบอัด) แทนที่จะลดหน่วยความจำ
    4. เพิ่ม memory_limit อย่างจงใจ ต่อ worker นี่เป็นการตั้งค่า PHP มาตรฐาน ไม่ใช่คีย์ NextPDF ตั้งมันใน pool config หรือด้วย ini_set('memory_limit', '256M') สำหรับกระบวนการ CLI/queue และกำหนดขนาดมัน เทียบกับจุดสูงสุดที่ profile แล้ว ไม่ใช่การเดา
  • เกี่ยวข้อง การสตรีมและหน่วยความจำ

รายการ: หน่วยความจำเติบโตตามจำนวนหน้าบนเอกสารขนาดใหญ่มาก

หัวข้อที่มีชื่อว่า “รายการ: หน่วยความจำเติบโตตามจำนวนหน้าบนเอกสารขนาดใหญ่มาก”
  • อาการ เอกสารหลายพันหน้าหน่วยความจำหมดแม้ว่าแต่ละหน้าจะเล็ก และจุดสูงสุด เพิ่มขึ้นโดยประมาณตามจำนวนหน้า
  • สาเหตุที่เป็นไปได้ ตัวเขียนแบบ buffered ถือทั้งเอกสารที่ serialize แล้วใน heap สำหรับเอกสารขนาดใหญ่มากนั่นคือต้นทุนหลัก
  • การแก้ไข
    1. เลือกเส้นทางการเขียนแบบสตรีม ใช้เส้นทางการเขียนแบบสตรีมที่บันทึกไว้ซึ่ง อธิบายใน การสตรีมและหน่วยความจำ: มัน serialize แต่ละหน้าขณะที่ประกอบและปล่อย buffer ซึ่งลดการเติบโตของ page-buffer/เอาต์พุต metadata ขนาดเล็กต่อวัตถุ (offset, page tree) ยังคง scale ตามจำนวนหน้า/วัตถุได้ ทำตามจุดเข้าที่บันทึกไว้แทนที่จะคัดลอกคลาสภายใน — เอนจินสตรีมที่อยู่เบื้องหลังเป็นระดับ experimental และสัญลักษณ์ของมัน ไม่ใช่พื้นผิวสาธารณะที่เสถียร
    2. สำหรับ 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 เหล่านี้
  • เกี่ยวข้อง การสตรีมและหน่วยความจำ

รายการ: worker ที่มีอายุยาวหน่วยความจำหมดหลังหลายงาน

หัวข้อที่มีชื่อว่า “รายการ: worker ที่มีอายุยาวหน่วยความจำหมดหลังหลายงาน”
  • อาการ การเรนเดอร์เดี่ยวสำเร็จ แต่ queue worker ที่เรนเดอร์ PDF จำนวนมาก ติดต่อกันหน่วยความจำหมดหลังนาทีหรือชั่วโมง
  • สาเหตุที่เป็นไปได้ กระบวนการ PHP ที่มีอายุยาวสะสมการจัดสรรข้ามงาน การเติบโต ที่ช้าซึ่งมองไม่เห็นในคำขอเดียวทบต้นข้ามหลายพันครั้ง
  • การแก้ไข
    1. แชร์ registry สร้างเอกสารใหม่ สร้าง FontRegistry และ ImageRegistry หนึ่งครั้ง ณ เวลา boot และส่งมันให้ DocumentFactory สร้าง Document ใหม่ ต่อหนึ่งงานด้วย $factory->create($config) การแยกวิเคราะห์ฟอนต์และภาพจึง เกิดหนึ่งครั้งสำหรับกระบวนการ ไม่ใช่หนึ่งครั้งต่อหนึ่งงาน และแผนผังเอกสาร ต่องานถูกเก็บคืนเมื่อมันออกนอกขอบเขต ทำตาม examples/14-worker-factory.php
    2. จำกัด image cache ที่แชร์ด้วย new ImageRegistry(maxCacheBytes: ...) เพื่อไม่ให้มันเติบโตอย่างไม่มีขีดจำกัดข้ามงาน
    3. รีไซเคิล 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
  • เกี่ยวข้อง การสตรีมและหน่วยความจำ

รายการ: จุดวิกฤต throughput บนกระบวนการที่ cold หรือ warm ไม่พอ

หัวข้อที่มีชื่อว่า “รายการ: จุดวิกฤต throughput บนกระบวนการที่ cold หรือ warm ไม่พอ”
  • อาการ การเรนเดอร์ครั้งแรกในกระบวนการที่สดใหม่ช้า หรือทุกคำขอจ่ายต้นทุน parse ที่คำขอ warm ไม่ควรจ่าย
  • สาเหตุที่เป็นไปได้ ต้นทุน cold-start สองอย่างทับซ้อนกัน PHP ที่ไม่มี opcache recompile ทุกไฟล์ในแต่ละคำขอ และ FontRegistry ที่ไม่ได้ warm แยกวิเคราะห์ แต่ละหน้าฟอนต์ครั้งแรกที่ใช้
  • การแก้ไข
    1. เปิดใช้งาน 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
    2. 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 เดียวกันให้มีชีวิตข้ามหลายคำขอ
    3. อย่าแยกวิเคราะห์ template เดียวกันซ้ำต่อคำขอ แก้ฟอนต์และทรัพยากรที่ ใช้ซ้ำได้หนึ่งครั้ง ณ เวลา boot ผ่าน registry ที่แชร์ มีเพียง Document ต่องานเท่านั้นที่ควรถูกสร้างในคำขอ
  • เกี่ยวข้อง การสตรีมและหน่วยความจำ

รายการ: เซิร์ฟเวอร์อิ่มตัวและ latency พุ่งภายใต้ concurrency

หัวข้อที่มีชื่อว่า “รายการ: เซิร์ฟเวอร์อิ่มตัวและ latency พุ่งภายใต้ concurrency”
  • อาการ latency ต่อการเรนเดอร์ดีเมื่ออยู่โดดเดี่ยว แต่ภายใต้ภาระงาน เครื่อง swap, CPU อิ่มตัว หรือคำขอเข้าคิวและ time out
  • สาเหตุที่เป็นไปได้ PHP-FPM worker มากเกินไปสำหรับ RAM ที่มี ดังนั้นผลรวมของ จุดสูงสุดของ worker เกินหน่วยความจำกายภาพและโฮสต์ swap หรือ worker น้อยเกินไป ดังนั้นคำขอ serialize หลัง pool ขนาดเล็ก
  • การแก้ไข
    1. กำหนดขนาด pm.max_children จากจุดสูงสุดที่ profile แล้ว ใช้สูตรมาตรฐาน

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

      วัดจุดสูงสุดจริงของ worker ด้วยเอกสารตัวแทน (ดูหมายเหตุการ profile ใน ขอบเขต) เผื่อ headroom สำหรับ OS และบริการที่อยู่ร่วมใดๆ และหาร เผื่อ margin ไว้ อย่ากำหนดขนาดเป็น 100% ของ RAM

    2. ตรึงต้นทุนการบีบอัดในงบประมาณของคุณ การบีบอัด Flate สามารถเป็นต้นทุน CPU ที่สำคัญของการเขียน stream และ scale ตามปริมาณไบต์ stream ที่บีบอัดได้ ดังนั้นจำนวนหน้าและปริมาณฟอนต์ที่ฝังมีอิทธิพลต่อ CPU ต่อการเรนเดอร์ การ ประมวลผลภาพ การ subset ฟอนต์ และการแยกวิเคราะห์อินพุตก็สามารถครองงานได้ วัดด้วยเอกสารตัวแทน และคำนึงถึงตัวขับเคลื่อนจริงเมื่อคุณเลือกจำนวน worker และ CPU

    3. ตั้ง pm.max_requests ควบคู่กับ pm.max_children เพื่อให้ child รีไซเคิล และเรียกคืนการเติบโตที่ช้าใดๆ เหมือนในรายการ worker ด้านบน

  • เกี่ยวข้อง การสตรีมและหน่วยความจำ

รายการ: อินพุตที่ไม่น่าเชื่อถือขนาดใหญ่ช้าหรือแพงต่อการแยกวิเคราะห์

หัวข้อที่มีชื่อว่า “รายการ: อินพุตที่ไม่น่าเชื่อถือขนาดใหญ่ช้าหรือแพงต่อการแยกวิเคราะห์”
  • อาการ การเรนเดอร์ช้าหรือหน่วยความจำหนักบนอินพุตที่ใหญ่หรือซ้อนกันลึก โดยเฉพาะ HTML หรือฟอนต์ที่คุณไม่ได้ผลิต
  • สาเหตุที่เป็นไปได้ ต้นทุนการแยกวิเคราะห์ scale ตามขนาดและโครงสร้างอินพุต อินพุตที่ผิดปกติ (การซ้อนลึก จำนวน element มหาศาล หรือฟอนต์ที่ผิดรูปแบบ) สามารถ ครองงบประมาณได้
  • การแก้ไข
    1. พึ่งพาขอบเขตของเอนจิน parser HTML writeHtml() แบบเนทีฟบังคับใช้ MAX_NESTING_DEPTH = 100 และ MAX_ELEMENT_COUNT = 50000 (ADR-001) อินพุตที่เกิน cap เหล่านั้นถูกปฏิเสธแทนที่จะถูกปล่อยให้หน่วยความจำของ กระบวนการหมด (Chrome bridge ที่เป็นทางเลือก writeHtmlChrome() อยู่นอก ขอบเขตของ cap ADR-001 เหล่านี้และบังคับใช้ขีดจำกัดหน่วยความจำ/อินพุตแยก ต่างหากของตัวเอง)
    2. จัดการฟอนต์ที่ผู้เรียกจัดหาเป็นสิ่งที่ไม่น่าเชื่อถือ ฟอนต์ที่ผิดรูปแบบ raise NextPDF\Exception\FontParsingException แทนที่จะทำให้เอาต์พุตเสียหาย ดังนั้นจับข้อยกเว้นที่ระบุและปฏิเสธอินพุตแทนที่จะลองใหม่
    3. ตรวจสอบและกำหนดขนาดอินพุตที่ขอบเขตของคุณ และใช้ขีดจำกัดระดับคำขอบนขนาด เอกสารสำหรับเนื้อหาที่ผู้เรียกมีอิทธิพล
  • เกี่ยวข้อง การแก้ไขปัญหา: ฟอนต์และการแท็ก
อาการตัวควบคุมที่เป็นไปได้มากที่สุด
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

Glossary: streaming writer · font subsetting