Mert Tosun
← Yazılar
Node.js ile File Streaming: Büyük Dosyalar, Bellek ve Performans

Node.js ile File Streaming: Büyük Dosyalar, Bellek ve Performans

Blog YazarıYazılım

fs.readFile ile on gigabaytlık bir dosyayı okumak, süreç belleğini tek seferde doldurmaya davetiye çıkarır. Stream ise veriyi parça parça taşır: bellek kullanımı sınırlı kalır, gecikme (latency) erken başlar ve tüketici hazır oldukça üretici yavaşlayabilir — buna backpressure denir.

Bu yazıda Node.js’te dosya akışlarının temelini ve üretimde sık kullanılan kalıpları özetliyoruz.

Stream Nedir?

Node.js’te stream, sınırlı boyutta chunk’lar halinde akan veri kanalıdır. Türler:

Tür Rol
Readable Okur (ör. dosya, HTTP yanıtı)
Writable Yazar (ör. disk, socket)
Duplex İki yönlü (ör. TCP)
Transform Okur, dönüştürür, yazar (ör. gzip)

Dosya tarafında en sık kullanılanlar: fs.createReadStream, fs.createWriteStream.

readFile Yerine createReadStream

import fs from 'node:fs';

// Kötü: tüm dosya RAM’e
// const buf = fs.readFileSync('huge.bin');

const rs = fs.createReadStream('huge.bin', { highWaterMark: 64 * 1024 });
rs.on('data', (chunk) => {
  // chunk: Buffer veya string (encoding verilirse)
});
rs.on('end', () => console.log('bitti'));
rs.on('error', (err) => console.error(err));

highWaterMark dahili tampon boyutunu etkiler; çoğu senaryoda varsayılan yeterlidir.

pipe ve pipeline

Eski kalıp readable.pipe(writable) hâlâ çalışır; modern ve önerilen yol stream.promises.pipeline veya stream.pipeline ile hata iletimi ve temiz kapanıştır:

import fs from 'node:fs';
import { pipeline } from 'node:stream/promises';
import zlib from 'node:zlib';

await pipeline(
  fs.createReadStream('input.log'),
  zlib.createGzip(),
  fs.createWriteStream('input.log.gz')
);

pipeline zincirdeki hatayı yakalar ve stream’leri güvenli biçimde sonlandırmaya çalışır.

Backpressure (Basınç)

Okuyucu yavaş, yazıcı hızlıysa Writable tamponu dolar. Node.js bu durumda Readable’ı geçici olarak durdurur — bellek taşması önlenir. pipe bunu yönetir; manuel write kullanıyorsanız stream.write() dönüş değerini ve drain olayını dinlemeniz gerekir.

HTTP ile Birlikte

Büyük dosyayı istemciye gönderirken:

import fs from 'node:fs';
import http from 'node:http';

http.createServer((req, res) => {
  const stream = fs.createReadStream('report.pdf');
  res.setHeader('Content-Type', 'application/pdf');
  stream.pipe(res);
  stream.on('error', () => res.destroy());
}).listen(3000);

Üretimde pipeline(fs.createReadStream(...), res) tercih edin; hata ve kapanış tek yerde toplanır.

Sıkıştırma Zinciri

Log arşivleme veya statik varlık sunumu için:

await pipeline(
  fs.createReadStream('data.json'),
  zlib.createGzip(),
  fs.createWriteStream('data.json.gz')
);

Transform katmanı eklemek (ör. satır satır JSON parse) için stream.Transform veya readline ile createInterface birlikte kullanılabilir.

Hata Yönetimi

  • pipeline / finished kullanın; yalnız pipe ile unutulan error dinleyicileri yarı kalmış stream bırakır.
  • HTTP yanıtında hata olursa bağlantıyı res.destroy(err) ile kapatın.

Ne Zaman Stream?

  • Dosya RAM’den büyük veya boyutu önceden bilinmiyorsa
  • Erken tüketim gerekiyorsa (ilk byte’lar gelir gelmez işlem)
  • Zincirleme dönüşüm (şifreleme, sıkıştırma, parse)

Ne Zaman Buffer?

  • Küçük yapılandırma dosyaları
  • Tüm içeriğin atomik olarak gerekli olduğu testler

Özet

Stream, Node.js’in I/O verimliliğinin kalbidir. Dosya + HTTP + sıkıştırma üçlüsünde pipeline ile hataları tek noktada toplayın; readFile ile büyük dosyayı “kopyalamak” yerine akışı tercih edin.

İyi kodlamalar.