İçeriğe geç

Akış ve bellek: profil çıkarma ve kuyruk işçisi rehberi

NextPDF, tek geçişli çalışır ve belge düzeyinde bir Belge Nesne Modeli (DOM) hiçbir zaman tutmaz; bu nedenle giriş tarafındaki bellek kullanımı, öğe sayısıyla değil iç içe geçme derinliğiyle sınırlanır. Bu sayfa, akış modelini, mimari karar kaydı (ADR)-001’deki kısıtlamaları ve motorun uzun süre çalışan bir kuyruk işçisinde güvenli biçimde nasıl çalıştırılacağını açıklar.

Terminal window
composer require nextpdf/core:^3

NextPDF farklı bellek profillerine sahip iki yazma yoluna sahiptir.

Varsayılan bellek içi yazıcı önce belgenin tamamını oluşturur, ardından onu serileştirir. Tepe bellek kullanımı, toplam çıktı boyutunu takip eder. Bu, tipik belgeler için iyi çalışır ancak çok büyük belgelerde maliyetli olabilir.

Akış yazıcısı, her sayfayı oluşturulduğu sırada serileştirir ve sonraki sayfa başlamadan önce boşaltır. Yayınlanan motor — StreamingPdfWriter, StreamingCursor, DevNullWriter ve WriterState enum’u, src/Writer/Streaming/ içinde — gerçek, nihai, test edilmiş ve 3.1.0 sürümünden bu yana yayınlanmıştır. Bu motor, experimental katmanındaki StreamingWriterInterface ve CursorInterface sözleşmeleri aracılığıyla sunulur. Motor sınıfları dahili olduğundan sözleşmelere bağımlı olun ve uygulamayı Core’un sağlamasına izin verin. (Daha önceki bir .ai/contracts-map.md ek açıklaması, akışı hatalı biçimde “yalnızca sözleşme / uygulama yok” olarak tanımlamıştı; bu eskimiş ek açıklama kusuru #610 numaralı sorunda izlenir ve B1 sözleşme belgelerinde düzeltilmiştir — motor 3.1.0 sürümünden bu yana yayınlanmıştır.)

Akış motoru, yerleşik bellek kullanımının sayfa sayısıyla birlikte büyümeyeceği biçimde tasarlanmıştır. Tamamlanan her sayfanın arabelleği yazıcıya devredilir ve serbest bırakılır. Çapraz başvuru tablosu ve /Kids sayfa ağacı başvuruları, PHP yığınında birikmek yerine hemen diske taşan php://temp/maxmemory:0 geçici akışlarına yazılır. Serileştirilmiş sonuç standart bir sayfa ağacıdır: Count girdisi bir düğümden türeyen yaprak düğümlerin (sayfa nesnelerinin) sayısıdır (ISO 32000-2 §7.7.3.3), Kids girdisi ise o düğümün doğrudan alt öğelerine yapılan dolaylı başvuruların dizisidir (ISO 32000-2 §7.7.3.2). Kesin bellek profili bir experimental katmanı özelliğidir ve ara sürümler arasında değişebilir; bu nedenle tek bir ölçümden çıkardığınız varsayımı sabit olarak kodlamayın.

ADR-001, HTML işleme hattının bellek modelini yönetir. Belirteçleyici, tek geçişte bir belirteç listesi üretir. Ayrıştırıcı bu listeyi soldan sağa tüketir ve içerik akışı operatörlerini bir dizge arabelleğine yazar. Kalıcı bir öğe ağacı oluşturulmaz: ayrıştırıcı, iç içe geçme düzeyi başına en fazla bir HtmlStyleState tutar; bu, MAX_NESTING_DEPTH = 100 ile sınırlanır ve MAX_ELEMENT_COUNT = 50_000 için kesin bir üst sınır uygular. İleriye bakma gerektiren iki işlem — tablo sütun boyutlandırması ile :has() / :last-child seçici ailesi — saklanan bir DOM yerine, düz belirteç listesi üzerinde sınırlı ön tarama dizin dizileri kullanır. Faz 0 kıyaslaması (docs/architecture/adr-001-memory-benchmark.md, 2026-04-06 tarihinde, PHP 8.5.3, memory_limit=1G ile çalıştırıldı) 50,000 öğeli bir belge için akış yolunda 50 MB tepe değeri, buna karşılık kısmi iş saklayan bir simülasyonda 4 MB ölçtü. Rapor, bunun yaklaşık 50 MB’ını mimariden bağımsız olarak biriken içerik akışına atfeder ve akış modeli için o düzenekte 4–5 kat giriş tarafı avantajını yalıtır. Bu rakamlar yalnızca o donanım ve test düzeneğinde gözlemlendi; garanti niteliğinde değildir.

İnce ayar yapmadan önce belleğin profilini çıkarın

“İnce ayar yapmadan önce belleğin profilini çıkarın” başlıklı bölüm

Herhangi bir şeyi değiştirmeden önce ölçün. HTML işleme hattı tools/perf-benchmark.php tarafından denetlenir (composer ai:perf-check ile çalıştırılır); bu araç, mutlak süreç tepe değeri yerine gerileme ekseni olarak kullanılan hedef başına artımlı tepe değeri olan peak_memory_delta_bytes değerini bildirir. Cycle 36 temel ölçümü (docs/architecture/PERFORMANCE-BUDGETS.md §6.3, bir i9-13900K, 64 GB, PHP 8.5.3, opcache kapalı üzerinde 2026-05-17 tarihinde alındı) 16 target/mode çiftinin 12’sinde 0 bayt tepe farkı gözlemledi. Sıfır olmayan dört fark, sonraki çalıştırmalarda sabit kalan ilk dokunuş yazı tipi önbelleği ve izleme arabelleği ayırmalarına atfedildi. Bunları taşınabilir sabitler olarak değil, o donanım düzeneği için gözlemlenen değerler olarak okuyun. Kendi belgenizin anlık profilini çıkarmak için, işleme öncesinde ve sonrasında memory_get_peak_usage(true) değerini örnekleyin ve yinelemeler arasında tepe değeri memory_reset_peak_usage() ile sıfırlayın; bu, kıyaslamanın hedef başına maliyeti yalıtmak için kullandığı yöntemle aynıdır.

Bir kuyruk işçisi, uzun ömürlü bir PHP sürecidir: çerçeveyi bir kez başlatır, yerleşik kalır ve işleri bir döngü içinde işler. Onu hızlı yapan da, bellek hijyenini önemli kılan da budur. Tek bir istekte görünmeyen yavaş bir sızıntı, binlerce iş boyunca birikebilir. PERFORMANCE-BUDGETS §1 bu hata kipini açıkça adlandırır: birçok PDF’i art arda işleyen bir işçi, tekil işlemeler düzgün görünse bile saatler sonra belleği tüketebilir.

NextPDF, işçi ortamlarını destekler. DocumentFactory, bir işçinin her iş için yeni bir belge oluştururken süreç ömrü boyunca yaşayan bir FontRegistry ve ImageRegistry paylaşmasına olanak tanır; böylece yazı tipi ve görüntü ayrıştırması her işte yeniden değil, yalnızca bir kez gerçekleşir. ADR-001, HTML ayrıştırıcısının istek başına ve hiçbir statik değiştirilebilir durum olmadan oluşturulduğunu ve gelecekteki biçimlendirme bağlamı nesnelerinin aynı istek başına kapsamlandırmayı izlemesi gerektiğini kaydeder. Aşağıdaki adımlar, bir işçiyi güvenli biçimde yapılandırır.

Adım 1 — kayıt defterlerini işler arasında paylaşın

“Adım 1 — kayıt defterlerini işler arasında paylaşın” başlıklı bölüm

Kayıt defterlerini süreç başlangıcında bir kez oluşturun ve examples/14-worker-factory.php örneğindeki deseni izleyerek her iş için yeniden kullanın:

<?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');

Görüntü kayıt defterinin maxCacheBytes değeri, paylaşılan önbelleği sınırlar; böylece önbellek işler arasında sınırsız büyüyemez.

Bu bir NextPDF motor garantisi değil, herhangi bir PHP işçisi için genel süreç denetimi uygulamasıdır: uzun ömürlü bir sürecin bellek biriktirmemesi veya eski kodu süresiz çalıştırmaması için işçileri düzenli aralıklarla yeniden başlatın. Başlıca iki PHP kuyruk sistemi de yerleşik sınırlar ve düzenli yeniden başlatma mekanizmaları sağlar.

Örneğin, Laravel kuyrukları için (https://laravel.com/docs/12.x/queues), queue:work komutu işçiyi uzun ömürlü bir süreç olarak çalıştırır. Belgelenmiş seçenekler şunlardır: --memory (varsayılan 128 MB; işçi bellek sınırını aştığında çıkar), --max-jobs (belirli sayıda işten sonra çıkar) ve --max-time (belirli sayıda saniyeden sonra çıkar). queue:restart komutu, işçilere mevcut işten sonra temiz biçimde çıkmaları için sinyal verir; böylece bir dağıtım veya düzenli zamanlayıcı, sürmekte olan bir işlemeyi kesmeden onları geri dönüştürebilir. Laravel Horizon (https://laravel.com/docs/12.x/horizon), Redis işçilerini bir auto dengeleme stratejisi ve düzenli sonlandırma sağlayan php artisan horizon:terminate ile denetler; bu komut, süreç yöneticisi denetleyiciyi yeniden başlatmadan önce sürmekte olan işleri tamamlar.

Örneğin, Symfony Messenger için (https://symfony.com/doc/current/messenger.html), messenger:consume komutu varsayılan olarak sonsuza kadar çalışır. Belgelenmiş sınır seçenekleri şunlardır: --limit (N mesajı işler, ardından çıkar), --memory-limit (örneğin 128M; bellek sınıra ulaştığında çıkar) ve --time-limit (örneğin 3600; süre dolduktan sonra çıkar). Symfony belgeleri, çıkan bir sürecin otomatik olarak yeniden başlaması için işçiyi Supervisor veya systemd altında çalıştırmayı önerir; ayrıca messenger:stop-workers, her işçiye mevcut mesajını bitirmesini ve temiz biçimde çıkmasını söyleyen bir önbellek bayrağı ayarlar.

Her dağıtımda, işçilerin yeni kodu almaları için düzenli bir yeniden başlatma sinyali verin: Laravel için php artisan queue:restart (veya php artisan horizon:terminate), Symfony için php bin/console messenger:stop-workers. Süreç yöneticisi — Supervisor, systemd veya Horizon/Octane denetleyicisi — ardından yeni kod tabanıyla yeni bir süreç başlatır. Bu, uzun ömürlü PHP işçileri için genel bir dağıtım uygulamasıdır ve NextPDF’ten bağımsızdır.

Akış yolu, tamamlanan her sayfayı boşaltarak ve çapraz başvuru ile sayfa ağacı kayıtlarını diske dayalı geçici akışlara taşıyarak tepe belleği sınırlamak üzere tasarlanmıştır. Sonuç olarak, yerleşik bellek kümesinin sayfa sayısıyla birlikte büyümemesi amaçlanır. Bu davranış, yayınlanan 3.1.0 motorunda gözlemlenir ve altın temel yeniden üretilebilirlik testleriyle sabitlenir; ancak profil bir experimental katmanı özelliği olduğundan, sabit bir sayı yerine tasarım davranışı olarak ifade edilir. HTML işleme hattının giriş tarafı belleği, öğe sayısıyla değil MAX_NESTING_DEPTH = 100 ile sınırlanır (ADR-001). Bu sayfadaki tüm somut sayılar tarihli bir yapıta bağlıdır — 2026-04-06 ADR-001 kıyaslaması ve 2026-05-17 PERFORMANCE-BUDGETS Cycle 36 temel ölçümü — ve bu belgelerin adlandırdığı donanım düzeneklerinde gözlemlendi; bunları taşınabilir garantiler değil, gözlemler olarak değerlendirin. 1500 ms / 64 MB değerindeki performance_budget, sözleşmeye dayalı bir üst sınır değil, çalışma zarfıdır.

Akış imlecinin writeContent() yöntemi, baytları sayfa içerik akışına olduğu gibi ekler. Operatör sözdizimini doğrulamaz. Çağıranın etkileyebildiği içeriği işleyen bir işçide, güvenilmeyen girdiyi asla writeContent() yöntemine geçirmeyin; bunun yerine, yayınlanan imlecin PDF değişmez dizge dilbilgisine göre kaçışladığı writeText() yöntemini kullanın. Çıktı akışının sahibi çağırandır: motor bu akışa yazar ancak onu hiçbir zaman kapatmaz veya yeniden açmaz; bu nedenle çıktıyı yönlendiremez. Bir işçi, yazıcının close() yöntemi döndükten sonra tanıtıcıyı kendisi kapatmalıdır; aksi takdirde işler arasında bir dosya tanımlayıcısı sızdırır. Kayıt defterlerini işler arasında paylaşmak, bir güven sınırı değil, bir performans iyileştirmesidir: paylaşılan bir ImageRegistry, ayrıştırılmış görüntüleri önbelleğe alır; bu nedenle maxCacheBytes değerini bilinçli olarak boyutlandırın ve çok kiracılı bir işçide kiracılar arasında önbellek yalıtımı olduğunu varsaymayın.

İddiaStandartMaddeKanıt
Akış yazıcısı, Kids girdisi düğümün doğrudan alt öğelerine yapılan dolaylı başvuruların dizisi olan bir sayfa ağacı üretir.ISO 32000-2§7.7.3.2
Akış yazıcısı, sayfa ağacı düğümünden türeyen yaprak sayfa nesnelerinin sayısına eşit bir Count girdisi üretir.ISO 32000-2§7.7.3.3

Maddeler başka sözcüklerle aktarılmış ve terimler sözlükle uyumlu tutulmuştur; hiçbir normatif metin yeniden üretilmemiştir.

  • Contracts / StreamingexperimentalStreamingWriterInterface ve CursorInterface ile bunların durum makinesi.
  • HTML / Streaming constraints (ADR-001) — tek geçişli işleme, saklanan DOM kullanılmaması kararı ve yeniden gözden geçirme eşikleri.
  • Performance — HTML işleme hattı gecikmesi ve bellek gerileme denetimi.
  • Layout — sayfa başına durum tutmayan sayfa donatısı motorları.
  • PERFORMANCE-BUDGETS — sızıntı yapan işçi hata kipi ve gerileme denetimi temel ölçümü.