Ders 09 / 14
Ödeme Akışı
İki tarafın kayıtlarının ayrıştığı vaka: mutabakat çevriminde eşleşmeyen kayıt sayısının kaynağına göre ayrılması, yoklamanın farkı binde 59,74'ten binde 9,76'ya indirmesi, geç gelen onayın kalan farkın çoğunu oluşturması, çevrim aralığının fark oranını düşürürken kapanma süresini uzatması ve denetim izinin yedi yıllık hacmi.
İçindekiler
Önceki vakada rezervasyon bir söz verdi ve sözü tutan adım kapsam dışında bırakıldı: ödemenin kendisi. O adım iki taraflıdır; kayıt hem bizde hem dış tarafta tutulur, ikisi birbirinin defterini göremez ve aralarındaki tek bağ ileti alışverişidir.
Etkisizlik ve tekillik anahtarı Dayanıklılık ve Güvenilirlik kursunun Etkisiz İşlemler dersinde kuruldu ve tekrarlanmaz; yeniden gönderilen bir istek ikinci bir işlem açmaz. Bu dersin sorusu başkadır: iletiler kaybolduğunda ya da geç geldiğinde iki defter ne kadar ayrışır, bu fark nasıl sayılır ve ne zaman kapanır.
Kısıtlar ve Kapsam
İşlevsel gereksinimler: ödeme başlatmak, dış taraftan gelen durum bildirimini işlemek, durumu sorgulamak, iade başlatmak ve her durum geçişini denetim izine yazmak.
Tasarlanmayan işler: kimlik doğrulama akışı, dolandırıcılık puanlama, muhasebe defterinin kapanışı ve iade kurallarının kendisi.
İşlevsel olmayan gereksinimler eşikle ve kaynağıyla yazılır: bir işlemin iki taraftaki durumu en geç 30 dakika içinde eşleşir (kaynak: bir farkın elle araştırılmadan önce kendi kendine kapanmasına bırakılan süre), bir mutabakat çevriminde eşleşmeyen kayıt oranı binde 10’u geçmez (kaynak: bir çevrimde elle incelenebilecek kayıt sayısı), her durum geçişi denetim izinde görünür ve iz kaydı sonradan değişmez (kaynak: bir işlemin geçmişinin yeniden kurulabilmesi yükümlülüğü).
Varsayımlar ve Ölçek
| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| OD1 | günlük ödeme işlemi | 1.200.000 | tamamlanan ve reddedilen girişimlerin toplamı |
| OD2 | tepe çarpanı | 4 | akşam saatlerinin gün ortalamasına oranı |
| OD3 | bildirim gecikmesi | 0,92 / 0,06 / 0,02 | beş saniyeden, beş dakikadan ve daha uzun süreden sonra gelen bildirim payları |
| OD4 | bildirim kaybı | 0,03 | dış tarafın bildiriminin hiç ulaşmaması |
| OD5 | yoklama aralığı | 60 saniye | beklemede kalan işlemin sorulma sıklığı |
| OD6 | mutabakat çevrimi | 900 saniye | iki defterin karşılaştırılma sıklığı |
| OD7 | denetim izi | 5 geçiş × 260 bayt | işlem başına durum geçişi sayısı ve kayıt boyu |
| OD8 | saklama süresi | 2555 gün | uyuşmazlıkların yedi yıl geriye açılabilmesi |
| OD9 | dış tarafta sonuçlanma | 0,90 / 0,08 / 0,02 | 3, 3–60 ve 60–900 saniyede sonuçlanan işlem payları; dış tarafın kendi onay adımları kuyruk üretir |
| OD10 | eşzamanlı doğrulama bütçesi | 20 saniye | isteğin yanıt beklerken açık tutulabileceği üst süre |
// odeme/olcek.mjs — OD varsayim tablosundan cikan kabaca buyukluk hesabi const OD = { gunlukIslem: 1_200_000, tepeCarpani: 4, bildirimKaybi: 0.03, yoklama: 60, cevrim: 900, izKaydiBayt: 260, gecisSayisi: 5, saklamaGun: 2555 }; // OD1..OD8 const GUN = 86_400; const tepe = (OD.gunlukIslem / GUN) * OD.tepeCarpani; const izGunluk = OD.gunlukIslem * OD.gecisSayisi * OD.izKaydiBayt; console.log(`tepe islem/s ${tepe.toFixed(2)}`); console.log(`cevrimde karsilastirilan kayit ${Math.round(tepe * OD.cevrim)}`); console.log(`gunluk kaybolan bildirim ${OD.gunlukIslem * OD.bildirimKaybi}`); console.log(`yoklamanin bulacagi kayit/s ${(tepe * OD.bildirimKaybi).toFixed(2)}`); console.log(`denetim izi gunluk GB ${(izGunluk / 1e9).toFixed(2)}`); console.log(`denetim izi ${OD.saklamaGun} gunde TB ${((izGunluk * OD.saklamaGun) / 1e12).toFixed(2)}`); console.log(`iz kaydi / islem kaydi orani ${OD.gecisSayisi} (her gecis bir satir)`); console.log(`\n${"mutabakat cevrimi".padStart(19)}${"tepe partide kayit".padStart(20)}` + `${"gunluk cevrim".padStart(15)}`); for (const c of [300, OD.cevrim, 3600]) console.log(`${`${c / 60} dk`.padStart(19)}${Math.round(tepe * c).toString().padStart(20)}` + `${(GUN / c).toFixed(0).padStart(15)}`); console.log(`\ngunluk karsilastirilan kayit cevrimden bagimsiz: ${OD.gunlukIslem} ` + `(her kayit bir kez); cevrim yalnizca parti boyunu ve fark suresini degistirir`);
tepe islem/s 55.56
cevrimde karsilastirilan kayit 50000
gunluk kaybolan bildirim 36000
yoklamanin bulacagi kayit/s 1.67
denetim izi gunluk GB 1.56
denetim izi 2555 gunde TB 3.99
iz kaydi / islem kaydi orani 5 (her gecis bir satir)
mutabakat cevrimi tepe partide kayit gunluk cevrim
5 dk 16667 288
15 dk 50000 96
60 dk 200000 24
gunluk karsilastirilan kayit cevrimden bagimsiz: 1200000 (her kayit bir kez); cevrim yalnizca parti boyunu ve fark suresini degistirir
Bu sayılar hesap sınıfındadır; OD9 ile OD10 yalnız elenen alternatifin modelinde kullanılır. Üçü tasarımı belirliyor. Birincisi, tepe hız saniyede 55,56 işlemdir; bu vakanın hiçbir kararı hacimle ilgili değildir. İkincisi, günde 36.000 bildirim hiç ulaşmaz ve bunları bulmanın maliyeti saniyede 1,67 sorgudur — küçük bir yük, ama olmadığında o kayıtlar ayrışır. Üçüncüsü, denetim izi asıl hacim kalemidir: günde 1,56 GB, yedi yılda 3,99 TB, işlem kaydının beş katı satır.
Farkın Ölçülmesi
Ölçüm süreç içi bir modeldir: gerçek dış taraf, ağ ya da depo yoktur; gecikme ve kayıp parametredir, zaman model saatidir. Model bir çevrimin işlemlerini üretir, her biri için dış tarafta sonuçlanma anıyla yerel tarafın bunu öğrendiği anı hesaplar ve çevrim bitince iki durumu karşılaştırır. Fark üç kaynağa ayrılır: bildirimi kaybolan, bildirimi geç gelen ve dış tarafta henüz sonuçlanmamış işlem — sonuncusu fark değildir, iki taraf da aynı şeyi söyler.
// odeme/mutabakat.mjs — iki tarafin kayitlari arasindaki farkin surec ici modeli. Gercek dis // taraf, ag ya da depo yoktur: gecikme ve kayip parametredir, zaman model saatidir (saniye). const TEPE = 55.56, YOKLAMA = 60, KAYIP = 0.03; // hesap; OD5, OD4 const SONUCLANMA = 3; // dis tarafta islemin bitme suresi (s) function uretec(tohum) { // 32 bitlik dogrusal esitlikli uretec let s = tohum >>> 0; return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; } // OD3: bildirim gecikmesi uc dilimde - %92 hizli, %6 gecikmeli, %2 gec gelen const gecikme = (r) => (r < 0.92 ? 5 * (r / 0.92) : r < 0.98 ? 5 + 295 * ((r - 0.92) / 0.06) : 300 + 1500 * ((r - 0.98) / 0.02)); export function kos({ cevrim, yoklamali, tohum }) { const rnd = uretec(tohum); const n = Math.round(TEPE * cevrim); const s = { islem: n, gecGelen: 0, kaybolan: 0, sonuclanmamis: 0 }; for (let i = 0; i < n; i += 1) { const baslangic = (i / TEPE), biten = baslangic + SONUCLANMA; const kayipMi = rnd() < KAYIP, g = gecikme(rnd()); const bildirim = kayipMi ? Infinity : biten + g; const yoklamaAni = yoklamali ? Math.ceil(biten / YOKLAMA) * YOKLAMA : Infinity; const bilinen = Math.min(bildirim, yoklamaAni); if (biten >= cevrim) { s.sonuclanmamis += 1; continue; } // dis tarafta da bitmemis if (bilinen < cevrim) continue; // iki taraf da biliyor if (kayipMi) s.kaybolan += 1; else s.gecGelen += 1; } s.eslesmeyen = s.gecGelen + s.kaybolan; s.binde = (1000 * s.eslesmeyen) / s.islem; return s; } const yaz = (etiket, r) => console.log(`${etiket.padEnd(26)}${String(r.islem).padStart(9)}${String(r.eslesmeyen).padStart(12)}` + `${String(r.gecGelen).padStart(11)}${String(r.kaybolan).padStart(10)}` + `${String(r.sonuclanmamis).padStart(15)}${r.binde.toFixed(2).padStart(9)}`); console.log(`model: tepe ${TEPE} islem/s, bildirim kaybi ${KAYIP}, yoklama ${YOKLAMA} s, tohum 20260730`); console.log(`\n${"duzen (cevrim 900 s)".padEnd(26)}${"islem".padStart(9)}${"eslesmeyen".padStart(12)}` + `${"gec gelen".padStart(11)}${"kaybolan".padStart(10)}${"sonuclanmamis".padStart(15)}${"binde".padStart(9)}`); yaz("yalniz bildirim", kos({ cevrim: 900, yoklamali: false, tohum: 20260730 })); yaz("bildirim + yoklama", kos({ cevrim: 900, yoklamali: true, tohum: 20260730 })); console.log(`\n${"mutabakat cevrimi".padStart(19)}${"islem".padStart(9)}${"eslesmeyen".padStart(12)}` + `${"binde".padStart(9)}${"esik binde 10".padStart(15)}${"kapanma dk".padStart(12)}${"esik 30 dk".padStart(12)}`); for (const c of [300, 900, 3600]) { const r = kos({ cevrim: c, yoklamali: true, tohum: 20260730 }); console.log(`${`${c / 60} dk`.padStart(19)}${String(r.islem).padStart(9)}${String(r.eslesmeyen).padStart(12)}` + `${r.binde.toFixed(2).padStart(9)}${(r.binde <= 10 ? "gecer" : "gecmez").padStart(15)}` + `${String((2 * c) / 60).padStart(12)}${((2 * c) / 60 <= 30 ? "gecer" : "gecmez").padStart(12)}`); } const a = kos({ cevrim: 900, yoklamali: false, tohum: 20260730 }); const b = kos({ cevrim: 900, yoklamali: true, tohum: 20260730 }); const c60 = kos({ cevrim: 3600, yoklamali: true, tohum: 20260730 }); console.log(`\nyoklamanin kapattigi fark: ${a.eslesmeyen} -> ${b.eslesmeyen} kayit ` + `(binde ${a.binde.toFixed(2)} -> ${b.binde.toFixed(2)})`); console.log(`kalan farkin ${b.gecGelen}/${b.eslesmeyen} tanesi gec gelen onaydir`); console.log(`cevrim 15 dk -> 60 dk: eslesmeyen sayisi ${b.eslesmeyen} -> ${c60.eslesmeyen} (neredeyse ayni), ` + `binde ${b.binde.toFixed(2)} -> ${c60.binde.toFixed(2)}`);
model: tepe 55.56 islem/s, bildirim kaybi 0.03, yoklama 60 s, tohum 20260730
duzen (cevrim 900 s) islem eslesmeyen gec gelen kaybolan sonuclanmamis binde
yalniz bildirim 50004 2987 1437 1550 166 59.74
bildirim + yoklama 50004 488 369 119 166 9.76
mutabakat cevrimi islem eslesmeyen binde esik binde 10 kapanma dk esik 30 dk
5 dk 16668 488 29.28 gecmez 10 gecer
15 dk 50004 488 9.76 gecer 30 gecer
60 dk 200016 482 2.41 gecer 120 gecmez
yoklamanin kapattigi fark: 2987 -> 488 kayit (binde 59.74 -> 9.76)
kalan farkin 369/488 tanesi gec gelen onaydir
cevrim 15 dk -> 60 dk: eslesmeyen sayisi 488 -> 482 (neredeyse ayni), binde 9.76 -> 2.41
Bu sayılar ölçüm sınıfındadır; tohum 20260730 ile yeniden üretilir.
İlk tablo yoklamanın payını ayırıyor. Yalnız bildirime dayanan düzende çevrim başına 2987 kayıt eşleşmiyor, binde 59,74; eşik binde 10 olduğuna göre bu düzen tek başına geçmiyor. Yoklama eklendiğinde fark 488’e, binde 9,76’ya iniyor: kaybolan bildirim 1550’den 119’a düşüyor, çünkü en geç 60 saniye içinde sorgulamayla bulunuyor.
Kalan 488 kaydın 369’u geç gelen onaydır ve yoklama bunu kapatamaz: dış taraf işi bitirmiş, bildirimi henüz göndermemiştir, yoklama da o anda “beklemede” yanıtı alır. Bu bir hata değil, iki tarafın saatleri arasındaki gerçek bir aralıktır; mutabakatın işi onu saymak ve kapanmasını beklemektir.
İkinci tablo çevrim aralığını seçiyor ve en öğretici satır sayının kendisidir: eşleşmeyen kayıt 5 dakikada 488, 15 dakikada 488, 60 dakikada 482 — neredeyse hiç değişmiyor. Fark bir sınır olayıdır; çevrimin uzunluğuyla değil, kenarındaki işlem sayısıyla belirlenir. Çevrimi uzatmak farkı çözmez, oranı binde 29,28’den binde 2,41’e seyreltir. Kapanma süresi ise iki çevrim uzunluğundadır: 10, 30 ve 120 dakika. İki eşik iki yönden bastırıyor ve yalnız 15 dakika ikisini birden geçiyor.
Tasarım
Durum bildirimi ve yoklama. Dış tarafın bildirimi Uygulama Katmanı ve Servis Etkileşimi kursunun Eşzamansız İstek–Yanıt dersindeki yapıdır; yoklama ise bu konunun ilk vakasında elenmiş kalıptır. Orada birincil teslim yolu olarak 140,63 kat istek ürettiği için elenmişti; burada yalnız beklemede kalanı sorduğu için tepe yükü saniyede 1,67 sorgudur. Aynı kalıp, farklı rol, farklı parametre.
Denetim izi. Veri Katmanı Ölçekleme kursunun Olay Kaynaklı Tasarım dersindeki eklemeli kütük burada denetim izidir; parametre saklama süresi ve geçiş sayısıdır: işlem başına 5 satır, 260 bayt, yedi yılda 3,99 TB. İzin üzerine yazılmaz, yalnız eklenir; işlemin o anki durumu izden türetilen bir izdüşümdür.
Fark bulununca yapılan iş. Eşleşmeyen kayıt Dayanıklılık ve Güvenilirlik kursunun Telafi Edici İşlemler dersindeki adımı doğurur: yerel taraf “başarılı” derken dış taraf “reddedildi” diyorsa yerel etki geri alınır. Parametre, telafinin yalnız mutabakat tarafından tetiklenmesidir; hiçbir istek yolu kendi başına telafi başlatmaz.
Bilerek kullanılmayan kalıp. Ödeme durumu önbelleğe alınmaz (Veri Katmanı Ölçekleme, Önbellek Mimarisi): bayat bir durum kullanıcıya kesinleşmiş gibi gösterilir — burada bayatlık bir pencere değil, bir kusurdur.
Elenen Alternatif: Eşzamanlı Doğrulama
Mutabakat çevrimini büsbütün ortadan kaldıran bir tasarım savunulabilir: her ödeme dış tarafa senkron sorulur ve istek yanıtı bekler, yani ayrışacak iki defter hiç oluşmaz. Bu düzenin duyarlı olduğu şey yük değil, dış tarafta sonuçlanma süresidir. Seçilen tasarımda o süre uzadıkça işlemler yukarıdaki modelin üçüncü kaynağına, fark saymayan kovaya düşer; eşzamanlı düzende ise doğrudan yanıtsız isteğe dönüşür. Alternatifin modeli bu yüzden sonuçlanmaya kendi dağılımını (OD9) ve isteğe bir bekleme bütçesi (OD10) verir; senkron yanıt kayıpsız sayılır, ölçü alternatife elverişlidir.
// odeme/eszamanli.mjs — elenen alternatifin surec ici modeli. Gercek dis taraf yoktur: // sonuclanma suresi parametredir, zaman model saatidir (saniye). const TEPE = 55.56, CEVRIM = 900, N = Math.round(TEPE * CEVRIM); // hesap; OD6 function uretec(tohum) { // 32 bitlik dogrusal esitlikli uretec let s = tohum >>> 0; return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; } const sonuclanma = (r) => // OD9: %90 3 s, %8 3-60 s, %2 60-900 s (r < 0.9 ? 3 : r < 0.98 ? 3 + 57 * ((r - 0.9) / 0.08) : 60 + 840 * ((r - 0.98) / 0.02)); function kos(butce) { // her odeme yanit gelene dek istegi acik tutar const rnd = uretec(20260730); let belirsiz = 0, tutulan = 0; for (let i = 0; i < N; i += 1) { const s = sonuclanma(rnd()); tutulan += Math.min(s, butce); if (s > butce) belirsiz += 1; // yanitsiz kapandi, kapatacak cevrim de yok } return { belirsiz, binde: (1000 * belirsiz) / N, ort: tutulan / N }; } console.log(`model: tepe ${TEPE} islem/s, cevrim ${CEVRIM} s, ${N} islem, tohum 20260730`); for (const b of [20, 60, 540]) { const r = kos(b); console.log(`butce ${b} s: belirsiz ${r.belirsiz} (binde ${r.binde.toFixed(2)}, esik ` + `${r.binde <= 10 ? "gecer" : "gecmez"}), ortalama tutulma ${r.ort.toFixed(2)} s, ` + `tepede acik istek ${(TEPE * r.ort).toFixed(1)}`); }
model: tepe 55.56 islem/s, cevrim 900 s, 50004 islem, tohum 20260730 butce 20 s: belirsiz 3789 (binde 75.77, esik gecmez), ortalama tutulma 4.48 s, tepede acik istek 249.1 butce 60 s: belirsiz 995 (binde 19.90, esik gecmez), ortalama tutulma 6.38 s, tepede acik istek 354.4 butce 540 s: belirsiz 421 (binde 8.42, esik gecer), ortalama tutulma 13.06 s, tepede acik istek 725.7
Bu sayılar ölçüm sınıfındadır; tohum 20260730 ile yeniden üretilir. Alternatifi eleyen sayı binde 75,77tir: 20 saniyelik bütçede çevrim başına 3789 işlem yanıtsız kapanır — eşiğin 7,6 katı — ve bunları kapatacak bir çevrim yoktur; kapanma süresi 30 dakika değil, tanımsızdır. Bütçeyi büyütmek ucuz değildir: 60 saniyede oran binde 19,90’da kalıyor, eşik ancak 540 saniyede geçiliyor, yani istemci dokuz dakika bekletilerek; o noktada tepede aynı anda 725,7 istek açık duruyor.
Kazandığı da yazılır: bütçe içinde yanıtlanan işlemde iki defter yanıt anında eşleşir, ortalama kapanma 4,48 saniyedir, çevrim ve telafi işi hiç doğmaz. Kısıt şurada dönerdi: dış taraf işlemlerin yüzde 99’unu bütçe içinde sonuçlandırsaydı belirsiz oran binde 10’a iner, alternatif eşiği geçer ve mutabakat çevrimi gereksizleşirdi.
Arıza Davranışı ve Feda Edilen
Dış tarafın bildirim ucu tümden durursa yoklama tek kanal kalır ve bütün işlemler en geç 60 saniye gecikmeyle öğrenilir; bu tepe yükte saniyede 55,56 sorgudur, normalin 33 katıdır ve Dayanıklılık ve Güvenilirlik kursunun Kısıtlama ve Yük Boşaltma dersindeki kısıtlama bu hızı keser. Mutabakat çevrimi kaçırılırsa fark kaybolmaz, sonraki çevrimde iki katı kayıtla görünür; mutabakatın kendisi durumunu izden yeniden kurduğu için kesintiye dayanıklıdır.
Feda edilen tek cümlededir: bu tasarım anlık tutarlılığı feda eder — iki defter hiçbir zaman aynı anda aynı şeyi söylemez, yalnız 30 dakika içinde söyleyeceği güvence altına alınır.
Özet
- Bu vakada hacim karar vermiyor: tepe 55,56 işlem/s her yapıda taşınır, asıl hacim kalemi denetim izidir — günde 1,56 GB, yedi yılda 3,99 TB.
- Yalnız bildirime dayanan düzende çevrim başına 2987 kayıt eşleşmiyor (binde 59,74); yoklama bunu 488’e (binde 9,76) indiriyor ve kaybolan bildirimi 1550’den 119’a düşürüyor.
- Kalan 488 farkın 369’u geç gelen onaydır; yoklama bunu kapatamaz, çünkü dış taraf da o anda sonucu bildirmemiştir.
- Eşleşmeyen kayıt sayısı çevrim uzunluğundan neredeyse bağımsızdır (488, 488, 482); çevrimi uzatmak farkı çözmez, oranı binde 29,28’den binde 2,41’e seyreltir.
- İki eşik çevrimi tek bir değere sıkıştırıyor: binde 10 sınırı 5 dakikayı, 30 dakikalık kapanma sınırı 60 dakikayı eliyor ve 15 dakika kalıyor.
- Çevrimsiz eşzamanlı doğrulama alternatifi binde 75,77 belirsiz kayıtla eleniyor; eşiği ancak 540 saniyelik bekleme bütçesiyle, tepede 725,7 açık istekle geçiyor.
Sonraki Adım
Beş vaka aynı ailedendi: yazma hâkimdi ve bir kaydın yanlış olması kabul edilmiyordu. Kararlar teslim garantisinden, kanal sırasından, pencere biçiminden, kilit türünden ve mutabakat çevriminden çıktı. Beşinde de gözden kaçan ortak bir özellik var: veri küçüktü. Bir ileti 220 bayt, bir bildirim kaydı 300 bayt, bir sayaç 48 bayt, bir stok satırı 64 bayt, bir iz kaydı 260 bayttı; hiçbir tasarım kararı baytın kendisinden çıkmadı, hepsi kayıt başına anlamdan çıktı. Kaydın tek başına anlamsız olduğu, değerinin ancak yığın hâlinde ortaya çıktığı ve tek bir nesnenin bir sunucunun belleğine sığmadığı sistemlerde bu sıralama tersine döner. Sonraki konu o tersine dönmüş sıralamayı ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.