İçeriğe geç
academia.sh

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 code alanı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat