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.createReadStreamiçine vermeyin. Bu, "Directory Traversal" saldırılarına yol açabilir. Dosya yollarını mutlakapath.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: trueseç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.


Yorumlar (0)
Yorum Yaz