Gereksinimler ve Ön Hazırlık
Bu rehberdeki kod örneklerini çalıştırabilmek için sisteminizde Node.js'in güncel bir sürümünün (2026 itibarıyla LTS sürümü olan v22 veya üzeri önerilir) kurulu olması gerekir. Kurulumu doğrulamak için terminalinize node -v komutunu yazabilirsiniz.
Çalışma ortamınızı hazırlamak için bir proje klasörü oluşturun ve gerekli paketleri başlatın:
mkdir node-stream-compression
cd node-stream-compression
npm init -y
Bu işlemlerden sonra, Node.js'in yerleşik modülü olan zlib ve fs (File System) modüllerini kullanacağız. Harici bir paket yüklemenize gerek yoktur; bu da uygulamanızın bağımlılıklarını minimumda tutar.
Stream ve Buffer Mantığını Anlamak
Node.js'te Stream (akış), verinin bir noktadan diğerine parça parça aktarılmasıdır. Buffer ise bu parçaların bellekte tutulduğu geçici alanlardır. Büyük bir dosyayı doğrudan okumaya çalışmak, dosya boyutu RAM kapasitenizi aştığında uygulamanın çökmesine (Out of Memory hatası) neden olur.
Sıkıştırma işleminde Stream kullanmak, veriyi okurken sıkıştırıp eş zamanlı olarak bir hedefe (dosya veya ağ soketi) yazmanızı sağlar. Bu sayede bellek kullanımı sabit kalır.
Adım Adım Gzip Sıkıştırma İşlemi
İlk olarak, bir metin dosyasını gzip algoritması ile nasıl sıkıştıracağımızı inceleyelim. zlib.createGzip() fonksiyonu, bir "Transform Stream" oluşturur. Bu akış, kendisine gelen veriyi sıkıştırarak bir sonraki aşamaya iletir.
const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream');
const gzip = zlib.createGzip();
const source = fs.createReadStream('buyuk-veri.txt');
const destination = fs.createWriteStream('veri.txt.gz');
pipeline(source, gzip, destination, (err) => {
if (err) {
console.error('Sıkıştırma sırasında hata oluştu:', err);
} else {
console.log('Dosya başarıyla sıkıştırıldı.');
}
});
Burada pipeline fonksiyonunu kullanmamızın sebebi, akışlar arasındaki hata yönetimini (error handling) otomatik olarak yapması ve akışlar kapandığında kaynakları temizlemesidir.
Sıkıştırılmış Veriyi Okuma ve Açma
Sıkıştırılmış bir veriyi okumak için zlib.createGunzip() kullanılır. Bu işlem, sıkıştırılmış akışı alır ve orijinal haline geri döndürür. Aşağıdaki örnekte, daha önce oluşturduğumuz .gz dosyasını okuyup içeriğini konsola yazdırıyoruz.
const fs = require('fs');
const zlib = require('zlib');
const gunzip = zlib.createGunzip();
const input = fs.createReadStream('veri.txt.gz');
const output = process.stdout; // Çıktıyı terminale yönlendiriyoruz
input.pipe(gunzip).pipe(output);
Bu yöntem, dosyanın tamamını belleğe almadan, parça parça işleyerek terminale basar. Çok büyük log dosyalarını incelemek için idealdir.
Stream Kullanımında Karşılaştırmalı Analiz
Aşağıdaki tablo, geleneksel yöntem ile Stream kullanımının temel farklarını özetlemektedir.
| Özellik | Geleneksel (fs.readFile) | Stream (fs.createReadStream) |
|---|---|---|
| Bellek Kullanımı | Dosya boyutu kadar (Yüksek) | Sabit ve düşük (Chunk boyutu) |
| Başlama Hızı | Dosyanın tamamı okunana kadar bekler | İlk parça okunduğu an başlar |
| Büyük Veri Uyumu | Düşük (Crash riski) | Yüksek (Ölçeklenebilir) |
HTTP Sunucusunda Sıkıştırılmış Yanıt Dönme
Modern web uygulamalarında, istemciye gönderilen verinin sıkıştırılması bant genişliğini ciddi oranda azaltır. Node.js'in yerleşik http modülü ile bir sunucu oluşturup, yanıtı anlık olarak sıkıştırarak gönderelim.
const http = require('http');
const zlib = require('zlib');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Encoding': 'gzip',
'Content-Type': 'text/plain'
});
const gzip = zlib.createGzip();
gzip.pipe(res);
gzip.write('Bu veri sıkıştırılarak gönderiliyor.');
gzip.end();
}).listen(3000, () => console.log('Sunucu 3000 portunda çalışıyor.'));
Bu kod bloğu, istemciye gönderilen veriyi gzip ile sıkıştırır. İstemci tarafı (tarayıcı), Content-Encoding: gzip başlığını gördüğünde veriyi otomatik olarak açacaktır.
Hata Yönetimi ve Güvenlik Uyarıları
Kritik Güvenlik Uyarısı: Kullanıcıdan gelen veriyi sıkıştırırken veya açarken "Zip Bomb" saldırılarına dikkat edilmelidir. Çok küçük bir sıkıştırılmış dosya, açıldığında gigabaytlarca boyuta ulaşarak sunucunuzu kilitleyebilir. Her zaman işlenen veri boyutunu sınırlayın ve ham dosya boyutunu kontrol edin.
Sıkıştırma işlemlerinde hata yönetimi için pipeline kullanmak, bellek sızıntılarını önlemek için en güvenli yoldur. Ayrıca, dosya yollarını doğrudan kullanıcıdan alıyorsanız, path.normalize kullanarak dizin geçişi (directory traversal) saldırılarını engelleyin.
Sıkça Sorulan Sorular
Stream kullanırken neden pipeline tercih etmeliyim?
pipe() metodu hataları otomatik olarak yaymaz, bu da hata yönetimi için karmaşık olay dinleyicileri yazmanıza neden olur. pipeline ise hata durumunda tüm akışları güvenli bir şekilde kapatır ve bellek sızıntısını önler.
Gzip her dosya türü için uygun mudur?
Gzip metin tabanlı verilerde (JSON, HTML, CSS, JS) çok başarılıdır. Ancak zaten sıkıştırılmış olan (JPEG, PNG, MP4) dosyaları tekrar sıkıştırmak dosya boyutunu küçültmez, aksine işlemci yükünü artırır.
Sıkıştırma seviyesini ayarlayabilir miyim?
Evet, zlib.createGzip({ level: 9 }) şeklinde bir konfigürasyon nesnesi geçebilirsiniz. 1 en hızlı ama az sıkıştırma, 9 ise en yavaş ama en yüksek sıkıştırma oranıdır.
Büyük dosyaları parçalara ayırarak sıkıştırmak mantıklı mı?
Evet, özellikle dosya transferi yaparken dosyayı 64KB'lık parçalara bölüp sıkıştırmak, ağ kesilmelerinde sadece o parçayı tekrar göndermenize olanak tanır.
Bellek kullanımını nasıl izleyebilirim?
Node.js içerisinde process.memoryUsage() fonksiyonunu kullanarak uygulamanızın anlık RAM tüketimini izleyebilirsiniz.
Özel Veri İşleme İçin Stream.Transform Kullanımı
Dahili zlib modülü sıkıştırma için mükemmel olsa da, bazen veriyi sıkıştırmadan önce veya sonra belirli bir formata sokmanız gerekebilir. stream.Transform sınıfı, veriyi bir noktadan alıp işleyerek başka bir noktaya iletmek için idealdir. Örneğin, bir log dosyasını sıkıştırmadan önce tüm satırları büyük harfe çeviren veya hassas verileri maskeleyen bir akış oluşturabilirsiniz.
Aşağıdaki örnekte, veriyi işleyen özel bir Transform sınıfının pipeline içerisinde nasıl konumlandırıldığını görebilirsiniz:
const { Transform } = require('stream');
const { pipeline } = require('stream/promises');
const fs = require('fs');
const zlib = require('zlib');
const maskSensitiveData = new Transform({
transform(chunk, encoding, callback) {
// Gelen verideki e-posta adreslerini maskele
const processed = chunk.toString().replace(/\S+@\S+\.\S+/g, '***@***.com');
callback(null, processed);
}
});
async function runCustomPipeline() {
await pipeline(
fs.createReadStream('input.txt'),
maskSensitiveData,
zlib.createGzip(),
fs.createWriteStream('output.txt.gz')
);
console.log('Veri maskelendi ve sıkıştırıldı.');
}
runCustomPipeline().catch(console.error);
Performans İzleme ve Darboğaz Analizi
Stream tabanlı uygulamalarda performans darboğazları genellikle CPU yoğunluklu sıkıştırma işlemlerinden veya I/O (Giriş/Çıkış) hızındaki uyumsuzluklardan kaynaklanır. Uygulamanızın ne kadar verimli çalıştığını anlamak için perf_hooks modülünü kullanarak işlem sürelerini ölçebilirsiniz.
İzleme İçin İleri İpuçları
- Backpressure Yönetimi: Eğer okuma hızı yazma hızından yüksekse, Node.js otomatik olarak
highWaterMarkseviyesini kontrol eder. Bu değerifs.createReadStreamiçerisinde manuel olarak ayarlayarak bellek ve hız dengesini optimize edebilirsiniz. - CPU Profiling: Sıkıştırma işlemi ana thread'i bloklayabilir. Eğer çok büyük dosyalarla çalışıyorsanız,
worker_threadsmodülünü kullanarak sıkıştırma işlemini ayrı bir thread'e devretmeyi düşünün. - Event Loop Gecikmesi:
perf_hooksile event loop'un ne kadar süreyle meşgul olduğunu takip ederek, uygulamanızın yanıt süresinin düşüp düşmediğini analiz edin.
Aşağıdaki tablo, farklı highWaterMark değerlerinin sistem üzerindeki olası etkilerini özetlemektedir:
| Değer | Bellek Kullanımı | CPU Verimliliği | Kullanım Senaryosu |
|---|---|---|---|
| Düşük (16KB) | Çok Düşük | Düşük (Sık kesinti) | Düşük RAM'li cihazlar |
| Varsayılan (64KB) | Orta | Dengeli | Genel web uygulamaları |
| Yüksek (256KB+) | Yüksek | Yüksek (Hızlı I/O) | Yüksek hızlı ağ/disk işlemleri |
Bu teknikleri uygularken, her zaman pipeline yapısını kullanmaya devam edin; çünkü bu yapı, stream'ler arasında hata oluştuğunda tüm kaynakların (file descriptors) güvenli bir şekilde kapatılmasını garanti eder.
Sonuç
Node.js ile sıkıştırılmış veri akışında Stream kullanımı, uygulamanızın performansını ve ölçeklenebilirliğini doğrudan etkileyen temel bir beceridir. Bu rehberde, zlib modülünün gücünü, pipeline ile hata yönetimini ve HTTP üzerinden sıkıştırılmış yanıt göndermeyi öğrendiniz.
Bir sonraki adım olarak, stream.Transform sınıfını kullanarak kendi özel veri işleme akışlarınızı (örneğin veriyi şifreleyen veya veriden belirli kelimeleri çıkaran bir akış) oluşturmayı deneyebilirsiniz. Kodlarınızda her zaman asenkron yapıyı korumaya ve kaynakları doğru şekilde serbest bırakmaya özen gösterin.


Yorumlar (0)
Yorum Yaz