Ders 03 / 17
Arayüzlerden Veri Toplama
Aynı arayüzden üç toplama kararı üç farklı küme çıkarıyor ve üç yanıt da tamam dönüyor: dar kapsamlı belirteç 1.400 kayıt toplar ve geçmiş dönemin 477 satırını hiç istemeden dışarıda bırakır, konum tabanlı sayfalama tam 1.877 satır toplar ama 35 kaydı hiç almaz ve 35 kaydı iki kez alır, imleç tabanlı sayfalama 1.877 kaydın tamamını bir kez toplar. Konum tabanlı kararın genel toplamı 38.690,7 m³ ile birebir tutar çünkü atlanan ve yinelenen kayıtlar aynı okuma değerini taşır; hasar yalnız bölge kırılımında 70,5 m³ sapma olarak görünür. Hız sınırı altında sınır yanıtını boş sayfa sayan sıra 600 kayıtta durup 1.277 kaydı kaybeder; sınıra uyan sıra aynı kümeyi 38 istek ve 3 bekleme birimiyle eksiksiz toplar, sayfa boyu 200'e çıkarıldığında bekleme sıfıra iner.
İçindekiler
Dosya da veritabanı da veriyi bir kerede verdi. Dosya okunduğunda kaç satır olduğu belliydi, sorgu koştuğunda küme elde duruyordu. Bir arayüzden veri toplarken bu güvence yoktur: kayıtlar parça parça gelir, her parçanın sonrakine nasıl bağlandığını çağıran taraf takip eder, kaynak çağrı sıklığına bir sınır koyar ve hangi kayıtların görülebileceğini belirteç belirler.
Dersin ölçüsü şudur: kaynaktaki satırın kaçı toplanan kümeye girdi. Arayüz tasarımı, sayfalama ve hız sınırı sunan taraf açısından önkoşuldur ve burada tekrarlanmaz; ölçülen tek şey tüketen tarafın kaç kaydı aldığı, kaç kaydı iki kez aldığı ve kaç kaydı hiç istemediğidir. Teknik sınırdan önce bir sınır daha vardır: bir kaynaktan veri toplamak, kaynağın kullanım koşulları ve açık bir erişim izni olmadan teknik olarak mümkün olsa bile hukuki ve mesleki bir sorundur; burada izin verilmiş bir toplama varsayılır.
- VT10. Arayüz kurgudur ve
nodeile modellenir. Ağ çağrısı yapılmaz: istek bir işlev çağrısı, yanıt bir nesnedir. Modeldir. - VT11. Kaynaktaki küme önceki dersin deposundaki okuma tablosunun aynısıdır — aynı tohum, 1.877 satır, 38.690,7 m³.
- VT12. Arayüz iki sıralama sunar.
okumasıralaması okuma değerine göre azalandır ve eşit değerlerin iç sırası her istekte yeniden kurulur;kimliksıralaması abone-dönem anahtarına göre artandır ve tekildir. Sayfa boyu en çok 200’dür, fazlası sessizce kırpılır. - VT13. Pencere kotası 12 istektir. Kota aşıldığında yanıt kayıt taşımaz ve durumu
sinirolur. Süre gerçek saatle ölçülmez; kaynağın pencere sayısı bekleme birimi olarak sayılır. - VT14. İki toplamın aynı sayılması için eşik 0,05 m³’tür, çünkü okumalar bir ondalıkla raporlanıyor ve altındaki bir fark yalnızca toplama sırasından gelebilir.
Arayüzün Verdiği Sayfa
// arayuz.mjs — bolgesel olcum aginin kurgu okuma arayuzu. MODELDIR: ag cagrisi // yapilmaz, "istek" bir islev cagrisi ve "yanit" bir nesnedir. Kayitlar onceki // dersin deposundaki 1877 okuma satirinin aynisidir: ayni tohum, ayni sayilar. export const TOHUM = 4711, BOY = 50, ENBUYUKBOY = 200, KOTA = 12; const uretec = (t) => { let s = t >>> 0; return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; }; // Okuma desi-m3 tamsayisi olarak tutulur; siralama anahtari tamsayi oldugu // icin kayan noktali degerler esitlik karsilastirmasina hic girmez. const r = uretec(TOHUM); export const KAYIT = []; for (let i = 0; i < 1400; i++) KAYIT.push({ abone_no: 10000 + i, donem: "2024-01", desi: 50 + Math.floor(r() * 321) }); for (let i = 0; i < 477; i++) KAYIT.push({ abone_no: 10000 + i, donem: "2024-02", desi: 50 + Math.floor(r() * 321) }); export const BOLGE = ["kuzey", "doğu", "merkez", "güney", "batı"]; export const kimlik = (k) => `${k.abone_no}-${k.donem}`; export const m3 = (k) => k.desi / 10; export const bolgesi = (k) => BOLGE[(k.abone_no - 10000) % 5]; // "okuma": okumaya gore azalan, esit degerlerin ic sirasi her istekte yeniden // kurulur. "kimlik": abone-donem anahtarina gore artan ve tekil. function sirali(sira, istekNo) { if (sira === "kimlik") return [...KAYIT].sort((a, b) => (kimlik(a) < kimlik(b) ? -1 : 1)); const g = uretec(TOHUM + istekNo * 7919); for (let i = 0; i < 5; i++) g(); return KAYIT.map((k) => ({ k, p: g() })).sort((a, b) => b.k.desi - a.k.desi || a.p - b.p) .map((x) => x.k); } // Kota bir pencerede kabul edilen istek sayisidir; asildiginda yanit kayit // tasimaz. Kapsam "gecmis" icermiyorsa kaynak yalnizca acik donemi verir. export function sunucu({ kota = KOTA } = {}) { let istek = 0, pencerede = 0, bekleme = 0; return { sayim: () => ({ istek, bekleme }), bekle() { pencerede = 0; bekleme++; }, iste({ kapsam = ["okuma"], sira = "kimlik", konum = 0, imlec = null, boy = BOY }) { istek++; pencerede++; if (pencerede > kota) return { durum: "sinir", kayitlar: [], sonraki: imlec }; const b = Math.min(boy, ENBUYUKBOY); let kume = sirali(sira, istek); if (!kapsam.includes("gecmis")) kume = kume.filter((k) => k.donem === "2024-01"); const bas = imlec === null ? konum : kume.findIndex((k) => kimlik(k) > imlec); const dilim = bas < 0 ? [] : kume.slice(bas, bas + b); return { durum: "tamam", toplam: kume.length, kayitlar: dilim.map((k) => ({ abone_no: k.abone_no, donem: k.donem, okuma_m3: m3(k), bolge: bolgesi(k) })), sonraki: dilim.length < b ? null : kimlik(dilim[dilim.length - 1]) }; }, }; } if (import.meta.url === `file://${process.argv[1]}`) { const s = sunucu({ kota: Infinity }), TAM = ["okuma", "gecmis"]; const oz = (y) => `durum ${y.durum}, toplam ${y.toplam}, sayfada ` + `${y.kayitlar.length} kayit, sonraki imlec ${y.sonraki}`; console.log(`tohum ${TOHUM}; ${KAYIT.length} okuma satiri, ` + `${KAYIT.reduce((t, k) => t + m3(k), 0).toFixed(1)} m3; boy ${BOY}, ust sinir ` + `${ENBUYUKBOY}, kota ${KOTA}`); console.log(`tam kapsam: ${oz(s.iste({ kapsam: TAM }))}`); console.log(`dar kapsam: ${oz(s.iste({ kapsam: ["okuma"] }))}`); const u = () => s.iste({ kapsam: TAM, sira: "okuma", konum: 49, boy: 3 }) .kayitlar.map((x) => `${x.abone_no}/${x.okuma_m3}`).join(" "); console.log(`"okuma" sirasinda 50. kayittan uc tane, iki ayri istekte:\n ${u()}\n ${u()}`); }
tohum 4711; 1877 okuma satiri, 38690.7 m3; boy 50, ust sinir 200, kota 12 tam kapsam: durum tamam, toplam 1877, sayfada 50 kayit, sonraki imlec 10024-2024-02 dar kapsam: durum tamam, toplam 1400, sayfada 50 kayit, sonraki imlec 10049-2024-01 "okuma" sirasinda 50. kayittan uc tane, iki ayri istekte: 11319/36 10452/36 10828/36 11319/36 10018/36 10230/36
İki belirteç arasındaki fark üçüncü satırdadır. Dar kapsamlı belirteç tamam durumu döndürüyor,
sayfa dolu geliyor, imleç ilerliyor ve hiçbir yerde bir yetki uyarısı yok. Tek işaret toplam
alanının 1.877 yerine 1.400 yazmasıdır ve bu sayının doğru olup olmadığı yanıtın içinden
anlaşılamaz. Kapsam hata üreten bir kısıt değildir; kaynağın verdiği kümeyi küçültür ve geri
kalanını hiç var olmamış gibi gösterir.
Son iki satır ikinci sorunu gösteriyor: aynı konumdan aynı üç kayıt iki kez istendi ve gelen kayıtlar farklı. Sıralama anahtarı okuma değeridir ve o değer tekil değildir — 1.877 kayıt en çok 321 farklı okuma değerine dağılır, yani her değeri ortalama altı kayıt paylaşır. Eşit değerlerin kendi aralarındaki sırası kaynağın kararına kalmıştır. Sabit görünen tek şey listedeki konumdur, ama o konumda hangi kaydın durduğu sabit değildir.
Aynı Arayüz, Üç Toplama Kararı
// topla.mjs — ayni arayuzden uc toplama karari uc farkli kume cikarir. Hicbir // karar hata almaz, butun yanitlar "tamam" doner. Olculen sey donen kayit // sayisi degil, kaynaktaki 1877 satirin kacinin toplanan kumeye girdigidir. import { KAYIT, kimlik, m3, bolgesi, BOLGE, sunucu, BOY } from "./arayuz.mjs"; const TAM = ["okuma", "gecmis"]; // Imlec tabanli sayfalama: her sayfa son anahtardan sonrasini ister, sinir // kaymaz. T1 bunu dar, T3 tam kapsamli belirtecle yapar. function imlecli(kapsam) { const s = sunucu({ kota: Infinity }), toplanan = []; let imlec = null; do { const y = s.iste({ kapsam, sira: "kimlik", imlec, boy: BOY }); toplanan.push(...y.kayitlar); imlec = y.sonraki; } while (imlec !== null); return { toplanan, istek: s.sayim().istek }; } // T2. Konum tabanli sayfalama, "okuma" sirasinda: anahtar tekil olmadigi icin // sayfa siniri her istekte baska bir kaydin uzerine duser. function konumla() { const s = sunucu({ kota: Infinity }), toplanan = []; let konum = 0, toplam = Infinity; while (konum < toplam) { const y = s.iste({ kapsam: TAM, sira: "okuma", konum, boy: BOY }); toplam = y.toplam; toplanan.push(...y.kayitlar); konum += BOY; } return { toplanan, istek: s.sayim().istek }; } // Esik 0.05 m3: okumalar bir ondalikla tutuluyor, altindaki bir fark yalnizca // toplama sirasindan gelebilir. const ESIK = 0.05, HEDEF = KAYIT.reduce((t, k) => t + m3(k), 0); const BHEDEF = new Map(BOLGE.map((b) => [b, KAYIT.filter((k) => bolgesi(k) === b).reduce((t, k) => t + m3(k), 0)])); const sapma = (kume) => { // bolge kiriliminin en buyuk sapmasi const t = new Map(BOLGE.map((b) => [b, 0])); for (const x of kume) t.set(x.bolge, t.get(x.bolge) + x.okuma_m3); return Math.max(...BOLGE.map((b) => Math.abs(t.get(b) - BHEDEF.get(b)))); }; const y1 = (h) => h.map((v, j) => (j ? String(v).padStart([0, 7, 7, 7, 8, 9, 11, 11, 9][j]) : String(v).padEnd(20))).join(""); console.log(y1(["toplama karari", "giren", "istek", "cikan", "farkli", "atlanan", "yinelenen", "toplam m3", "sapma m3"])); const sonuc = {}; for (const [ad, f] of [["T1 dar kapsam", () => imlecli(["okuma"])], ["T2 konum tabanli", konumla], ["T3 imlec tabanli", () => imlecli(TAM)]]) { const r = sonuc[ad] = f(), farkli = new Set(r.toplanan.map(kimlik)).size; r.farkli = farkli; r.toplam = r.toplanan.reduce((t, x) => t + x.okuma_m3, 0); console.log(y1([ad, KAYIT.length, r.istek, r.toplanan.length, farkli, KAYIT.length - farkli, r.toplanan.length - farkli, r.toplam.toFixed(1), sapma(r.toplanan).toFixed(1)])); } const t2 = sonuc["T2 konum tabanli"]; console.log(`\nT2 kaynagin bildirdigi ${t2.toplanan.length} satiri tutturdu, ama ` + `${KAYIT.length - t2.farkli} kayit hic gelmedi ve ${t2.toplanan.length - t2.farkli} kayit ` + `iki kez geldi; atlanan ve yinelenen ayni okuma degerini tasidigi icin genel toplam ` + `hedeften ${Math.abs(t2.toplam - HEDEF).toFixed(3)} m3 ayrisiyor (esik ${ESIK}), bolge ` + `kiriliminda ise esigin ${Math.round(sapma(t2.toplanan) / ESIK)} kati sapiyor`);
toplama karari giren istek cikan farkli atlanan yinelenen toplam m3 sapma m3 T1 dar kapsam 1877 29 1400 1400 477 0 28790.8 2016.2 T2 konum tabanli 1877 38 1877 1842 35 35 38690.7 70.5 T3 imlec tabanli 1877 38 1877 1877 0 0 38690.7 0.0 T2 kaynagin bildirdigi 1877 satiri tutturdu, ama 35 kayit hic gelmedi ve 35 kayit iki kez geldi; atlanan ve yinelenen ayni okuma degerini tasidigi icin genel toplam hedeften 0.000 m3 ayrisiyor (esik 0.05), bolge kiriliminda ise esigin 1410 kati sapiyor
Son sütun bölge kırılımındaki en büyük sapmadır.
T1 sayfalamayı doğru tüketiyor ve yine de kümenin dörtte birini kaçırıyor. Yirmi dokuz isteğin hepsi başarılı döndü, imleç düzgün ilerledi ve döngü doğru yerde durdu. Kayıp tamamen kapsamdan gelir: geçmiş dönemin 477 satırı hiç istenmedi ve toplam 28.790,8 m³, yani gerçeğin 0,74 katı oldu. Bir toplayıcının başarıyla bitmesi, hangi kümeyi görmeye yetkili olduğunu söylemez; kapsam sorusu toplamadan önce sorulur.
T2 dersin asıl bulgusudur. Toplanan satır sayısı 1.877, yani kaynağın bildirdiği sayıyla birebir aynı; “beklenen kadar kayıt geldi mi” diye bakan bir denetim burada geçer. Oysa kayıt kimlikleri sayılınca 35 kayıt hiç gelmemiş, 35 kayıt iki kez gelmiştir. Sayfa sınırı, eşit okumaların iç sırası her istekte yeniden kurulduğu için her seferinde başka bir kaydın üzerine düştü: biri önceki sayfanın sonuna geçip atlandı, bir başkası sonraki sayfanın başına düşüp yeniden geldi.
Hatanın ölçüde neden görünmediği, sessiz kaybın en temiz örneğidir. Atlanan ve yinelenen kayıtlar aynı eşit değer öbeğinden geldiği için aynı okuma değerini taşır: toplam 38.690,7 m³’te kalır ve fark 0,05 m³’lük eşiğin altındadır. Hasar yalnızca kimliğe bağlı bir kırılımda ortaya çıkar — bölge kırılımında en büyük sapma 70,5 m³, eşiğin 1.410 katıdır.
T3 aynı 1.877 kaydı 38 istekte bir kez toplar; farkı yaratan tek şey imlecin tekil ve kararlı bir anahtar üzerinde ilerlemesidir. Konum tabanlı sayfalama listenin istekler arasında sabit kaldığını varsayar ve bu varsayım tekil olmayan bir sıralama anahtarında da, toplama sürerken kayıt eklenen bir kaynakta da yanlıştır.
Hız Sınırına Uyan Sıra
// sira.mjs — hiz siniri altinda toplama sirasi. Sinir asma teknigi yoktur: // olculen sey sinira uyan siranin kac bekleme birimi harcadigi ve sinira // uymayan siranin kac kaydi disarida biraktigidir. Sure gercek saatle olculmez; // kaynagin pencere sayisi bir bekleme birimi olarak sayilir. import { KAYIT, kimlik, sunucu, BOY, ENBUYUKBOY, KOTA } from "./arayuz.mjs"; const TAM = ["okuma", "gecmis"]; // H1: sinir yanitini bos sayfa sayan sira. Kayit gelmeyince "kume bitti" der. function h1() { const s = sunucu(), toplanan = []; let imlec = null, y; do { y = s.iste({ kapsam: TAM, imlec, boy: BOY }); toplanan.push(...y.kayitlar); imlec = y.sonraki; } while (y.kayitlar.length > 0 && imlec !== null); return { toplanan, ...s.sayim() }; } // H2: sinira uyan sira. Pencere kotasini kendisi sayar ve dolunca bekler. function h2(boy) { const s = sunucu(), toplanan = []; let imlec = null, pencerede = 0; do { if (pencerede === KOTA) { s.bekle(); pencerede = 0; } const y = s.iste({ kapsam: TAM, imlec, boy }); pencerede++; toplanan.push(...y.kayitlar); imlec = y.sonraki; } while (imlec !== null); return { toplanan, ...s.sayim() }; } const y1 = (h) => h.map((v, j) => (j ? String(v).padStart([0, 7, 7, 9, 8, 9][j]) : String(v).padEnd(28))).join(""); console.log(y1(["sira", "giren", "istek", "bekleme", "cikan", "atlanan"])); for (const [ad, f] of [["H1 sinir yanitini bos sayan", h1], [`H2 sinira uyan, boy ${BOY}`, () => h2(BOY)], ["H2 sinira uyan, boy 20", () => h2(20)], ["H2 sinira uyan, boy 100", () => h2(100)], [`H2 sinira uyan, boy ${ENBUYUKBOY}`, () => h2(ENBUYUKBOY)]]) { const r = f(), farkli = new Set(r.toplanan.map(kimlik)).size; console.log(y1([ad, KAYIT.length, r.istek, r.bekleme, r.toplanan.length, KAYIT.length - farkli])); }
sira giren istek bekleme cikan atlanan H1 sinir yanitini bos sayan 1877 13 0 600 1277 H2 sinira uyan, boy 50 1877 38 3 1877 0 H2 sinira uyan, boy 20 1877 94 7 1877 0 H2 sinira uyan, boy 100 1877 19 1 1877 0 H2 sinira uyan, boy 200 1877 10 0 1877 0
H1 hız sınırının en pahalı yanlış anlaşılmasıdır. On üçüncü istek kotayı aştığı için kayıt taşımadan döndü, toplayıcı bunu “küme bitti” diye okudu ve süreç sıfır çıkış koduyla bitti: 600 kayıt toplandı, 1.277 kayıt dışarıda kaldı. Boş sayfa ile reddedilmiş istek birbirine benzer; ayırt edilmezse fark yalnızca satır sayısında görünür.
H2 aynı kümeyi eksiksiz topluyor ve bedeli 3 bekleme birimidir. Son üç satır bu bedelin nereden geldiğini gösteriyor: bekleme birimi, istek sayısının pencere kotasına bölümünden çıkar. Sayfa boyu 20 iken 94 istek ve 7 bekleme birimi gerekir; boy 200 iken 10 istek yeter ve toplama tek pencerede biter. Toplanan kayıt dört satırda da 1.877’dir. Sayfa boyu bir ayrıntı değil, doğrudan bir toplama süresi kararıdır ve kaynağın üst sınırı bu sürenin alt sınırını belirler. VT12’ye göre bu üst sınır aşılmaya çalışıldığında bir hata da gelmez: 400 kayıtlık sayfa isteyip konumunu 400 ilerleten bir sıra, kaynak 200 verdiği için her turda 200 kaydı sessizce atlar. Gelen sayfadaki kayıt sayısı sayılır, istenen boy varsayılmaz.
Özet
- Bir arayüzden toplanan kümede doğrulanacak sayı, dönen kayıt sayısı değil, kaynaktaki satırların kaçının kümeye bir kez girdiğidir.
- Dar kapsamlı belirteç hiçbir uyarı üretmeden 477 satırı dışarıda bırakır ve toplam gerçeğin 0,74 katına iner; kapsam sorusu toplamadan önce sorulur.
- Konum tabanlı sayfalama tekil olmayan bir anahtarda 35 kaydı atlar ve 35 kaydı iki kez toplar; satır sayısı ve genel toplam doğru görünür, hasar bölge kırılımında 70,5 m³ sapma olarak çıkar.
- Tekil ve kararlı bir anahtar üzerinde ilerleyen imleç aynı kümeyi 1.877 kayıt, sıfır atlanan ve sıfır yinelenen olarak toplar.
- Hız sınırı yanıtını boş sayfa sayan sıra 600 kayıtta durup 1.277 kaydı kaybeder; sınıra uyan sıra 38 istek ve 3 bekleme birimi harcar ve sayfa boyu 200’e çıkarıldığında bekleme sıfıra iner.
Sonraki Adım
Bu dersin kaynağı verisini alan için tasarlanmış bir arayüzden verdi: alanların adı belliydi, sayfa sınırı ilan edilmişti, kayıt sayısı yanıtın içinde yazıyordu. Verinin bir kısmı ise böyle bir arayüzün arkasında değil, insan gözü için düzenlenmiş bir sayfanın yapısının içinde durur. Orada alan adı yoktur; yalnızca konum ve görünüm işaretleri vardır ve bunlar bir sözleşme değildir. Sonraki ders kurgu bir yayın sayfasından ayrıştırma yapar ve tek şeyi ölçer: yapı değiştiğinde kaç seçici kırılır, kaç satır düşer, kaç satır yanlış alandan okunmuş olarak hayatta kalır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.