Node.js İle Stream Kullanarak Büyük Dosya Okuma Nasıl Yapılır?

Node.js İle Stream Kullanarak Büyük Dosya Okuma Nasıl Yapılır?
Node.js İle Stream Kullanarak Büyük Dosya Okuma Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu rehberdeki kod örneklerini çalıştırabilmek için bilgisayarınızda Node.js'in 20.x veya daha güncel bir sürümünün yüklü olması önerilir. Stream yapısı Node.js'in temelinde yer aldığı için ekstra bir paket kurulumuna gerek yoktur.

  • Node.js (LTS sürümü önerilir)
  • Temel JavaScript (ES6+ sözdizimi) bilgisi
  • Terminal veya komut satırı kullanımı

Projenizi başlatmak için bir klasör oluşturun ve terminalde npm init -y komutunu çalıştırarak package.json dosyanızı oluşturun.

Stream Nedir ve Neden Kullanmalıyız?

Stream (akış), verinin bir kaynaktan hedefe küçük paketler halinde taşınmasıdır. Dosya okuma işlemlerinde stream kullanmak, dosyanın tamamını RAM'e yüklemek yerine, sadece işlenen anlık parçayı bellekte tutar. Bu, özellikle sunucu tarafında çok sayıda eşzamanlı istek alan uygulamalar için hayati önem taşır.

Özellik fs.readFile (Geleneksel) fs.createReadStream (Stream)
Bellek Kullanımı Dosya boyutu kadar (Yüksek) Sabit ve düşük (Chunk boyutu kadar)
Hız Tüm dosya yüklenene kadar bekler İlk parça okunduğu an işleme başlar
Kullanım Alanı Küçük konfigürasyon dosyaları Büyük loglar, video/ses dosyaları

Adım Adım Okuma Akışı Oluşturma

Node.js'te fs.createReadStream metodunu kullanarak bir dosya akışı başlatırız. Bu metod bize bir ReadableStream nesnesi döner. Aşağıdaki kod, basit bir dosyanın nasıl okunacağını göstermektedir:

const fs = require('fs');

// Okunacak dosya yolunu belirtin
const stream = fs.createReadStream('buyuk-dosya.txt', { encoding: 'utf8' });

stream.on('data', (chunk) => {
    console.log(`Yeni veri parçası alındı: ${chunk.length} bayt`);
});

stream.on('end', () => {
    console.log('Dosya okuma işlemi tamamlandı.');
});

stream.on('error', (err) => {
    console.error('Hata oluştu:', err.message);
});

Bu örnekte data olayı, her parça (chunk) okunduğunda tetiklenir. end olayı ise dosyanın sonuna gelindiğini belirtir. error olayı ise dosya bulunamadığında veya okuma izni olmadığında devreye girer.

Backpressure Yönetimi ve Pipe Kullanımı

Büyük dosyaları bir yerden bir yere aktarırken (örneğin bir dosyayı okuyup HTTP yanıtı olarak göndermek), okuma hızı ile yazma hızı farklı olabilir. Bu durum "backpressure" denilen tıkanıklığa yol açar. pipe() metodu bu süreci otomatik yönetir.

const fs = require('fs');

const okumaAkisi = fs.createReadStream('kaynak.txt');
const yazmaAkisi = fs.createWriteStream('kopya.txt');

// Pipe, backpressure yönetimini otomatik yapar
okumaAkisi.pipe(yazmaAkisi);

yazmaAkisi.on('finish', () => {
    console.log('Dosya başarıyla kopyalandı.');
});

pipe metodu, okuma akışını yazma akışına bağlar. Eğer yazma işlemi yavaşlarsa, okuma akışı otomatik olarak yavaşlatılır.

Async Iterators ile Modern Stream Okuma

Node.js 2026 standartlarında, stream nesneleri for await...of döngüsü ile çok daha temiz bir şekilde okunabilir. Bu yöntem, olay dinleyicileri (event listeners) yerine daha okunabilir bir kod yapısı sunar.

const fs = require('fs');

async function dosyayiOku(dosyaYolu) {
    const stream = fs.createReadStream(dosyaYolu, { encoding: 'utf8' });

    try {
        for await (const chunk of stream) {
            console.log('Parça işleniyor...');
            // Burada veriyi işleyebilirsiniz
        }
        console.log('İşlem bitti.');
    } catch (err) {
        console.error('Okuma hatası:', err);
    }
}

dosyayiOku('veriler.log');

Bu yöntem, hata yönetimini try-catch blokları ile yapmanıza olanak tanır ve kodun karmaşıklığını azaltır.

Güvenlik ve Performans İpuçları

Kritik Uyarı: Kullanıcıdan gelen dosya yollarını doğrudan fs.createReadStream içine vermeyin. Bu, "Directory Traversal" saldırılarına yol açabilir. Dosya yollarını mutlaka path.basename() veya izin verilen bir beyaz liste (whitelist) ile doğrulayın.

Performans için highWaterMark seçeneğini kullanarak chunk boyutunu ayarlayabilirsiniz. Varsayılan değer genellikle 64KB'dır. Çok büyük dosyalar için bu değeri artırmak disk okuma sayısını azaltabilir.

const stream = fs.createReadStream('buyuk-video.mp4', {
    highWaterMark: 128 * 1024 // 128KB'lık parçalar halinde oku
});

