İçeriğe geç
academia.sh

Ders 08 / 21

Yalıtım Düzeylerinin Uygulama Etkisi

Okuma anomalilerinin uygulamadan görünümü: aynı sorgunun iki kez çalıştırılmasıyla tekrarlanamayan okuma ve hayalet satırın ölçülmesi, iki toplama sorgusu arasındaki değişmezin binlerce örnekte ihlal edilmesi ve yalıtımın bedeli.

İçindekiler

Önceki ders iki bağlantının aynı satıra yazmak istemesini ve birinin ötekini beklemesini ölçtü. Yazma–yazma çakışması görünür bir olaydır: biri bekler, sonra yazar. Okuma tarafında olanlar sessizdir.

Bir işlem sürerken başka bir bağlantı aynı satırları okursa ne görür? Değişikliğin öncesini mi, sonrasını mı, yoksa sorgudan sorguya değişen bir karışımı mı? Yanıt yalıtım düzeyine bağlıdır. İleri SQL kursunda düzeyler ve izin verdikleri anomaliler tanımlandı; bu ders aynı anomalileri uygulama kodundan ölçer ve düzeyin seçilmesinin uygulamada ne anlama geldiğini gösterir.

Düzenek

Şema küçüktür: bir üye, beş kitap, ödünç kayıtları.

# kur.sh — dersin veritabanini ve isaret dosyalarini sifirlar
rm -f kutuphane.db kutuphane.db-wal kutuphane.db-shm okundu yazildi
sqlite3 kutuphane.db >/dev/null <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE uye (uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, odunc_siniri INTEGER NOT NULL);
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
                    durumu TEXT NOT NULL CHECK (durumu IN ('rafta','odunc')));
CREATE TABLE odunc (odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL,
                    uye_id INTEGER NOT NULL, iade_tarihi TEXT);
INSERT INTO uye VALUES (4,'Emre',3);
INSERT INTO kitap VALUES (1,'Körlük','rafta'),(2,'Tutunamayanlar','rafta'),
  (3,'Kum Kitabı','rafta'),(4,'Yaban','rafta'),(5,'Sessiz Ev','rafta');
SQL

İki süreç birbirini işaret dosyalarıyla bekler. Bu, zamanlamayı rastlantıya bırakmadan belirli bir sıralamayı üretmenin en küçük yoludur.

İki Anomali, Tek Senaryo

Okuyucu aynı iki sorguyu iki kez çalıştırır: bir satırın değerini ve bir bağıntının satır sayısını. Arada yazıcı devreye girer.

// okuyucu.mjs — ayni iki sorguyu iki kez calistirir; kip: otomatik | islem
import { DatabaseSync } from "node:sqlite";
import { writeFileSync, existsSync } from "node:fs";

const kip = process.argv[2];
const db = new DatabaseSync("kutuphane.db");
const oku = () => ({
  sinir: db.prepare("SELECT odunc_siniri AS s FROM uye WHERE uye_id = 4").get().s,
  kitap: db.prepare("SELECT count(*) AS n FROM kitap").get().n,
});

if (kip === "islem") db.exec("BEGIN");
const ilk = oku();
writeFileSync("okundu", "");
while (!existsSync("yazildi")) { /* yazicinin kesinlestirmesini bekle */ }
const ikinci = oku();
if (kip === "islem") db.exec("COMMIT");

console.log(`kip=${kip}`);
console.log(`  ilk okuma   : sinir=${ilk.sinir}  kitap=${ilk.kitap}`);
console.log(`  ikinci okuma: sinir=${ikinci.sinir}  kitap=${ikinci.kitap}`);
console.log(`  tekrarlanamayan okuma: ${ilk.sinir !== ikinci.sinir}`);
console.log(`  hayalet satir        : ${ilk.kitap !== ikinci.kitap}`);
// yazici.mjs — okuyucunun ilk okumasindan sonra guncelleyip yeni satir ekler
import { DatabaseSync } from "node:sqlite";
import { writeFileSync, existsSync } from "node:fs";

while (!existsSync("okundu")) { /* okuyucunun ilk okumasini bekle */ }
const db = new DatabaseSync("kutuphane.db");
db.exec("BEGIN IMMEDIATE");
db.prepare("UPDATE uye SET odunc_siniri = 5 WHERE uye_id = 4").run();
db.prepare("INSERT INTO kitap VALUES (6,'Anayurt Oteli','rafta')").run();
db.exec("COMMIT");
writeFileSync("yazildi", "");
console.log("yazici: sinir 5 yapildi, yeni kitap eklendi");

Aynı senaryo iki kipte çalıştırılır. otomatik kipinde okuyucu hiçbir işlem açmaz; her sorgu kendi başına yürür. islem kipinde iki okuma tek bir işlem sınırının içindedir.

# deney.sh — ayni senaryo iki kipte
for kip in otomatik islem; do
  sh kur.sh
  node yazici.mjs >/dev/null &
  yazici=$!
  node okuyucu.mjs $kip
  wait $yazici
  echo
