PHP 8.3'te json_decode ile 180 MB log dosyasını parçalarken bellek

这篇内容暂时只有 Türkçe 版本,以下是原文。

Üretimde 8.3.4, Debian 12, php-fpm, memory_limit 256M. Elimde 180 MB’lık, satır başına bir JSON nesnesi olan bir uygulama log dosyası vardı; her satır 1–3 KB, toplam ~140 bin satır. Amacım belirli bir request_id aralığını çekip başka bir formata yazmak. Naif yol: file() + array_map(json_decode) + array_filter. 256M’de 90. saniyede OOM.

Yaptığım şey dosyayı belleğe almadan satır satır okumak ve decode sonucunu hemen bırakmak oldu:

$in = new SplFileObject('/var/log/app.jsonl', 'r');
$out = fopen('php://temp', 'w+');
while (!$in->eof()) {
    $line = $in->fgets();
    if ($line === '' || $line[0] !== '{') { continue; }
    $row = json_decode($line, true, 8, JSON_THROW_ON_ERROR);
    if (!isset($row['request_id'])) { continue; }
    fwrite($out, $row['request_id'] . "\t" . ($row['ms'] ?? 0) . "\n");
    unset($row, $line);
}
rewind($out);

Aynı dosyada tepe bellek 256M’den ~14M’ye indi, süre 90 sn’den 22 sn’ye düştü. Sürpriz olan json_decode değil, file() çağrısının kendisiydi: dosyayı diziye almak satır başına ayrı zval + string kopyası demek, 180 MB’lık dosya kolayca 500 MB’a şişiyor. SplFileObject::fgets tek satırlık tampon tutuyor, unset ise sadece referansı bırakıyor; asıl kazanç decode’un erken bırakılması.

JSON_THROW_ON_ERROR’u bilerek ekledim: bozuk satır sessizce null dönüp filter’dan geçince saatlerce yanlış sayımla uğraşmıştım. depth=8 yeterli, çünkü log şeması düz; daha derin bir şema gelirse 512 hata koduyla patlar, bunu testte gördüm.

Nerede işe yaramaz: satırlar 5 MB’ı aşan tek JSON blob’lar ise SplFileObject yine tüm satırı belleğe alır ve json_decode kendi içinde kopya çıkarır; o durumda akış tabanlı bir parser ya da önce split şart. Bir de php://temp 2 MB’ı geçince diske düşer; çıktıyı doğrudan başka bir dosyaya yazacaksanız temp’e hiç gerek yok, ben sadece sıralamayı görmek için kullandım. Windows’ta satır sonu karışıksa fgets son satırı boş dönebilir, $line !== "" kontrolünü o yüzden bıraktım.

Sizin log işlerinde json_decode’u satır satır mı çağırıyorsunuz, yoksa toplu decode edip sonra mı filtreliyorsunuz? Toplu yolda bellek tavanını nasıl hesaplıyorsunuz?

0
赞同度 100% · 0 条评论 · 4 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。