Sıkça Sorulan Sorular

Stream kullanırken dosya sonunu nasıl anlarım?

end olayını dinleyerek veya for await...of döngüsünün doğal olarak bitmesini bekleyerek dosya sonuna gelindiğini anlayabilirsiniz.

Neden fs.readFile yerine stream tercih etmeliyim?

fs.readFile tüm dosyayı RAM'e yükler. Eğer dosya 2GB ise ve sunucunuzda 1GB boş RAM varsa, uygulamanız çöker. Stream ise sadece o anki parçayı belleğe alır.

Pipe kullanırken hata yönetimi nasıl yapılır?

stream.pipeline yardımcı fonksiyonunu kullanmanız önerilir. pipe metodu hataları otomatik yaymaz, ancak pipeline hataları yakalayıp temiz bir şekilde yönetmenizi sağlar.

highWaterMark değerini neye göre seçmeliyim?

Genellikle varsayılan değer yeterlidir. Ancak yüksek performanslı sistemlerde disk ve ağ hızına göre deneme-yanılma yoluyla optimize edilebilir.

Stream ile veriyi nasıl dönüştürebilirim?

stream.Transform sınıfını kullanarak veriyi okurken aynı anda sıkıştırabilir veya formatını değiştirebilirsiniz.

Stream Performansını İzleme ve Hata Ayıklama

Büyük ölçekli uygulamalarda stream işlemlerinin ne kadar bellek tükettiğini ve işlem süresini nasıl etkilediğini anlamak kritik öneme sahiptir. Node.js'in yerleşik process.memoryUsage() metodu ile stream akışı sırasındaki bellek değişimlerini gözlemleyebilirsiniz.

Hata ayıklama sürecinde ise stream.pipeline metodunu kullanmak, standart .pipe() kullanımından daha güvenlidir. .pipe() metodu, stream zincirindeki hataları otomatik olarak yaymaz; bu da bellek sızıntılarına veya askıda kalan dosya tanımlayıcılarına (file descriptors) yol açabilir.

const { pipeline } = require('stream');
const fs = require('fs');
const zlib = require('zlib');

pipeline(
  fs.createReadStream('buyuk-veri.log'),
  zlib.createGzip(),
  fs.createWriteStream('buyuk-veri.log.gz'),
  (err) => {
    if (err) {
      console.error('Pipeline hatası:', err);
    } else {
      console.log('İşlem başarıyla tamamlandı.');
    }
  }
);

Stream Tabanlı Dosya İşleme Senaryosu: CSV Dönüştürücü

Gerçek dünya senaryolarında, genellikle devasa CSV dosyalarını satır satır okuyup belirli bir formatta (örneğin JSON) dışa aktarmanız gerekir. Bu işlemde Transform stream sınıfını kullanarak veriyi belleğe yüklemeden işleyebilirsiniz.

Aşağıdaki örnek, bir CSV dosyasını okuyup her satırı bir nesneye dönüştüren ve bunu bir dosyaya yazan yapıyı göstermektedir:

const { Transform } = require('stream');

class CSVToJson extends Transform {
  constructor() {
    super({ objectMode: true });
  }

  _transform(chunk, encoding, callback) {
    const data = chunk.toString().split(',');
    const json = JSON.stringify({ id: data[0], name: data[1] });
    this.push(json + '\n');
    callback();
  }
}

const readStream = fs.createReadStream('data.csv');
const writeStream = fs.createWriteStream('data.json');
const transformer = new CSVToJson();

readStream.pipe(transformer).pipe(writeStream);

İleri Seviye İpuçları

  • Object Mode: Eğer stream üzerinden sadece string veya buffer değil, JavaScript nesneleri aktaracaksanız objectMode: true seçeneğini kullanın.
  • Destroy Metodu: Stream'i manuel olarak durdurmanız gerektiğinde stream.destroy() metodunu kullanarak kaynakları serbest bırakın.
  • Event Dinleyicileri: 'data' ve 'end' event'leri yerine, akışın durumunu yönetmek için 'finish' ve 'close' event'lerini takip etmek daha tutarlı sonuçlar verir.

Sonuç

Node.js ile stream kullanarak büyük dosya okuma, ölçeklenebilir uygulamalar geliştirmenin temel taşıdır. Bellek verimliliği ve hata yönetimi sayesinde sistem kaynaklarını en verimli şekilde kullanırsınız. Bu rehberdeki yöntemleri uygulayarak, dosya boyutundan bağımsız, stabil çalışan sistemler kurabilirsiniz. Bir sonraki adım olarak, zlib modülü ile stream üzerinden dosya sıkıştırma ve açma işlemlerini inceleyerek veri transferi performansınızı daha da artırabilirsiniz.

Kod güvenliği sorumluluk reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) dosya sistemi işlemlerinde yetkilendirme, girdi doğrulama ve hata loglama mekanizmalarını mutlaka uygulayın.

Bu yazıya tepkinizi paylaşın:
Selin Korkmaz

Kendin yap (DIY) projeleri ve sürdürülebilir yaşam ipuçları üzerine odaklanıyorum. Okuyucularıma bütçe dostu ve yaratıcı çözüm önerileri sunmaktan keyif alıyorum.

Yorumlar (0)

Yorum Yaz