Ders 07 / 16
Denetim Kaydı
İzlenebilirlik gereksiniminin dört ölçüsü: hangi karar noktasının kaydedildiği ve buna bağlı olarak kaç sorunun yanıtlanabildiği, zincirleme özetin yakaladığı değişiklik ile yakalayamadığı kuyruk kesme, kaydın kendi taşıdığı kişisel veri ve saklama süresinin hacme etkisi.
İçindekiler
Önceki iki ders iki yapılandırma kararını ölçtü ve ikisi de bir soruyu ortada bıraktı: kim, ne zaman. Uygulamanın kendi kayıtları isteği ve hatayı yazar; hangi görevlinin hangi üyenin cezasını sildiğini yazmaz. Denetim kaydı bu boşluğu doldurur ve Vaka Çalışmaları kursundaki denetim izinden amacıyla ayrılır: orada iz, durumu yeniden kurmaya yarayan bir veri yapısı kararıydı; burada kayıt, bir kararın sorumlusunu sonradan gösterebilmek için tutulur ve bu yüzden kendisinin de değiştirilmemiş olması gerekir.
İzlenebilirlik dört soruyla ölçülür. Hangi olay kaydediliyor — yani sistemin karar noktalarının kaçı yazılıyor. Kayıt sonradan değiştirilmemiş olduğunu kanıtlayabiliyor mu. Kaydın kendisi ne taşıyor. Ve ne kadar süre saklanıyor. Dördü de yapılandırma kararıdır ve yanlış ayarlandıklarında sistem hata vermez.
Karar Noktaları
BG16 — ödünç sisteminin on iki karar noktası vardır ve günlük hacimleri aşağıdaki tabloda
durur; toplamı 3.012 olaydır. BG17 — her kayıt dört alan taşır: tür, aktör, hedef, sonuç. Depo
node:sqlite üzerindedir ve her kaydın özeti bir öncekinin özetini kapsar.
// kayit.mjs — denetim kaydi deposu: karar noktalari, zincirleme ozet, dogrulama import { createHash } from 'node:crypto'; import { DatabaseSync } from 'node:sqlite'; export const TOHUM = 4231; let durum = TOHUM; const rast = () => (durum = (durum * 1103515245 + 12345) % 2147483648) / 2147483648; const sec = (n) => Math.floor(rast() * n); // Karar noktalari: [tur, gunluk sayi, durum degistirir mi, aktor sinifi, hedef sinifi] export const TURLER = [ ['GIRIS', 1200, false, 'uye', 'kendi'], ['GIRIS_RET', 60, false, 'uye', 'kendi'], ['GORUNTULE', 900, false, 'gorevli', 'uye'], ['ODUNC', 400, true, 'uye', 'katalog'], ['IADE', 380, true, 'uye', 'katalog'], ['KATALOG_DUZENLE', 45, true, 'gorevli', 'katalog'], ['CEZA_SIL', 12, true, 'gorevli', 'uye'], ['DISA_AKTARIM', 4, false, 'gorevli', 'uye'], ['YETKI_DEGISIKLIGI', 5, true, 'gorevli', 'uye'], ['UYE_SIL', 3, true, 'gorevli', 'uye'], ['DAGITIM', 2, true, 'gorevli', 'sistem'], ['DONDURME', 1, true, 'gorevli', 'sistem'], ]; export const UYE = 40, GOREVLI = 6, KATALOG = 60; export function gunuUret() { const olaylar = []; for (const [tur, sayi, yazar, aktorSinifi, hedefSinifi] of TURLER) { for (let i = 0; i < sayi; i++) { const aktor = aktorSinifi === 'uye' ? `U-${1 + sec(UYE)}` : `G-${1 + sec(GOREVLI)}`; const hedef = hedefSinifi === 'kendi' ? aktor : hedefSinifi === 'uye' ? `U-${1 + sec(UYE)}` : hedefSinifi === 'katalog' ? `K-${800 + sec(KATALOG)}` : 'sistem'; olaylar.push({ tur, aktor, hedef, yazar, sira: rast(), sonuc: tur === 'GIRIS_RET' ? 'ret' : 'kabul' }); } } return olaylar.sort((a, b) => a.sira - b.sira); } // Zincirleme ozet: her kaydin ozeti bir oncekinin ozetini de kapsar. export const ozetle = (sira, once, govde) => createHash('sha256').update(`${sira}|${once}|${govde}`).digest('hex').slice(0, 16); export function depoAc(olaylar, takma = null) { const db = new DatabaseSync(':memory:'); db.exec('create table kayit (sira integer primary key, tur text, aktor text, hedef text, sonuc text, govde text, ozet text)'); const ekle = db.prepare('insert into kayit values (?, ?, ?, ?, ?, ?, ?)'); let once = 'baslangic'; olaylar.forEach((o, i) => { const [aktor, hedef] = takma ? [takma(o.aktor), takma(o.hedef)] : [o.aktor, o.hedef]; const govde = JSON.stringify({ tur: o.tur, aktor, hedef, sonuc: o.sonuc }); once = ozetle(i + 1, once, govde); ekle.run(i + 1, o.tur, aktor, hedef, o.sonuc, govde, once); }); return db; } // Dogrulama zinciri bastan yurur; ilk uyusmazligin sirasini dondurur, yoksa 0. export function dogrula(db) { let once = 'baslangic'; for (const k of db.prepare('select * from kayit order by sira').all()) { once = ozetle(k.sira, once, k.govde); if (once !== k.ozet) return k.sira; } return 0; }
Üç Kayıt Politikası
Kaydedilecek olayları seçmenin üç yaygın biçimi vardır ve üçü de savunulabilir görünür: yalnız durumu değiştiren işlemleri yazmak, yalnız reddedilen yetki isteklerini yazmak, bütün karar noktalarını yazmak. Aralarındaki farkı ölçmenin yolu hacim değil, yanıtlanabilen soru sayısıdır. Aşağıdaki koşum sekiz hesap sorabilme sorusunu her üç kayda da gerçek sorgu olarak sorar; soruların hedefleri kaydın kendisinden okunur.
// olcum.mjs — uc kayit politikasi, zincirin yakaladigi ve yakalamadigi, kaydin kendi gizliligi import { createHash } from 'node:crypto'; import { gunuUret, depoAc, dogrula, ozetle, TURLER, TOHUM } from './kayit.mjs'; const olaylar = gunuUret(); const tam = depoAc(olaylar); const tek = (s) => tam.prepare(s).get(); const uyeCeza = tek("select hedef from kayit where tur='CEZA_SIL'").hedef; const silinen = tek("select hedef from kayit where tur='UYE_SIL'").hedef; const katalog = tek("select hedef from kayit where tur='KATALOG_DUZENLE'").hedef; const gorevli = tek("select aktor from kayit where tur='GORUNTULE' group by aktor order by count(*) desc").aktor; const sorular = [ [`${uyeCeza} uyesinin cezasini kim sildi`, `select aktor from kayit where tur='CEZA_SIL' and hedef='${uyeCeza}'`], [`${gorevli} kac ayri uyenin kaydina bakti`, `select distinct hedef from kayit where tur='GORUNTULE' and aktor='${gorevli}'`], [`${katalog} kaydini kim duzenledi`, `select aktor from kayit where tur='KATALOG_DUZENLE' and hedef='${katalog}'`], ['hangi hesap dortten cok basarisiz giris aldi', "select hedef from kayit where tur='GIRIS_RET' group by hedef having count(*) > 4"], ['imza anahtarini kim dondurdu', "select aktor from kayit where tur='DONDURME'"], ['dagitimi kim baslatti', "select aktor from kayit where tur='DAGITIM'"], [`${uyeCeza} uyesinin verisine kimler bakti`, `select distinct aktor from kayit where hedef='${uyeCeza}' and tur in ('GORUNTULE','DISA_AKTARIM')`], [`silinen ${silinen} uyesinin kaydina kim eristi`, `select distinct aktor from kayit where hedef='${silinen}' and tur='GORUNTULE'`], ]; const politikalar = { 'yalniz yazma': (o) => o.yazar, 'yalniz yetki reddi': (o) => o.sonuc === 'ret', 'butun kararlar': () => true, }; const s = (x, n) => String(x).padStart(n); console.log(`tohum ${TOHUM}: ${TURLER.length} karar noktasi, ${olaylar.length} olay/gun`); console.log(`${'politika'.padEnd(20)}${s('kayit', 7)}${s('bayt', 8)}${s('yanitlanan soru', 17)}${s('yanitsiz soru', 18)}`); const yanit = {}; for (const [ad, suzgec] of Object.entries(politikalar)) { const db = depoAc(olaylar.filter(suzgec)); const bayt = db.prepare('select sum(length(govde)) as b, count(*) as n from kayit').get(); yanit[ad] = sorular.map(([, sql]) => db.prepare(sql).all().length > 0); const say = yanit[ad].filter(Boolean).length; console.log(`${ad.padEnd(20)}${s(bayt.n, 7)}${s(bayt.b, 8)}${s(`${say}/8`, 17)}` + `${s(yanit[ad].map((v, i) => (v ? '' : i + 1)).filter(Boolean).join(',') || '-', 18)}`); } console.log(sorular.map(([ad], i) => ` ${i + 1}. ${ad}`).join('\n')); // Zincir: govdeyi degistirmek, ozeti de tazelemek, kuyrugu kesmek. const bozuk = depoAc(olaylar); const kurban = 500; const eski = bozuk.prepare('select govde, ozet from kayit where sira=?').get(kurban); const yeni = eski.govde.replace('"sonuc":"kabul"', '"sonuc":"ret"'); bozuk.prepare('update kayit set govde=? where sira=?').run(yeni, kurban); console.log(`\nzincir temizken dogrulama: ${dogrula(tam)}`); console.log(`${kurban}. kaydin govdesi degistirildi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`); const onceki = bozuk.prepare('select ozet from kayit where sira=?').get(kurban - 1).ozet; bozuk.prepare('update kayit set ozet=? where sira=?').run(ozetle(kurban, onceki, yeni), kurban); console.log(`ozet de tazelendi -> ilk uyusmazlik ${dogrula(bozuk)}. sirada`); const kesik = depoAc(olaylar); kesik.prepare('delete from kayit where sira > ?').run(olaylar.length - 25); console.log(`son 25 kayit silindi -> dogrulama ${dogrula(kesik)} (0 = uyusmazlik yok)`); for (const capa of [Infinity, 500, 100, 10]) { const sessiz = capa === Infinity ? olaylar.length : olaylar.length % capa; const ad = capa === Infinity ? 'capa yok ' : `capa her ${s(capa, 3)} kayitta`; console.log(`${ad} -> bu kosumda ${s(sessiz, 4)} kayit sessizce silinebilir, en kotu ${capa === Infinity ? sessiz : capa - 1}`); } // Kaydin kendi gizliligi. const kisisel = tam.prepare("select count(*) as n from kayit where aktor like 'U-%' or hedef like 'U-%'").get().n; const gecmis = tam.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all(); const tuzla = (tuz) => (d) => (d.startsWith('U-') ? `T-${createHash('sha256').update(tuz + d).digest('hex').slice(0, 8)}` : d); const takma = depoAc(olaylar, tuzla('donem-1')); const takmaGecmis = takma.prepare("select aktor, count(*) as n from kayit where tur='ODUNC' group by aktor order by n desc").all(); const donem2 = new Set(gecmis.map((g) => tuzla('donem-2')(g.aktor))); const ortak = takmaGecmis.filter((g) => donem2.has(g.aktor)).length; console.log(`\nkisi tanimlayan kayit ${kisisel}/${olaylar.length} (%${(100 * kisisel / olaylar.length).toFixed(1)})`); console.log(`odunc gecmisi kurulabilen uye: acik kimlikle ${gecmis.length}, en uzun liste ${gecmis[0].n} kayit`); console.log(`ayni olcum takma adli kayitta: ${takmaGecmis.length} uye, en uzun liste ${takmaGecmis[0].n} kayit`); console.log(`donem tuzu degistiginde iki donemin ortak takma adi: ${ortak}`); const bayt = tam.prepare('select sum(length(govde)) as b from kayit').get().b; for (const gun of [30, 180, 3650]) { console.log(`saklama ${s(gun, 4)} gun -> ${s(olaylar.length * gun, 9)} kayit, ` + `${s((bayt * gun / 1048576).toFixed(1), 7)} MiB, kisi-gun ${gecmis.length * gun}`); }
tohum 4231: 12 karar noktasi, 3012 olay/gun politika kayit bayt yanitlanan soru yanitsiz soru yalniz yazma 848 52494 4/8 2,4,7,8 yalniz yetki reddi 60 3752 1/8 1,2,3,5,6,7,8 butun kararlar 3012 186509 8/8 - 1. U-12 uyesinin cezasini kim sildi 2. G-1 kac ayri uyenin kaydina bakti 3. K-834 kaydini kim duzenledi 4. hangi hesap dortten cok basarisiz giris aldi 5. imza anahtarini kim dondurdu 6. dagitimi kim baslatti 7. U-12 uyesinin verisine kimler bakti 8. silinen U-27 uyesinin kaydina kim eristi zincir temizken dogrulama: 0 500. kaydin govdesi degistirildi -> ilk uyusmazlik 500. sirada ozet de tazelendi -> ilk uyusmazlik 501. sirada son 25 kayit silindi -> dogrulama 0 (0 = uyusmazlik yok) capa yok -> bu kosumda 3012 kayit sessizce silinebilir, en kotu 3012 capa her 500 kayitta -> bu kosumda 12 kayit sessizce silinebilir, en kotu 499 capa her 100 kayitta -> bu kosumda 12 kayit sessizce silinebilir, en kotu 99 capa her 10 kayitta -> bu kosumda 2 kayit sessizce silinebilir, en kotu 9 kisi tanimlayan kayit 2964/3012 (%98.4) odunc gecmisi kurulabilen uye: acik kimlikle 40, en uzun liste 18 kayit ayni olcum takma adli kayitta: 40 uye, en uzun liste 18 kayit donem tuzu degistiginde iki donemin ortak takma adi: 0 saklama 30 gun -> 90360 kayit, 5.3 MiB, kisi-gun 1200 saklama 180 gun -> 542160 kayit, 32.0 MiB, kisi-gun 7200 saklama 3650 gun -> 10993800 kayit, 649.2 MiB, kisi-gun 146000
Sekiz Sorunun Yanıtı
Yalnız yazma işlemlerini kaydeden politika günlük 3.012 olayın 848’ini tutar ve sekiz sorunun dördünü yanıtlar. Yanıtsız kalan dördü aynı sınıftandır: hepsi bir okumayı sorar. Bir görevlinin kaç üyenin kaydına baktığı, bir üyenin verisine kimlerin eriştiği, silinen bir üyenin kaydına silinmeden önce kimin girdiği — bunların hiçbiri durumu değiştirmez, dolayısıyla hiçbiri yazılmaz. Kişisel veriye erişimin izlenebilirliği sorulduğunda bu politika yapısal olarak yanıtsızdır ve yapılandırmasında bunu belirten bir işaret yoktur.
Yalnız reddedilen istekleri kaydeden politika 60 kayıtla en ucuzudur ve sekizde bir yanıt verir. Yanıtladığı tek soru başarısız girişlerle ilgilidir. Bu politikanın sessiz varsayımı şudur: kötüye kullanım reddedilmiş isteklerde görünür. Oysa yetkisi olan bir görevlinin dokuz yüz kayıt görüntülemesi hiç reddedilmez ve bu kayıtta hiç görünmez.
Bütün karar noktalarını yazmak sekizde sekiz verir ve bedeli yaklaşık dört katı hacimdir (186.509 bayta karşılık 52.494). Hacim farkının nereden geldiği önemlidir: 3.012 olayın 2.100’ü giriş ve görüntülemedir; yani izlenebilirliğin pahalı kısmı tam olarak yalnız yazma politikasının atladığı kısımdır. Karar noktası saymak bu yüzden bir hacim tartışmasından önce gelir — hangi soruların yanıtlanabilir olacağı, hangi olayın yazıldığıyla belirlenir.
Zincirin Yakalamadığı
Kayıt, sorumluluğu göstermek için tutuluyorsa sonradan değiştirilmediğinin gösterilebilmesi
gerekir. Zincirleme özet bunu kısmen verir. Beş yüzüncü kaydın gövdesindeki sonuç alanı kabul
yerine ret yapıldığında doğrulama tam o sırada durur. Değiştiren kişi o kaydın özetini de
yeniden hesaplarsa uyuşmazlık bir sonraki kayda kayar (501): her özet bir öncekini kapsadığı için
tek bir kaydı düzeltmek ondan sonraki bütün kayıtları yeniden yazmayı gerektirir.
Kuyruk kesme farklıdır. Son yirmi beş kayıt silindiğinde doğrulama sıfır döndürür — yani hiçbir uyuşmazlık yoktur. Kalan zincir kusursuzdur, çünkü zincir yalnızca kendisinin iç tutarlılığını gösterir; kaç kaydın var olması gerektiğini bilmez. Bir kaydın silindiğini anlamanın tek yolu, zincirin bir noktadaki özetini kaydın dışına, başka bir sorumluluğun altına yazmaktır. Çapa dönemi bu boşluğun ölçüsüdür: çapa yoksa günün 3.012 kaydının tamamı sessizce silinebilir, beş yüzde bir çapayla en kötü 499, onda bir çapayla en kötü 9 kayıt. Yanlış yapılandırma burada kaydın olmamasıdır ve olmayan kayıt hiçbir doğrulamayı kırmaz.
Kaydın Kendi Riski
Bu günün kayıtlarının %98,4’ü bir kişiyi tanımlıyor. Denetim kaydı, tanımı gereği kimin ne yaptığını yazar; dolayısıyla toplandığı anda sistemin en yoğun kişisel veri kümesi hâline gelir. Ödünç kayıtlarından kırk üyenin tamamının okuma geçmişi kurulabiliyor, en uzun liste on sekiz kayıt. Kütüphane sisteminde bu, üyelerin ne okuduğunun listesidir ve ödünç servisinin kendi veritabanında iade sonrası silinmiş olsa bile denetim kaydında durmaya devam eder.
Üye kimliğini tuzlu bir özetle takma adlandırmak bu ölçüyü hiç değiştirmiyor: yine kırk üye, yine on sekiz kayıtlık en uzun liste. Takma ad kimliği gizler, bağlantıyı gizlemez; aynı takma ad aynı kişinin bütün kayıtlarını birbirine bağlamaya devam eder. Bağlantıyı kesen tek ayar tuzun dönem başına değişmesidir ve ölçüsü son satırdadır: iki dönemin ortak takma adı sıfırdır. Bedeli de oradadır — dönem tuzu atıldıktan sonra yedinci soru eski dönemler için sorulamaz, çünkü sorunun hedefi olan üyenin o dönemdeki takma adı yeniden hesaplanamaz.
Saklama süresi son ayardır ve iki ölçüyü birden büyütür. Otuz gün 5,3 MiB ve 1.200 kişi-gün eder; on yıl 649,2 MiB ve 146.000 kişi-gün. Hacim tarafı ucuzdur — on yıllık kayıt bir gigabaytın altındadır ve bu yüzden saklama süresini uzatmak hiçbir yerde bir uyarı üretmez. Artan şey maliyetin öteki yarısıdır: on yıl saklanan bir denetim kaydı, on yıllık okuma geçmişini bir arada tutan bir veri kümesidir ve sızdığında sızan şey isteklerin listesi değil, kişilerin listesidir.
Özet
- Denetim kaydı, denetim izinden amacıyla ayrılır: iz durumu yeniden kurar, kayıt bir kararın sorumlusunu gösterir ve bu yüzden kendi değiştirilemezliğini de kanıtlamak zorundadır.
- Yalnız yazma işlemlerini kaydetmek sekiz sorunun dördünü yanıtsız bıraktı ve yanıtsız kalanların hepsi bir okumayı soruyordu; yalnız reddedilen istekleri kaydetmek sekizde bir verdi.
- Bütün karar noktalarını yazmak sekizde sekiz verdi ve hacmi dört katına çıkardı; farkın tamamı giriş ile görüntüleme kayıtlarından geliyor.
- Zincirleme özet gövde değişikliğini 500. sırada, özeti tazelenmiş değişikliği 501. sırada yakaladı; son yirmi beş kaydın silinmesini hiç yakalamadı. Çapa dönemi bu sessiz boşluğun ölçüsüdür: çapasız 3.012, beş yüzde bir çapayla en kötü 499 kayıt.
- Kayıtların %98,4’ü bir kişiyi tanımlıyor ve kırk üyenin tamamının okuma geçmişi kurulabiliyor; takma adlandırma bu sayıyı değiştirmedi, yalnız dönemsel tuz iki dönem arasındaki bağı sıfırladı.
- Saklama süresini on yıla çıkarmak 649,2 MiB ve 146.000 kişi-gün eder; hacim uyarı üretmeyecek kadar küçüktür, kişi-gün ise saklama kararının asıl bedelidir.
Sonraki Adım
Denetim kaydı sonradan sorar ve iyi yanıt verir, ama hiçbir şeyi durdurmaz. Bu günün kaydında bin iki yüz giriş, altmış başarısız giriş ve dokuz yüz görüntüleme var; kayıt bunların kimden geldiğini yazar, birinin bu sayıya ulaşmaması gerektiğini söylemez. Kırk üyelik bir sistemde altmış başarısız girişin tek bir hesapta toplanması ile kırk hesaba dağılması aynı kayıt hacmini üretir ve ikisi bambaşka şeylerdir. Sonraki ders bu ayrımı ele alır: sınırın kime uygulandığını, yanıtın kendisinin bir hesabın var olup olmadığını nasıl ele verdiğini ve kilitlemenin doğru hesabı ne kadar dışarıda bıraktığını sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.