Khắc phục sự cố: bộ nhớ và hiệu năng
Phạm vi
Phần tiêu đề “Phạm vi”Các mục này bao quát hai họ thất bại bạn gặp dưới tải: PHP cạn bộ nhớ trong một lần kết xuất, và thông lượng rơi khỏi một vách một khi một tiến trình ấm hoặc bão hòa. Mỗi mục đặt tên một triệu chứng, nguyên nhân khả dĩ nhất, và một cách khắc phục dùng bề mặt NextPDF thực hoặc các điều khiển PHP-FPM chuẩn. Để hiểu mô hình ghi luồng nền tảng và một hướng dẫn worker, hãy đọc Ghi luồng và bộ nhớ; trang này là người bạn đồng hành ở phía sự cố của nó.
Hãy đo trước. Lấy mẫu memory_get_peak_usage(true) trước và sau một lần kết xuất
và gọi memory_reset_peak_usage() giữa các lần lặp, theo cách benchmark của
engine cô lập chi phí từng-lần-kết-xuất. Tinh chỉnh mà không có một mốc sẽ di
chuyển vách thay vì loại bỏ nó.
Mục: “Allowed memory size exhausted” trong khi tạo
Phần tiêu đề “Mục: “Allowed memory size exhausted” trong khi tạo”- Triệu chứng. Một lần kết xuất ngắt với một fatal
Allowed memory size of <n> bytes exhaustedtừ runtime PHP, thường trên một tài liệu lớn hoặc nhiều ảnh. - Nguyên nhân khả dĩ. Đường ghi mặc định soạn toàn bộ tài liệu, rồi tuần tự
hóa nó, nên bộ nhớ đỉnh theo dõi tổng kích thước đầu ra. Một tài liệu lớn, các
ảnh nhúng lớn, hoặc một mặt phông chữ nhúng lớn có thể đẩy yêu cầu vượt qua
memory_limit. - Giải pháp.
- Giới hạn bộ đệm ảnh.
NextPDF\Core\Configphơi bàyimageCacheBytes(mặc định52428800, tức 50 MB). Hãy hạ nó bằng wither theo thực thể$config->withImageCacheBytes($bytes)(chữ kýwithImageCacheBytes(int $bytes): self) để một bản dựng nhúng nhiều ảnh thất bại nhanh trên một trần đã biết thay vì swap. Điều này chặn bộ đệm ảnh trong bộ nhớ; nó không tái lấy mẫu hay tái mã hóa chính các ảnh. - Thu nhỏ đầu vào trước khi nhúng. Core không hạ tỷ lệ hay tái mã hóa ảnh. Hãy đổi cỡ và tái mã hóa tác phẩm raster quá khổ trước khi bạn nhúng nó, và nhúng các phông chữ bạn thực sự dùng để việc subset hóa có một tập glyph nhỏ để giữ (xem Giảm kích thước tệp PDF).
- Giữ nén bật. Một
Configmới cócompressđặt thànhtrue. Hãy để nó bật cho các bản dựng thông thường;withCompress(false)không phải một tối ưu kích thước (nó thường làm tăng đầu ra). Hãy dùng đến nó để gỡ lỗi hoặc soát hồ sơ pipeline — nó dịch chuyển đánh đổi CPU/bộ nhớ (bỏ qua bước nén) thay vì giảm bộ nhớ. - Nâng
memory_limitmột cách có chủ đích, theo từng worker. Đây là một thiết lập PHP chuẩn, không phải một khóa NextPDF. Hãy đặt nó trong config của pool hoặc bằngini_set('memory_limit', '256M')cho tiến trình CLI/queue, và định cỡ nó dựa vào một đỉnh đã soát hồ sơ, không phải một sự đoán.
- Giới hạn bộ đệm ảnh.
- Liên quan. Ghi luồng và bộ nhớ.
Mục: bộ nhớ tăng theo số trang trên các tài liệu rất lớn
Phần tiêu đề “Mục: bộ nhớ tăng theo số trang trên các tài liệu rất lớn”- Triệu chứng. Một tài liệu nhiều nghìn trang cạn bộ nhớ ngay cả khi mỗi trang nhỏ, và đỉnh tăng gần như theo bước với số trang.
- Nguyên nhân khả dĩ. Bộ ghi có đệm giữ toàn bộ tài liệu đã tuần tự hóa trong heap. Với các tài liệu rất lớn đó là chi phí trội.
- Giải pháp.
- Hãy ưu tiên đường ghi luồng. Dùng đường ghi luồng đã tài liệu hóa được mô tả
trong Ghi luồng và bộ nhớ:
nó tuần tự hóa mỗi trang khi nó được soạn và giải phóng bộ đệm, điều này
giảm tăng trưởng bộ-đệm-trang/đầu-ra; siêu dữ liệu nhỏ theo từng đối tượng
(offset, cây trang) vẫn có thể tỷ lệ theo số trang/đối tượng. Hãy theo điểm
vào đã tài liệu hóa thay vì sao chép các lớp nội bộ — engine ghi luồng nền
tảng thuộc bậc
experimentalvà các ký hiệu của nó không phải bề mặt công khai ổn định. - Với bộ phân tích
writeHtml()native, hãy nhớ rằng bộ nhớ phía đầu vào bị giới hạn bởi cả các bảo vệ độ-sâu-lồng và số-lượng-phần-tử: ADR-001 chặn lồng ởMAX_NESTING_DEPTH = 100và từ chối các tài liệu vượtMAX_ELEMENT_COUNT = 50000. Một tài liệu chạm trần phần tử được báo cho biết vậy một cách tường minh thay vì âm thầm cạn bộ nhớ. Các trần ADR-001 này chi phối chỉ bộ phân tích native; cầu nối Chrome tùy chọn (writeHtmlChrome()) kết xuất ngoài tiến trình và có các giới hạn bộ-nhớ/đầu-vào riêng biệt của nó, không phải các trần này.
- Hãy ưu tiên đường ghi luồng. Dùng đường ghi luồng đã tài liệu hóa được mô tả
trong Ghi luồng và bộ nhớ:
nó tuần tự hóa mỗi trang khi nó được soạn và giải phóng bộ đệm, điều này
giảm tăng trưởng bộ-đệm-trang/đầu-ra; siêu dữ liệu nhỏ theo từng đối tượng
(offset, cây trang) vẫn có thể tỷ lệ theo số trang/đối tượng. Hãy theo điểm
vào đã tài liệu hóa thay vì sao chép các lớp nội bộ — engine ghi luồng nền
tảng thuộc bậc
- Liên quan. Ghi luồng và bộ nhớ.
Mục: một worker sống lâu cạn bộ nhớ sau nhiều công việc
Phần tiêu đề “Mục: một worker sống lâu cạn bộ nhớ sau nhiều công việc”- Triệu chứng. Các lần kết xuất đơn lẻ thành công, nhưng một queue worker kết xuất nhiều PDF liền nhau cạn bộ nhớ sau vài phút hoặc vài giờ.
- Nguyên nhân khả dĩ. Một tiến trình PHP sống lâu tích lũy các cấp phát qua các công việc. Một sự tăng trưởng chậm vô hình trong một yêu cầu cộng dồn qua hàng nghìn yêu cầu.
- Giải pháp.
- Chia sẻ các registry, tái tạo các tài liệu. Hãy dựng
FontRegistryvàImageRegistrymột lần lúc khởi động và truyền chúng cho mộtDocumentFactory; tạo mộtDocumentmới cho mỗi công việc bằng$factory->create($config). Việc phân tích phông chữ và ảnh khi đó xảy ra một lần cho tiến trình, không phải một lần cho mỗi công việc, và cây tài liệu theo-từng-công-việc được thu gom khi nó ra khỏi phạm vi. Hãy theoexamples/14-worker-factory.php. - Giới hạn bộ đệm ảnh dùng chung bằng
new ImageRegistry(maxCacheBytes: ...)để nó không thể tăng không giới hạn qua các công việc. - Tái chế worker — kiểm soát tiến trình, không phải một bảo đảm của engine.
Trong PHP-FPM, hãy đặt
pm.max_requestsđể mỗi child tái sinh sau một số yêu cầu cố định. Trong các queue Laravel hãy dùngqueue:work --max-jobs/--max-time/--memory; trong Symfony Messenger hãy dùngmessenger:consume --limit/--time-limit/--memory-limit.
- Chia sẻ các registry, tái tạo các tài liệu. Hãy dựng
- Liên quan. Ghi luồng và bộ nhớ.
Mục: vách thông lượng trên một tiến trình lạnh hoặc chưa-được-làm-nóng-đủ
Phần tiêu đề “Mục: vách thông lượng trên một tiến trình lạnh hoặc chưa-được-làm-nóng-đủ”- Triệu chứng. Các lần kết xuất đầu tiên trong một tiến trình mới chậm, hoặc mỗi yêu cầu trả một chi phí phân tích mà các yêu cầu ấm không nên trả.
- Nguyên nhân khả dĩ. Hai chi phí khởi-động-lạnh chồng lên nhau. PHP không có
opcache biên dịch lại mọi tệp trên mỗi yêu cầu, và một
FontRegistrychưa được làm nóng phân tích mỗi mặt phông chữ lần đầu nó được dùng. - Giải pháp.
- Bật opcache (và JIT ở nơi nó giúp ích). Hãy đặt
opcache.enable=1và mộtopcache.memory_consumptionhào phóng; trong môi trường thực tế hãy đặtopcache.validate_timestamps=0để cache không được kiểm tra lại mỗi yêu cầu. Thiết lập đó đòi hỏi một quy trình triển khai khởi động lại hoặc nạp lại PHP-FPM (hoặc đặt lại opcache theo cách khác, ví dụopcache_reset()/cachetool) trên mỗi lần phát hành — nếu không opcache cứ phục vụ bytecode cũ và mã cũ chạy sau một lần triển khai. Đây là các thiết lập ini PHP chuẩn, không phải các khóa NextPDF. - Làm nóng và khóa registry phông chữ lúc khởi động. Trên một thực thể
FontRegistry,$fontRegistry->warmup($fontFiles)phân tích các mặt chữ một lần lúc khởi động, và$fontRegistry->lock()đóng băng registry để mã lúc chạy không thể biến đổi trạng thái dùng chung;$fontRegistry->isLocked()báo cáo trạng thái. Trong một worker hoặc một application server thực sự sống lâu — một queue consumer hoặc một worker RoadRunner/Swoole/Octane giữ cùng tiến trình PHP sống qua nhiều yêu cầu — một registry đã được làm nóng, đã khóa giữ lại các mặt chữ đã phân tích của nó trong trạng thái đối tượng, biến việc phân tích phông chữ theo-từng-yêu-cầu thành một chi phí khởi-động-tiến-trình một-lần. Dưới mô hình yêu cầu PHP-FPM chuẩn, trạng thái đối tượng đã được làm nóng đó không sống sót qua các yêu cầu: opcache cache các lớp đã biên dịch và bytecode, không phải trạng thái đối tượng userland đã được làm nóng, nên mộtFontRegistryđã được làm nóng được dựng lại theo từng yêu cầu (chạy lại mỗi yêu cầu từ bootstrap của child), không phải được giữ ấm qua các yêu cầu bên trong một child. Trên PHP-FPM thuần, opcache chủ yếu khấu hao chi phí biên dịch lại bytecode; hãy chấp nhận rằng việc phân tích phông chữ được trả theo từng yêu cầu, không bị loại bỏ. Sự khấu hao qua-các-yêu-cầu — phân tích mỗi mặt chữ một lần cho cả vòng đời của tiến trình — chỉ áp dụng trong một tiến trình thực sự sống lâu như một worker RoadRunner/Swoole/Octane hoặc một queue consumer giữ cùng tiến trình PHP sống qua nhiều yêu cầu. - Đừng phân tích lại cùng một template theo từng yêu cầu. Hãy giải quyết
phông chữ và các tài nguyên tái dùng được một lần lúc khởi động qua các
registry dùng chung; chỉ
Documenttheo-từng-công-việc nên được tạo trong yêu cầu.
- Bật opcache (và JIT ở nơi nó giúp ích). Hãy đặt
- Liên quan. Ghi luồng và bộ nhớ.
Mục: máy chủ bão hòa và độ trễ tăng vọt dưới sự đồng thời
Phần tiêu đề “Mục: máy chủ bão hòa và độ trễ tăng vọt dưới sự đồng thời”- Triệu chứng. Độ trễ từng-lần-kết-xuất ổn khi cô lập, nhưng dưới tải hộp swap, CPU bão hòa, hoặc các yêu cầu xếp hàng và hết thời gian.
- Nguyên nhân khả dĩ. Quá nhiều worker PHP-FPM cho RAM khả dụng, nên tổng các đỉnh worker vượt bộ nhớ vật lý và host swap; hoặc quá ít worker, nên các yêu cầu tuần tự hóa sau một pool nhỏ.
- Giải pháp.
-
Định cỡ
pm.max_childrentừ một đỉnh đã soát hồ sơ. Hãy dùng công thức chuẩn:pm.max_children = (total RAM - OS/other overhead) / per-worker peak memoryHãy đo đỉnh thực của một worker với một tài liệu đại diện (xem ghi chú soát hồ sơ trong Phạm vi), dành chỗ trống cho hệ điều hành và bất kỳ dịch vụ nào cùng vị trí, và chia. Hãy để một biên độ; đừng định cỡ tới 100% RAM.
-
Ghim chi phí nén trong giới hạn của bạn. Nén Flate có thể là một chi phí CPU đáng kể của việc ghi một luồng và tỷ lệ theo khối lượng các byte luồng nén được, nên số trang và khối lượng phông chữ nhúng ảnh hưởng đến CPU từng-lần-kết-xuất; xử lý ảnh, subset hóa phông chữ, và phân tích đầu vào cũng có thể trội. Hãy đo với các tài liệu đại diện, và tính đến yếu tố dẫn dắt thực khi bạn chọn số worker và CPU.
-
Đặt
pm.max_requestscùng vớipm.max_childrenđể các child tái chế và thu hồi bất kỳ sự tăng trưởng chậm nào, như trong mục worker ở trên.
-
- Liên quan. Ghi luồng và bộ nhớ.
Mục: đầu vào lớn không đáng tin chậm hoặc tốn kém để phân tích
Phần tiêu đề “Mục: đầu vào lớn không đáng tin chậm hoặc tốn kém để phân tích”- Triệu chứng. Một lần kết xuất chậm hoặc nặng bộ nhớ trên một đầu vào lớn hoặc lồng sâu, đặc biệt là HTML hoặc một phông chữ bạn không tạo ra.
- Nguyên nhân khả dĩ. Chi phí phân tích tỷ lệ theo kích thước và cấu trúc đầu vào. Một đầu vào bệnh lý (lồng sâu, một số lượng phần tử khổng lồ, hoặc một phông chữ hỏng định dạng) có thể trội giới hạn.
- Giải pháp.
- Dựa vào các giới hạn của engine. Bộ phân tích HTML
writeHtml()native thực thiMAX_NESTING_DEPTH = 100vàMAX_ELEMENT_COUNT = 50000(ADR-001); các đầu vào vượt các trần đó bị từ chối thay vì được phép cạn tiến trình. (Cầu nối Chrome tùy chọn,writeHtmlChrome(), nằm ngoài phạm vi các trần ADR-001 này và thực thi các giới hạn bộ-nhớ/đầu-vào riêng biệt của nó.) - Coi các phông chữ do người gọi cung cấp là không đáng tin. Một phông chữ hỏng
định dạng ném
NextPDF\Exception\FontParsingExceptionthay vì làm hỏng đầu ra, nên hãy bắt ngoại lệ cụ thể và từ chối đầu vào thay vì thử lại. - Xác thực và định cỡ các đầu vào tại ranh giới của bạn, và áp các giới hạn ở mức yêu cầu lên kích thước tài liệu cho nội dung do-người-gọi-ảnh-hưởng.
- Dựa vào các giới hạn của engine. Bộ phân tích HTML
- Liên quan. Khắc phục sự cố: phông chữ và gắn thẻ.
Bảng quyết định: triệu chứng sang đòn bẩy
Phần tiêu đề “Bảng quyết định: triệu chứng sang đòn bẩy”| Triệu chứng | Đòn bẩy khả dĩ nhất |
|---|---|
Allowed memory size … exhausted trên một lần kết xuất đơn | Hạ $config->withImageCacheBytes(); thu nhỏ ảnh trước khi nhúng; nâng memory_limit theo từng worker |
| Bộ nhớ đỉnh tăng theo số trang | Dùng đường ghi luồng đã tài liệu hóa |
| Bộ nhớ worker leo qua nhiều công việc | Chia sẻ FontRegistry/ImageRegistry qua DocumentFactory; đặt pm.max_requests / --max-jobs |
| Các yêu cầu đầu chậm, chi phí phân tích theo từng yêu cầu | Bật opcache; $fontRegistry->warmup() rồi ->lock() lúc khởi động |
| Host swap / độ trễ tăng vọt dưới tải | Định cỡ pm.max_children = (RAM − overhead) / đỉnh theo từng worker |
| Chậm hoặc nặng trên đầu vào lớn/không-đáng-tin | Dựa vào các trần ADR-001; từ chối các phông chữ hỏng định dạng trên FontParsingException |
Trường hợp biên & điểm cần lưu ý
Phần tiêu đề “Trường hợp biên & điểm cần lưu ý”imageCacheByteslà một trần bộ nhớ, không phải một núm kích thước. Hạ nó chặn bộ đệm để một bản dựng thất bại nhanh; nó không bao giờ tái lấy mẫu hay tái mã hóa các ảnh bạn nhúng. Core không có điều khiển chất lượng ảnh.withCompress(false)làm các tệp lớn hơn và là một trợ thủ gỡ-lỗi/soát-hồ-sơ. Nó không phải một tối ưu kích thước; nó dịch chuyển đánh đổi CPU/bộ nhớ (nó bỏ qua bước nén) thay vì giảm bộ nhớ.- Hồ sơ bộ nhớ chính xác của engine ghi luồng là một thuộc tính bậc
experimentalvà có thể dịch chuyển giữa các bản phát hành minor. Hãy coi bất kỳ phép đo đơn lẻ nào là một quan sát, không phải một hằng số khả chuyển. memory_limit,opcache.*,pm.max_children, vàpm.max_requestslà các thiết lập PHP / PHP-FPM chuẩn. NextPDF không phơi bày các khóa riêng cho chúng; hãy cấu hình chúng trong runtime của bạn, không phải trongConfig.
Xem thêm
Phần tiêu đề “Xem thêm”- Ghi luồng và bộ nhớ — mô hình ghi luồng, các giới hạn ADR-001, và hướng dẫn batch-worker đầy đủ.
- Giảm kích thước tệp PDF — nén và subset hóa phông chữ, hai điều khiển kích thước thực.
- Khắc phục sự cố: phông chữ và gắn thẻ — các thất bại giải quyết, phân tích, và subset hóa phông chữ.
- Chỉ mục cơ sở tri thức
Bảng thuật ngữ: bộ ghi luồng · subset hóa phông chữ