done
sh deney.sh
kip=otomatik
  ilk okuma   : sinir=3  kitap=5
  ikinci okuma: sinir=5  kitap=6
  tekrarlanamayan okuma: true
  hayalet satir        : true

kip=islem
  ilk okuma   : sinir=3  kitap=5
  ikinci okuma: sinir=3  kitap=5
  tekrarlanamayan okuma: false
  hayalet satir        : false

İki anomali de ilk kipte göründü. odunc_siniri değeri 3’ten 5’e döndü: aynı satır, aynı sorgu, farklı sonuç. Buna tekrarlanamayan okuma denir. Kitap sayısı 5’ten 6’ya çıktı: sorgu aynı koşulu sordu, sonuç kümesine yeni bir satır girdi. Buna hayalet satır denir. İkisinin farkı, birincisinin var olan bir satırın değerinde, ikincisinin sonuç kümesinin üyeliğinde olmasıdır.

İkinci kipte iki okuma da ilk anın görüntüsünü verdi. İşlem açıldığında okuyucu bir anlık görüntü üzerinde çalışır ve o görüntü işlem boyunca sabit kalır.

Buradaki asıl gözlem uygulamayla ilgilidir. Kod iki kipte de aynı sorguları çalıştırdı; değişen tek şey bir BEGIN çağrısı oldu. Uygulamanın gördüğü yalıtım düzeyi, okumaların işlem sınırına alınıp alınmamasıyla belirlenir. Sorguları sınırsız çalıştıran bir servis, motorun sunduğu düzey ne olursa olsun, ardışık iki okuma arasında tutarlılık göremez.

Değişmezin İhlal Oranı

Yukarıdaki senaryo zamanlamayı elle kurdu. Gerçek yükte zamanlama kurulmaz, rastlantıya kalır. Bu durumda anomali “olur mu” değil “ne sıklıkla olur” sorusuna dönüşür.

Kütüphanede tutulması gereken bir değişmez var: ödünçte görünen kitap sayısı, iade edilmemiş ödünç kaydı sayısına eşit olmalı. Yazıcı bu değişmezi hiç bozmuyor; her adımı tek bir işlemde yapıyor.

// dolasan-yazici.mjs — kitaplari surekli odunc verip iade eder; her adim tek islem
import { DatabaseSync } from "node:sqlite";
import { writeFileSync, rmSync } from "node:fs";

const db = new DatabaseSync("kutuphane.db");
db.exec("PRAGMA busy_timeout = 5000");
const bitis = Date.now() + Number(process.argv[2] ?? 1500);
writeFileSync("yaziyor", "");
let adim = 0;
while (Date.now() < bitis) {
  const kitapId = (adim % 5) + 1;
  db.exec("BEGIN IMMEDIATE");
  const durum = db.prepare("SELECT durumu FROM kitap WHERE kitap_id = ?").get(kitapId).durumu;
  if (durum === "rafta") {
    db.prepare("UPDATE kitap SET durumu = 'odunc' WHERE kitap_id = ?").run(kitapId);
    db.prepare("INSERT INTO odunc (kitap_id, uye_id, iade_tarihi) VALUES (?,4,NULL)").run(kitapId);
  } else {
    db.prepare("UPDATE kitap SET durumu = 'rafta' WHERE kitap_id = ?").run(kitapId);
    db.prepare(`UPDATE odunc SET iade_tarihi = '2025-07-25'
                WHERE odunc_id = (SELECT max(odunc_id) FROM odunc
                                  WHERE kitap_id = ? AND iade_tarihi IS NULL)`).run(kitapId);
  }
  db.exec("COMMIT");
  adim += 1;
}
rmSync("yaziyor");
console.log(`yazici: ${adim} islem kesinlestirdi`);

Okuyucu değişmezi sürekli örnekler ve kaç örnekte bozuk gördüğünü sayar.

// tutarsizlik-say.mjs — iki toplama sorgusu arasindaki degismezi orneklerle sinar
import { DatabaseSync } from "node:sqlite";
import { existsSync } from "node:fs";

const kip = process.argv[2];
const db = new DatabaseSync("kutuphane.db");
db.exec("PRAGMA busy_timeout = 5000");
while (!existsSync("yaziyor")) { /* yazicinin baslamasini bekle */ }

let ornek = 0, ihlal = 0;
while (existsSync("yaziyor")) {
  if (kip === "islem") db.exec("BEGIN");
  const oduncteKitap = db.prepare("SELECT count(*) AS n FROM kitap WHERE durumu = 'odunc'").get().n;
  const acikKayit = db.prepare("SELECT count(*) AS n FROM odunc WHERE iade_tarihi IS NULL").get().n;
  if (kip === "islem") db.exec("COMMIT");
  ornek += 1;
  if (oduncteKitap !== acikKayit) ihlal += 1;
}
console.log(`kip=${kip}  ornek=${ornek}  degismez ihlali=${ihlal}`);
# degismez.sh — ayni degismez iki okuma kipinde ornekleniyor
for kip in otomatik islem; do
  sh kur.sh
  rm -f yaziyor
  node tutarsizlik-say.mjs $kip &
  okuyucu=$!
  node dolasan-yazici.mjs 1500
  wait $okuyucu
