Ders 15 / 20
Etkisiz İşlemler
Kurtarma sırasında yinelenen isteğin zararsızlaştırılması: yinelemenin üç kaynağının doksan saniyelik bir arıza penceresinde sayılması, tekillik anahtarının kapsamının yanlış seçildiğinde geçerli olayı bastırması ve anahtar defterinin K01 ölçeğinde tuttuğu kayıt, kapladığı bayt ile ikiye katladığı yazma satırı.
İçindekiler
Kurtarma ve Süreklilik konusu tatbikatla kapandı ve iki açık kalan sorun bıraktı. Devralma ile yeniden deneme birlikte çalıştığında aynı iş iki kez yürüdü: bir taşıyıcı bildirimi hem zaman aşımı sonrası yeniden gönderildi hem de devralan kopya tarafından kütükten yeniden sürüldü. İkincisi, yarıda kesilen iş akışları geri alınmadı. Bu ders birincisini ele alır.
Etkisizlik ve tekillik anahtarı (idempotency key) API Tasarımı ile Veri Erişim Katmanı kurslarında tanımlandı; aynı anahtarla gelen ikinci isteğin ilk yanıtı sunması, anahtarın etkiyle aynı işlemde saklanması ve teslim semantiğinin etkiyi değil teslimi çoğalttığı orada gösterildi. Burada tanım tekrarlanmaz. Bu dersin iki sorusu vardır: bir arıza penceresinde yinelemenin kaç yan etki ürettiği ve o yinelemeyi tutan defterin arızasız günde neye mal olduğu.
Yinelemenin Üç Kaynağı
Arıza senaryosu adıyla tanımlanır. Taşıyıcı bildirim ucu yavaşlıyor: uç yanıt vermeyi sürdürüyor ama yanıt süresi çağıranın zaman aşımı eşiğini geçiyor, dolayısıyla çağıran isteği başarısız sayıp yeniden deniyor. Yavaşlama sırasında bir kopya devralınırsa devralan taraf kütükteki son yazmaları yeniden sürer, kuyruk tarafı da onaylanmamış iletiyi yeniden teslim eder. Üç kaynak aynı anda çalışır.
DD1 — arıza penceresi 90 saniye, ayda 2 kez. Gerekçesi: bu süre K01’in 10 dakikalık kurtarma süresi varsayımının altındadır ve aylık kesinti bütçesinde kesinti olarak görünmez, ama zaman aşımı eşiğinin üstündedir. Duyarlılığı ayda 4 pencere ile verilir. DD2 — pencerede bir yazmanın yinelenme olasılığı 0,35, üç kaynağa bölünür: taşıyıcının yeniden denemesi 0,20, devralmada yeniden sürme 0,10, en az bir kez yeniden teslim 0,05. İkisi de varsayımdır ve K01’in tablosuna eklenmez.
Düzenek süreç içi bir modeldir: kuyruk, depo ve ağ yoktur, defter bir kümedir ve yan etkiler birer sayaçtır. Modelin taşıdığı tek özellik, aynı olayın birden çok kez işleyiciye ulaşmasıdır.
// etkisiz/akis.mjs — durum olayi akisinin ve tekillik defterinin surec ici modeli. // Gercek kuyruk, depo ya da ag yoktur: defter bir kume, yan etkiler birer sayactir. export const DIZI = ["kabul", "aktarma", "aktarma", "aktarma", "dagitima-cikis", "teslim-denemesi", "teslim"]; // K01 V4 = gonderi basina 7 durum olayi export function uretec(tohum) { // 32 bitlik dogrusal esitlikli uretec, tohum acik let s = tohum >>> 0; return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; } export const olaylar = (gonderi) => DIZI.map((durum, i) => ({ takip: `TR-${gonderi}`, durum, olayNo: i })); export const anahtar = (o, kip) => kip === "takip" ? o.takip : kip === "takip+durum" ? `${o.takip}|${o.durum}` : `${o.takip}|${o.durum}|${o.olayNo}`; // Yan etki: her olay bir durum kaydi yazar; teslim ayrica ucret kalemi ekler ve bildirim yollar. export const yanEtki = (o) => (o.durum === "teslim" ? 3 : 1); export function isleyici({ defterli, kip }) { const defter = new Set(); const s = { islenen: 0, bastirilan: 0, yanEtki: 0 }; return { s, al(o) { const a = anahtar(o, kip); if (defterli && defter.has(a)) { s.bastirilan += 1; return; } defter.add(a); s.islenen += 1; s.yanEtki += yanEtki(o); }, boy: () => defter.size, }; } // Teslim akisi: her olay bir kez gelir; ariza penceresinde uc kaynak fazladan teslim uretir. export function teslimler(gonderiSayisi, kaynaklar, tohum) { const rnd = uretec(tohum); const akis = [], sayac = new Map(kaynaklar.map(([ad]) => [ad, 0])); for (let g = 0; g < gonderiSayisi; g += 1) for (const o of olaylar(g)) { akis.push(o); for (const [ad, p] of kaynaklar) if (rnd() < p) { akis.push(o); sayac.set(ad, sayac.get(ad) + 1); } } return { akis, sayac }; }
Aynı akış üç anahtar kipiyle ve deftersiz olarak koşturulur. Anahtar kipi bir kapsam kararıdır: anahtar yalnız takip numarasından mı, takip numarası ile durumdan mı, yoksa taşıyıcının ürettiği olay kimliğini de içeren üçlüden mi oluşur.
// etkisiz/olc.mjs — arizasiz gunun bedeli ile ariza penceresinin kazanci ayri ayri sayilir import { DIZI, teslimler, isleyici } from "./akis.mjs"; const TEPE_YAZMA = 97.22, PENCERE = 90; // K01 hesabi; DD1 ariza penceresi const GONDERI = Math.round((TEPE_YAZMA * PENCERE) / DIZI.length); const KAYNAK = [["tasiyicinin yeniden denemesi", 0.20], // DD2, uc kaynaga bolunmus ["devralmada yeniden surme", 0.10], ["en az bir kez yeniden teslim", 0.05]]; const KIP = ["takip", "takip+durum", "takip+durum+olay"]; const kos = (kaynaklar, tohum) => { const { akis, sayac } = teslimler(GONDERI, kaynaklar, tohum); const satir = KIP.map((kip) => { const d = isleyici({ defterli: true, kip }); for (const o of akis) d.al(o); return [kip, d.s, d.boy()]; }); const y = isleyici({ defterli: false, kip: "takip+durum+olay" }); for (const o of akis) y.al(o); return { akis, sayac, satir, y: y.s }; }; const yaz = (baslik, r) => { console.log(`\n${baslik}: ${r.akis.length} teslim, deftersiz yan etki ${r.y.yanEtki}`); for (const [ad, s, boy] of r.satir) console.log(`${ad.padEnd(22)}${String(s.islenen).padStart(9)}${String(s.bastirilan).padStart(12)}` + `${String(s.yanEtki).padStart(10)}${String(r.y.yanEtki - s.yanEtki).padStart(11)}${String(boy).padStart(11)}`); }; console.log(`model: ${GONDERI} gonderi x ${DIZI.length} olay = ${GONDERI * DIZI.length} ozgun olay`); console.log(`(K01 tepe yazma ${TEPE_YAZMA} olay/s x DD1 pencere ${PENCERE} s)`); console.log(`\n${"anahtar kipi".padEnd(22)}${"islenen".padStart(9)}${"bastirilan".padStart(12)}` + `${"yan etki".padStart(10)}${"onlenen".padStart(11)}${"defter".padStart(11)}`); yaz("arizasiz gun (yineleme yok)", kos([], 20260730)); const r = kos(KAYNAK, 20260730); yaz("ariza penceresi (DD2)", r); console.log(`kaynak dagilimi: ${[...r.sayac].map(([a, n]) => `${a} ${n}`).join(", ")}`); const yinelenen = r.akis.length - GONDERI * DIZI.length; const yinelenenTeslim = r.akis.filter((o) => o.durum === "teslim").length - GONDERI; console.log(`yinelenen teslim ${yinelenen}, bunun ${yinelenenTeslim} tanesi "teslim" olayidir; ` + `yani ${yinelenenTeslim} yinelenen ucret kalemi ve ${yinelenenTeslim} yinelenen bildirim`);
model: 1250 gonderi x 7 olay = 8750 ozgun olay (K01 tepe yazma 97.22 olay/s x DD1 pencere 90 s) anahtar kipi islenen bastirilan yan etki onlenen defter arizasiz gun (yineleme yok): 8750 teslim, deftersiz yan etki 11250 takip 1250 7500 1250 10000 1250 takip+durum 6250 2500 8750 2500 6250 takip+durum+olay 8750 0 11250 0 8750 ariza penceresi (DD2): 11849 teslim, deftersiz yan etki 15251 takip 1250 10599 1250 14001 1250 takip+durum 6250 5599 8750 6501 6250 takip+durum+olay 8750 3099 11250 4001 8750 kaynak dagilimi: tasiyicinin yeniden denemesi 1789, devralmada yeniden surme 876, en az bir kez yeniden teslim 434 yinelenen teslim 3099, bunun 451 tanesi "teslim" olayidir; yani 451 yinelenen ucret kalemi ve 451 yinelenen bildirim
Bu sayılar ölçüm sınıfındadır: tohumu 20260730 olan bir üreteçle koşan süreç içi modelden gelirler ve aynı tohumla yeniden üretilirler. Girdileri ise K01’in hesabı (tepe yazma 97,22 olay/s) ile bu dersin varsayımlarıdır (DD1, DD2).
Anahtarın Kapsamı Bir Bastırma Kararıdır
Üstteki blok arızasız günü sayıyor ve tablonun en pahalı satırı orada. Yineleme yokken doğru
kapsam (takip+durum+olay) hiçbir şeyi bastırmıyor: 8750 olay işleniyor, 11250 yan etki
üretiliyor. Dar kapsamlı takip anahtarı ise 7500 geçerli olayı bastırıyor, çünkü bir
gönderinin yedi durum olayı aynı takip numarasını taşır; geriye yalnız ilki kalıyor ve yan etki
11250’den 1250’ye düşüyor. Orta kapsam takip+durum 2500 olay bastırıyor, çünkü dizideki üç
aktarma olayı aynı anahtarı üretiyor.
Yanlış kapsamın “önlenen” sütunundaki büyük sayıları bir kazanç gibi okunamaz. Arıza
penceresinde takip kipi 14001 yan etki önlemiş görünüyor; bunun 10.000’i arızasız günde de
oluşan bastırmadır, yani gerçek yan etkinin silinmesidir. Kural şudur: tekillik anahtarının
kapsamı, iş anlamında ayrı olan iki olayı ayırt edecek kadar geniş olmalıdır; anahtarı
daraltmak yinelemeyi değil, sistemin kendi işini yok eder.
Alt blok arızalı pencereyi sayıyor. Doğru kapsamda 8750 özgün olay 11.849 teslime dönüşüyor; fazladan 3099 teslimin 1789’u taşıyıcının yeniden denemesinden, 876’sı devralmada yeniden sürmeden, 434’ü en az bir kez teslimden geliyor. Defter olmasaydı yan etki 11.250 yerine 15.251 olurdu: 4001 fazladan yan etki. Bunun içinde 451 yinelenen ücret kalemi ve 451 yinelenen bildirim var — biri satıcıya iki kez fatura edilen bir teslim, öteki alıcıya iki kez giden bir teslim iletisidir.
Defterin Bedeli
Defter bedava değil. Her yazma önce defteri okur, sonra kendi kaydının yanına bir anahtar satırı yazar; bu satırlar bir pencere boyunca saklanır.
DD3 — anahtar kaydı 72 bayt, defter penceresi 24 saat. Kayıt anahtarı, gövde özetini, zaman damgasını ve sonuç göstergesini taşır. Pencerenin gerekçesi, bir istemcinin yeniden deneme zincirini ve devralma sonrası yeniden sürmeyi kapsayacak kadar uzun olmasıdır. Duyarlılığı kayıt boyunun iki katı, pencerenin üç katı ve bir saatlik pencereyle verilir.
// etkisiz/bedel.mjs — defterin K01 olceginde tuttugu kayit, kapladigi bayt ve satir hizlari const OLAY = 2_800_000, GONDERI = 400_000; // K01 hesabi: gunluk durum olayi ve gonderi const TEPE_YAZMA = 97.22, ARKA_OKUMA = 41.67; // K01 hesabi: tepe yazma, onbellek arkasi okuma const GUNLUK_MB = 976, ORAN = 2.33; // K01 hesabi: gunluk veri artisi, yazma/okuma const KAYIT = 72, SAAT = 24; // DD3: anahtar kaydi bayti ve defter penceresi const yazan = OLAY + GONDERI; console.log(`gunluk anahtar isteyen yazma ${yazan.toLocaleString("tr-TR")} ` + `(${OLAY.toLocaleString("tr-TR")} durum olayi + ${GONDERI.toLocaleString("tr-TR")} gonderi)`); console.log(`\n${"pencere".padStart(9)}${"kayit bayt".padStart(12)}${"tutulan kayit".padStart(15)}` + `${"defter MB".padStart(12)}${"gunluk artisin".padStart(16)}`); for (const [s, b] of [[SAAT, KAYIT], [SAAT, KAYIT * 2], [SAAT * 3, KAYIT], [1, KAYIT]]) { const kayit = (yazan * s) / 24, mb = (kayit * b) / 1e6; console.log(`${`${s} saat`.padStart(9)}${String(b).padStart(12)}` + `${Math.round(kayit).toLocaleString("tr-TR").padStart(15)}${mb.toFixed(2).padStart(12)}` + `${`%${((100 * mb) / GUNLUK_MB).toFixed(2)}`.padStart(16)}`); } const yaz2 = TEPE_YAZMA * 2, oku2 = ARKA_OKUMA + TEPE_YAZMA; console.log(`\ntepe yazma satir/s ${TEPE_YAZMA} -> ${yaz2.toFixed(2)} (${(yaz2 / TEPE_YAZMA).toFixed(2)} kat)`); console.log(`onbellek arkasi okuma/s ${ARKA_OKUMA} -> ${oku2.toFixed(2)} (${(oku2 / ARKA_OKUMA).toFixed(2)} kat)`); console.log(`depoda yazma/okuma orani ${ORAN} -> ${(yaz2 / oku2).toFixed(2)}`); const ONLENEN = 4001, UCRET = 451; // olc.mjs: pencere basina onlenen yan etki ve console.log(`\n${"DD1 pencere/ay".padStart(15)}${"onlenen yan etki".padStart(18)}` + // yinelenen teslim `${"yinelenen ucret kalemi".padStart(23)}${"gunluk kalemin".padStart(16)}`); for (const n of [2, 4]) console.log(`${n.toFixed(0).padStart(15)}${(n * ONLENEN).toLocaleString("tr-TR").padStart(18)}` + `${(n * UCRET).toLocaleString("tr-TR").padStart(23)}` + `${`%${((100 * n * UCRET) / (GONDERI * 30)).toFixed(5)}`.padStart(16)}`); console.log(`yanlis kapsam bedeli: takip+durum kipinde gunde ` + `${((OLAY * 2) / 7).toLocaleString("tr-TR")} gecerli olay bastirilir, ` + `takip kipinde ${((OLAY * 6) / 7).toLocaleString("tr-TR")}`);
gunluk anahtar isteyen yazma 3.200.000 (2.800.000 durum olayi + 400.000 gonderi)
pencere kayit bayt tutulan kayit defter MB gunluk artisin
24 saat 72 3.200.000 230.40 %23.61
24 saat 144 3.200.000 460.80 %47.21
72 saat 72 9.600.000 691.20 %70.82
1 saat 72 133.333 9.60 %0.98
tepe yazma satir/s 97.22 -> 194.44 (2.00 kat)
onbellek arkasi okuma/s 41.67 -> 138.89 (3.33 kat)
depoda yazma/okuma orani 2.33 -> 1.40
DD1 pencere/ay onlenen yan etki yinelenen ucret kalemi gunluk kalemin
2 8.002 902 %0.00752
4 16.004 1.804 %0.01503
yanlis kapsam bedeli: takip+durum kipinde gunde 800.000 gecerli olay bastirilir, takip kipinde 2.400.000
İki Günün Sayısı
Arızasız günün bedeli üç kalemdir. Defter 3.200.000 kayıt tutar ve 230,40 MB yer kaplar; bu, K01’in 976 MB’lık günlük veri artışının yüzde 23,61’idir. Tepe yazma satır hızı 97,22’den 194,44’e, yani tam iki katına çıkar. Okuma tarafına anahtar araması eklenir: önbellek arkası okuma 41,67’den 138,89’a çıkar, 3,33 kat. Depoda yazma/okuma oranı 2,33’ten 1,40’a iner — sayı küçüldü diye yük azalmadı; defter her iki tarafı da büyüttü, okuma tarafını daha çok.
Duyarlılık pencerede toplanıyor. Pencere 24 saatten 72 saate çıktığında defter 691,20 MB’a, yani günlük artışın yüzde 70,82’sine ulaşıyor; bir saatlik pencerede 9,60 MB ve yüzde 0,98’de kalıyor. Kayıt boyunu iki katına çıkarmak aynı etkiyi yapmıyor: 460,80 MB. Defterin bedelini kayıt boyu değil pencere belirler, çünkü pencere hem kayıt sayısını hem saklama süresini çarpar.
Arızalı günün kazancı iki kalemdir. DD1 ile ayda 2 pencere, pencere başına 4001 önlenen yan etki, ayda 8002. Bunun 902’si yinelenen ücret kalemidir ve K01’in günde 400.000 kalemine oranı yüzde 0,00752’dir. Oran küçüktür ve kalıbı savunan sayı bu değildir: 902 yinelenen ücret kalemi 902 yanlış faturalanmış teslim, yani 902 mutabakat uyuşmazlığıdır; aynı sayıda alıcı da ikinci kez teslim iletisi alır. DD1 iki katına çıktığında 1804 olur. Yan etkinin sayısı yüzdeyle değil tanesiyle okunur, çünkü her tane bir düzeltme işi doğurur.
Özet
- Yineleme tek kaynaktan gelmez: 90 saniyelik pencerede 3099 fazladan teslimin 1789’u yeniden denemeden, 876’sı devralmada yeniden sürmeden, 434’ü en az bir kez teslimden geldi.
- Defter olmadan yan etki 11.250 yerine 15.251 olurdu; önlenen 4001 yan etkinin 451’i yinelenen ücret kalemi, 451’i yinelenen bildirimdir.
- Tekillik anahtarının kapsamı bir bastırma kararıdır:
takipkipi arızasız günde 7500,takip+durumkipi 2500 geçerli olayı bastırıyor; K01 ölçeğinde bu günde 2.400.000 ve 800.000 olay eder. - Arızasız günün bedeli: 3.200.000 defter kaydı, 230,40 MB (günlük artışın yüzde 23,61’i), tepe yazma satırı 2,00 kat, önbellek arkası okuma 3,33 kat.
- Bedeli pencere belirler: 72 saatlik pencere defteri 691,20 MB’a çıkarıyor, bir saatlik pencere 9,60 MB’ta tutuyor; kayıt boyunu ikiye katlamak 460,80 MB veriyor.
- Arızalı günün kazancı ayda 8002 önlenen yan etki ve 902 yinelenen ücret kalemidir; günlük kalemin yüzde 0,00752’si, ama 902 ayrı düzeltme işi.
Sonraki Adım
Defter yinelemeyi durdurdu, ama yalnız aynı işin ikinci kez yapılmasını. Tatbikatın bıraktığı ikinci sorun yerinde duruyor: yarıda kesilen bir iş akışının tamamlanmış adımları hâlâ geri alınmıyor. Ücretlendirme işi üçüncü adımda düşerse ilk iki adımın etkisi kesinleşmiş kalır ve bunlar yinelenmiş değil, eksik kalmış etkilerdir; tekillik anahtarı bu duruma hiçbir şey söylemez. Sonraki ders geri alınamayan adımların dengelenmesini ele alır: telafi edilemeyen bir adımın sıralamada nereye konacağı, telafi çağrısının kendisi başarısız olduğunda kaç işin askıda kaldığı ve geri sarma penceresinde ara durumu kaç okumanın gördüğü.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.