Ders 05 / 20
Dosya Sistemi
Aynı işi sunan üç arayüzün farkı, iş parçacığı havuzunun rolü, hata kodlarıyla dallanma, dizin işlemleri ve yarım yazılmış dosya bırakmayan güncelleme.
İçindekiler
Çalışma zamanı modeli konusu, engelleyen işin olay döngüsünü nasıl durdurduğunu ölçtü. Dosya sistemi arayüzü bu ölçünün doğrudan uygulama alanıdır: aynı okuma işi için üç ayrı çağrı biçimi sunulur ve üçü olay döngüsüne farklı davranır.
Bu ders üç biçimi karşılaştırır, hangi durumda hangisinin doğru olduğunu belirler ve ölçüm toplayıcının ilk gerçek adımını yazar: dosyayı okuyup özetini üretmek.
Üç Arayüz
Yerleşik dosya sistemi modülü aynı işlemleri üç ayrı biçimde sunar. Eşzamanlı biçim
node:fs modülünde Sync sonekiyle, geri çağrılı biçim aynı modülde soneksiz,
söz tabanlı biçim ise node:fs/promises modülünde bulunur.
// fs-uc-arayuz.mjs import { readFileSync, readFile } from 'node:fs'; import { readFile as readFileSoz } from 'node:fs/promises'; const dosya = 'olcumler.ndjson'; const satirSay = (metin) => metin.trim().split('\n').length; // 1. Eszamanli console.log('eszamanli :', satirSay(readFileSync(dosya, 'utf8'))); // 2. Geri cagrili readFile(dosya, 'utf8', (hata, metin) => { if (hata) throw hata; console.log('geri cagri:', satirSay(metin)); }); // 3. Soz tabanli readFileSoz(dosya, 'utf8').then((metin) => { console.log('soz :', satirSay(metin)); }); console.log('senkron akis burada devam etti');
node fs-uc-arayuz.mjs
eszamanli : 12 senkron akis burada devam etti geri cagri: 12 soz : 12
Çıktının sırası üç biçimin farkını gösterir. Eşzamanlı çağrı, sonucu döndürene kadar
sürecin ilerlemesini durdurdu; sonucu ikinci satırdan önce yazdı. Diğer ikisi işi
başlatıp geri döndü, bu yüzden senkron akis burada devam etti satırı onların
sonucundan önce göründü.
Geri çağrılı biçimin imzası bir sözleşme taşır: ilk parametre hatadır, sonuç ikinci
parametredir. Hata yoksa ilk parametre null gelir. Bu düzen, sözler dile girmeden
önce eşzamansız işin tek yoluydu ve yerleşik modüllerin çoğunda hâlâ bulunur.
Söz tabanlı biçim aynı işi async/await ile sıralı okunabilir hâle getirir. Kurs
boyunca varsayılan seçim budur.
Seçim kuralı şudur:
- Eşzamanlı biçim, olay döngüsü henüz iş görmüyorken kullanılır: başlangıçtaki yapılandırma okuması, tek seferlik bir betiğin gövdesi, kapanış anındaki son eşzamanlı yazma. Bir isteği işlerken kullanılmaz.
- Söz tabanlı biçim, hizmet kodunun varsayılanıdır.
- Geri çağrılı biçim, söz sarmalayıcısının maliyetinden kaçınmak gereken sıcak yollarda ve söz tabanlı karşılığı olmayan çağrılarda kullanılır.
Eşzamansızlık Nereden Geliyor
Ağ soketleri için beklemeyi çekirdeğin bildirim düzeneği üstlenir; süreç yoklama aşamasında uyur ve veri geldiğinde uyanır. Dosya sisteminde durum farklıdır: yaygın işletim sistemlerinde sıradan dosyalar için karşılaştırılabilir bir bildirim düzeneği yoktur.
Çalışma zamanı bu boşluğu bir iş parçacığı havuzu (thread pool) ile doldurur. Dosya okuma, yazma ve dizin listeleme çağrıları havuzdaki bir iş parçacığına verilir; o iş parçacığı engelleyen sistem çağrısını yapar, iş bitince sonuç ana iş parçacığının kuyruğuna bırakılır. Ana iş parçacığı bu sırada başka istekleri işlemeye devam eder.
İki sonucu vardır. Birincisi, dosya işlemleri gerçekten paralel yürür; ikincisi, havuzun sabit bir genişliği vardır. Aynı anda havuz genişliğinden çok dosya işlemi başlatılırsa fazlası sıraya girer. Havuz genişliği bir ortam değişkeniyle ayarlanabilir; ölçmeden değiştirmek yarar sağlamaz.
Hata Kodlarıyla Dallanma
Dosya sistemi hataları, iletileriyle değil code alanlarıyla ayırt edilir. İleti
metni okunabilirlik içindir ve değişebilir; kod, işletim sisteminin sistem
çağrısından döndürdüğü ada karşılık gelir.
// fs-hata.mjs import { readFile } from 'node:fs/promises'; async function guvenliOku(yol) { try { return await readFile(yol, 'utf8'); } catch (hata) { if (hata.code === 'ENOENT') return ''; // dosya yok: bos veri if (hata.code === 'EACCES') { // izin yok: cagirana birak throw new Error(`okuma izni yok: ${yol}`, { cause: hata }); } throw hata; } } console.log('var olan :', (await guvenliOku('olcumler.ndjson')).length, 'karakter'); console.log('olmayan :', (await guvenliOku('yok.ndjson')).length, 'karakter'); try { await readFile('yok.ndjson', 'utf8'); } catch (hata) { console.log('kod:', hata.code, '| cagri:', hata.syscall, '| yol:', hata.path); }
node fs-hata.mjs
var olan : 1000 karakter olmayan : 0 karakter kod: ENOENT | cagri: open | yol: yok.ndjson
ENOENT (dosya yok), EACCES (izin yok), EISDIR (dizin verildi), ENOSPC
(disk dolu) ve EMFILE (açık dosya sınırı aşıldı) en sık karşılaşılanlardır. Linux’a
Giriş kursundaki izin modeli, EACCES hatasının ne zaman geleceğini belirler:
sürecin kullanıcı kimliği ve grup üyeliği dosyanın izin bitleriyle karşılaştırılır.
Yakalanan her hatanın yutulmaması önemlidir. Yukarıdaki fonksiyon yalnızca bildiği iki durumu ele alıyor, geri kalanı olduğu gibi yukarı fırlatıyor. Bilinmeyen bir kodu sessizce boş veriye çevirmek, arızayı gizler.
Dizin İşlemleri
Dizin oluşturma, listeleme ve silme aynı modülde bulunur. Aşağıdaki blok örnek ağacı kendi kurar, sonra listeler:
// fs-dizin.mjs import { mkdir, writeFile, readdir, stat, rm, readFile } from 'node:fs/promises'; // Örnek ağacı bu blok kendisi kurar await rm('veri', { recursive: true, force: true }); await mkdir('veri/2024-02-07/ham', { recursive: true }); // ara dizinleri de acar await writeFile('veri/2024-02-07/olcumler.ndjson', await readFile('olcumler.ndjson')); await writeFile('veri/2024-02-07/not.txt', 'kenar-03 dugumu sabahtan beri sicak\n'); const girdiler = await readdir('veri/2024-02-07', { withFileTypes: true }); for (const girdi of girdiler.sort((a, b) => a.name.localeCompare(b.name))) { if (girdi.isDirectory()) { console.log(`dizin - ${girdi.name}`); } else { const { size } = await stat(`veri/2024-02-07/${girdi.name}`); console.log(`dosya ${String(size).padStart(5)} ${girdi.name}`); } }
node fs-dizin.mjs
dizin - ham dosya 36 not.txt dosya 1000 olcumler.ndjson
recursive seçeneği iki yerde geçiyor ve iki farklı işi yapıyor. mkdir çağrısında
ara dizinleri açar ve dizin zaten varsa hata vermez; bu, işlemi yinelemeye dayanıklı
kılar. rm çağrısında ise alt ağacın tamamını siler — force ile birlikte, hedef
yoksa da hata vermez.
rm çağrısı geri alınamaz. Linux’a Giriş kursunda kabuk için söylenen kural burada
da geçerlidir: silinecek yolu bir değişkenden alıyorsanız, silmeden önce yolun
beklediğiniz kök altında kaldığını doğrulayın. Bu doğrulamanın nasıl yazıldığı
sonraki dersin konusudur.
readdir çağrısındaki withFileTypes seçeneği, adların yanında tür bilgisini de
getirir; onsuz her ad için ayrı bir stat çağrısı gerekirdi. Dönen girdilerin sırası
dosya sistemine bağlıdır ve alfabetik olduğu varsayılamaz — örnekteki sıralama
bunun içindir.
Yarım Dosya Bırakmadan Yazmak
Ölçüm toplayıcının ilk çıktısı, düğüm ve metrik başına özet üreten bir dosyadır. Doğrudan hedef dosyanın üzerine yazmak bir risk taşır: yazma sırasında süreç sonlanırsa dosya yarım kalır ve onu okuyan program bozuk veri görür.
Çözüm, geçici bir ada yazıp tamamlandığında yerine koymaktır. Aynı dosya sistemi içinde ad değiştirme işlemi bölünmez: okuyucular ya eski dosyanın tamamını ya yeni dosyanın tamamını görür.
// ozetle.mjs import { readFile, writeFile, rename, stat } from 'node:fs/promises'; async function ozetle(kaynak, hedef) { const metin = await readFile(kaynak, 'utf8'); const kova = new Map(); for (const satir of metin.split('\n')) { if (satir.trim() === '') continue; const { dugum, metrik, deger } = JSON.parse(satir); const anahtar = `${dugum}/${metrik}`; const kayit = kova.get(anahtar) ?? { sayi: 0, toplam: 0 }; kayit.sayi += 1; kayit.toplam += deger; kova.set(anahtar, kayit); } const ozet = [...kova].map(([anahtar, { sayi, toplam }]) => ({ anahtar, sayi, ortalama: Number((toplam / sayi).toFixed(2)), })); // Yarim yazilmis dosya birakmamak icin once gecici ada yaz, sonra yerine koy const gecici = `${hedef}.gecici`; await writeFile(gecici, JSON.stringify(ozet, null, 2) + '\n'); await rename(gecici, hedef); const bilgi = await stat(hedef); return { kayitSayisi: ozet.length, bayt: bilgi.size }; } const sonuc = await ozetle('olcumler.ndjson', 'ozet.json'); console.log(`${sonuc.kayitSayisi} ozet kaydi, ${sonuc.bayt} bayt yazildi`);
node ozetle.mjs
6 ozet kaydi, 477 bayt yazildi
Geçici dosya hedefle aynı dizinde oluşturulmalıdır. Farklı dosya sistemlerine yayılan bir ad değiştirme işlemi bölünmez değildir; çekirdek onu kopyala–sil olarak yürütür ve güvence ortadan kalkar.
Bu yaklaşımın sınırı da vardır: veri ancak süreç bittikten sonra değil, ad değiştirme işleminden sonra görünür hâle gelir, ama diske gerçekten yazıldığı an işletim sistemi önbelleğine bağlıdır. Elektrik kesintisine karşı dayanıklılık gerekiyorsa dosya tanıtıcısı üzerinden eşitleme çağrısı yapılır; sıradan hizmetlerde bu ek maliyet genellikle üstlenilmez.
Üretilen ozet.json dosyası kurs boyunca hizmetin sunacağı içeriktir; sonraki
derslerde aynı özet, önce akışlarla, sonra HTTP üzerinden üretilecek.
Özet
- Dosya sistemi işlemleri üç arayüzle sunulur: eşzamanlı, geri çağrılı ve söz tabanlı. Eşzamanlı biçim yalnızca olay döngüsü iş görmüyorken kullanılır.
- Dosya işlemlerinin eşzamansızlığı iş parçacığı havuzundan gelir; havuz sabit genişliktedir ve aşırı eşzamanlı istek sıraya girer.
- Hatalar
codealanıyla ayırt edilir; ileti metnine dayanan dallanma kırılgandır. mkdirçağrısındaki özyineli seçenek işlemi yinelemeye dayanıklı kılar;readdirçağrısının döndürdüğü sıra dosya sistemine bağlıdır.- Bir çıktı dosyası, geçici ada yazılıp aynı dizin içinde ad değiştirilerek güncellendiğinde okuyucular hiçbir zaman yarım içerik görmez.
Sonraki Adım
Bu dersteki bütün yollar çalışma dizinine göre yazıldı; program başka bir dizinden çağrıldığında dosyayı bulamaz. Ayrıca ayırıcı karakteri elle yazmak, aracı tek bir platforma bağlar. Sonraki ders yol kurmayı ele alır: parçaları birleştirmenin, göreli yolu mutlak yapmanın ve bir kullanıcı girdisinin kök dizin dışına çıkmadığını doğrulamanın kuralları.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.