done
sh degismez.sh
yazici: 29188 islem kesinlestirdi
kip=otomatik  ornek=17277  degismez ihlali=3327
yazici: 23632 islem kesinlestirdi
kip=islem  ornek=17446  degismez ihlali=0

Sayılar makineye ve o andaki yüke göre değişir; bir buçuk saniyede kaç işlem kesinleştirileceği sabit değildir. Değişmeyen ayrımdır: sınırsız okumada on yedi bin örneğin üç binden çoğu değişmezi bozuk gördü, işlem içinde okumada hiçbiri bozuk görmedi.

Oran yaklaşık beşte bir. Bu, ilgili raporun beş çağrıdan birinde tutarsız bir çift sayı döndürmesi demektir. Hata kütüğüne hiçbir şey düşmez; sorgular başarılıdır, kısıt ihlali yoktur, yalnız iki sayı birbirini tutmaz.

Düzeylerin Uygulamadaki Karşılığı

İleri SQL kursunda dört anomali sıralanmıştı: kirli okuma, tekrarlanamayan okuma, hayalet satır ve seri hâle getirilebilirlik ihlalleri. Uygulama tarafında bunların karşılığı üç soruya iner.

Hangi sorgular birlikte anlamlıdır? Birlikte anlamlı olan sorgular aynı işlem sınırının içine alınır. Yukarıdaki iki toplama sorgusu birlikte anlamlıydı; ayrı çalıştıklarında ürettikleri çift anlamsız oldu.

Okunan değer üzerine karar veriliyor mu? Okunan değere bakıp bir yazma yapılıyorsa, okuma ile yazma arasındaki aralıkta o değerin değişmemesi gerekir. Ödünç sınırını okuyup “yer var” diyerek yazan bir servis, aradaki değişikliğe karşı korunmalıdır. Bu, yalıtım düzeyinin tek başına çözmediği bir sorundur ve sonraki dersin konusudur.

Bayat veriye tahammül var mı? Anlık görüntü tutarlıdır ama günceldir demek değildir. İşlem uzun sürerse, okuduğu veri kesinleşme anında eskimiş olabilir. Uzun raporlar için bu kabul edilir; para hareketi gibi işler için edilmez.

Yalıtımın bedeli de bu üçüncü noktada durur. Yüksek düzey, ya kilit tutarak ötekileri bekletir ya da eski sürümleri saklayarak depolama ve temizlik maliyeti üretir. İlişkisel Veritabanı Yönetimi kursunda çok sürümlü eşzamanlılık denetimi olarak geçen mekanizma bu ikinci yolu izler: eski sürümler bir okuyucu onlara ihtiyaç duyduğu sürece silinemez, uzun açık kalan okuma işlemleri temizliği geciktirir.

Özet

  • Aynı iki sorgu, sınırsız çalıştığında değeri değişmiş bir satır (tekrarlanamayan okuma) ve sonuç kümesine giren yeni bir satır (hayalet satır) gördü; işlem sınırı içinde ikisi de görünmedi.
  • Uygulamanın gördüğü yalıtım düzeyi, okumaların işlem sınırına alınıp alınmamasıyla belirlenir; kod aynı kalırken tek bir BEGIN çağrısı sonucu değiştirdi.
  • İki toplama sorgusu arasındaki değişmez, sınırsız okumada on yedi bin örneğin yaklaşık beşte birinde bozuk göründü; işlem içinde okumada hiç bozulmadı.
  • Bu tür tutarsızlık hata üretmez: sorgular başarılıdır, yalnız birlikte anlamsızdırlar.
  • Yüksek yalıtımın bedeli bekleme ya da eski sürümlerin saklanmasıdır; uzun açık okuma işlemleri temizliği geciktirir.

Sonraki Adım

Yalıtım, okunan verinin tutarlı olmasını sağlar; okunan değere dayanarak yapılan yazmanın doğruluğunu garanti etmez. İki üye aynı anda son kalan kopyayı ödünç almak isterse, ikisi de “kitap rafta” görür, ikisi de yazar ve biri ötekinin sonucunu geçersiz kılar. Bu çatışmayı çözmenin iki yolu vardır: değişikliği yazarken kaydın hâlâ okunduğu hâlde olduğunu sınamak ya da okuma anında kaydı kilitleyip ötekini beklemek. Sonraki ders iki yolu da kurar, çatışma üretir, kilit hatasını birebir gösterir ve hangi yükte hangisinin daha ucuz olduğunu ölçer.

İ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