Ders 24 / 27
Maliyet Tahmini
Altyapı değişikliğinin bütçeye etkisinin uygulanmadan önce sayılması: plan farkından değişiklik başına aylık tahmin üretilmesi, sabit birim fiyatlı kalemler ile kullanıma bağlı kalemlerin ayrılması, gerçekleşenle karşılaştırıldığında hatanın nereden geldiği ve nasıl dağıldığı, toplam doğruluğun tek tek değişikliklerdeki sapmayı nasıl gizlediği ve bir eşik kuralının kaç değişikliği gereksiz durdurup kaç pahalı değişikliği geçirdiği.
İçindekiler
Önceki ders bir doğrulama koşumunun kendi maliyetini ve arkasında bıraktığı artığın sızan maliyetini saydı. İkisi de olan bitince sayıldı — para harcandıktan sonra. Aynı soru uygulamadan önce de sorulabilir, çünkü plan zaten elde: hangi kaynağın ekleneceği, hangisinin büyütüleceği, hangisinin silineceği orada yazılıdır. Bir plan farkı aynı zamanda bir fatura farkıdır.
Sistem ölçeğinde kapasite ve maliyet farkındalığı daha önce ölçüldü; buradaki birim ise değişikliğin kendisidir: bu katkı birleşirse aylık gider ne kadar artar? Bu ders plandan değişiklik başına tahmin üretir, gerçekleşenle karşılaştırır ve üç şeyi sayar: hangi kalem tahmin edilebiliyor, hangi kalem edilemiyor, hata nasıl dağılıyor. Sonra bir eşik kuralı — “tahmin şu kadarı aşarsa dur” — bilinen değişiklik kümesinde koşturulur ve kuralın iki hatası birden sayılır. Through-line bölgesel ölçüm ağıdır; fiyat listesi de tutarlar da kurgudur.
Tahmin Edilebilen ve Edilemeyen Kalem
Bir maliyet kalemi iki türden biridir. Sabit birim fiyatlı kalemin miktarı plandan doğrudan okunur: üç makine, 120 gigabayt disk, 500 gigabayt depo. Bu kalemler için tahmin bir çarpma işlemidir ve ay boyunca değişmez. Kullanıma bağlı kalemin miktarı ise planda yoktur; plan kaynağın var olacağını söyler ama kaç istek alacağını, kaç gigabayt veri çıkaracağını, kaç ileti taşıyacağını söylemez. Bu kalemler için tahmin, kayıtta yazılı beklenen kullanıma dayanır — yani bir tahminin üstüne kurulmuş bir tahmindir.
// tahmin.mjs — plan uzerinden degisiklik basina aylik maliyet tahmini uretir (model) import { writeFileSync } from "node:fs"; // AD19: fiyat listesi kurgudur, hicbir saglayicinin fiyatiyla ilgisi yoktur; birim "kb" (kurgu birim) const SABIT = { "makine-orta": 30, "makine-buyuk": 60, "disk-gb": 0.5, "depo-gb": 0.8, "arsiv-gb": 0.1 }; // kalem basina aylik sabit birim fiyat // kullanima bagli kalem: [aylik birim fiyat, belirsizlik carpani r] — gercek miktar 1/r ile r arasi const KULLANIM = { "istek-milyon": [4, 4], "cikan-veri-gb": [0.9, 4], "ileti-milyon": [2, 2], "okuma-milyon": [3, 2] }; // AD20: on iki degisiklik plandan okunmustur; her kalemin miktari plandaki kaynak farkidir, // kullanima bagli kalemlerde ise kayitta yazili beklenen kullanimdir (kurgu) const DEGISIKLIK = [ ["G01", "uc toplayici makine eklenir", [["makine-orta", 3], ["disk-gb", 120], ["cikan-veri-gb", 60]]], ["G02", "veri deposu 500 GB buyutulur", [["depo-gb", 500], ["okuma-milyon", 20]]], ["G03", "arsiv akisi eklenir", [["arsiv-gb", 2000], ["okuma-milyon", 30]]], ["G04", "olcum kuyrugu eklenir", [["ileti-milyon", 120]]], ["G05", "toplayici boyutu buyuk yapilir", [["makine-buyuk", 3], ["makine-orta", -3]]], ["G06", "yeni bolge: ag ve iki makine", [["makine-orta", 2], ["disk-gb", 80], ["cikan-veri-gb", 300]]], ["G07", "toplayici diski 40'tan 200 GB'a", [["disk-gb", 480], ["okuma-milyon", 10]]], ["G08", "yedek penceresi 7 gunden 30 gune", [["depo-gb", 1400]]], ["G09", "dis uc nokta acilir", [["istek-milyon", 20], ["cikan-veri-gb", 800]]], ["G10", "arsiv sinifi soguga alinir", [["arsiv-gb", -1500]]], ["G11", "toplayici sayisi 3'ten 9'a", [["makine-orta", 6], ["disk-gb", 240], ["cikan-veri-gb", 120]]], ["G12", "toplama sikligi iki katina", [["ileti-milyon", 40], ["okuma-milyon", 60]]], ]; const T = DEGISIKLIK.map(([kod, ne, kalemler]) => { let sabit = 0; const kullanim = []; for (const [kalem, miktar] of kalemler) { if (SABIT[kalem] !== undefined) sabit += SABIT[kalem] * miktar; else kullanim.push([kalem, KULLANIM[kalem][0] * miktar, KULLANIM[kalem][1]]); } const k = kullanim.reduce((s, [, t]) => s + t, 0); return { kod, ne, sabit, kullanim, tahmin: sabit + k, sabitKalem: kalemler.length - kullanim.length }; }); writeFileSync("tahmin.json", JSON.stringify(T)); console.log(`${"kod".padEnd(5)}${"degisiklik".padEnd(34)}${"sabit kalem".padStart(12)}` + `${"kullanim kalemi".padStart(16)}${"sabit kb".padStart(10)}${"kullanim kb".padStart(13)}` + `${"tahmin kb".padStart(11)}`); for (const d of T) { const k = d.tahmin - d.sabit; console.log(`${d.kod.padEnd(5)}${d.ne.padEnd(34)}${String(d.sabitKalem).padStart(12)}` + `${String(d.kullanim.length).padStart(16)}${d.sabit.toFixed(1).padStart(10)}` + `${k.toFixed(1).padStart(13)}${d.tahmin.toFixed(1).padStart(11)}`); } const sabitToplam = T.reduce((s, d) => s + d.sabit, 0); const kullanimToplam = T.reduce((s, d) => s + (d.tahmin - d.sabit), 0); const kalem = T.reduce((s, d) => s + d.sabitKalem + d.kullanim.length, 0); console.log(`\n${T.length} degisiklik, ${kalem} kalem: ` + `${T.reduce((s, d) => s + d.sabitKalem, 0)} sabit birim fiyatli (tahmin edilebilir), ` + `${T.reduce((s, d) => s + d.kullanim.length, 0)} kullanima bagli (tahmin edilemez)`); console.log(`toplam tahmin ${(sabitToplam + kullanimToplam).toFixed(1)} kb; ` + `sabit bolum ${sabitToplam.toFixed(1)} kb (%${(100 * sabitToplam / (sabitToplam + kullanimToplam)).toFixed(0)}), ` + `kullanim bolumu ${kullanimToplam.toFixed(1)} kb (%${(100 * kullanimToplam / (sabitToplam + kullanimToplam)).toFixed(0)})`); console.log(`kullanima bagli kalem tasiyan degisiklik: ${T.filter((d) => d.kullanim.length).length}/${T.length}`);
kod degisiklik sabit kalem kullanim kalemi sabit kb kullanim kb tahmin kb G01 uc toplayici makine eklenir 2 1 150.0 54.0 204.0 G02 veri deposu 500 GB buyutulur 1 1 400.0 60.0 460.0 G03 arsiv akisi eklenir 1 1 200.0 90.0 290.0 G04 olcum kuyrugu eklenir 0 1 0.0 240.0 240.0 G05 toplayici boyutu buyuk yapilir 2 0 90.0 0.0 90.0 G06 yeni bolge: ag ve iki makine 2 1 100.0 270.0 370.0 G07 toplayici diski 40'tan 200 GB'a 1 1 240.0 30.0 270.0 G08 yedek penceresi 7 gunden 30 gune 1 0 1120.0 0.0 1120.0 G09 dis uc nokta acilir 0 2 0.0 800.0 800.0 G10 arsiv sinifi soguga alinir 1 0 -150.0 0.0 -150.0 G11 toplayici sayisi 3'ten 9'a 2 1 300.0 108.0 408.0 G12 toplama sikligi iki katina 0 2 0.0 260.0 260.0 12 degisiklik, 24 kalem: 13 sabit birim fiyatli (tahmin edilebilir), 11 kullanima bagli (tahmin edilemez) toplam tahmin 4362.0 kb; sabit bolum 2450.0 kb (%56), kullanim bolumu 1912.0 kb (%44) kullanima bagli kalem tasiyan degisiklik: 9/12
Yirmi dört kalemin on üçü sabit birim fiyatlıdır, on biri kullanıma bağlı. Kalem sayısı bakımından çoğunluk tahmin edilebilir görünür, ama tutar bakımından bölünme farklıdır: toplamın yüzde elli altısı sabit, yüzde kırk dördü kullanıma bağlıdır. Daha önemlisi dağılımdır — on iki değişikliğin dokuzu en az bir kullanıma bağlı kalem taşır. Üç değişiklik (G05, G08, G10) yalnız sabit kalemden oluşur ve onların tahmini bir hesaplama değil, bir kesinliktir.
Gerçekleşenle Karşılaştırma
AD21 — sabit birim fiyatlı kalem ay boyunca değişmez, hatası tam olarak sıfırdır; kullanıma
bağlı kalemin gerçek miktarı beklenenin 1/r ile r katı arasındadır. AD22 — on iki
değişiklik aynı ay içinde uygulanır ve gerçekleşen tutar ay sonunda okunur (kurgu). Belirsizlik
çarpanı kaleme göre değişir: dış istek ve çıkan veri en oynak kalemlerdir (r = 4), kuyruk iletisi
ve depo okuması daha durgundur (r = 2).
// gercek.mjs — gerceklesen maliyet, tahmin hatasinin dagilimi ve esik kuralinin sonucu (model) import { readFileSync } from "node:fs"; // AD21: sabit birim fiyatli kalem ay boyunca degismez, hatasi sifirdir; kullanima bagli kalemin // gercek miktari beklenenin 1/r ile r kati arasindadir (r kalemin belirsizlik carpani) const TOHUM = 20260401; let durum = TOHUM; const rast = () => (durum = (durum * 48271) % 2147483647) / 2147483647; const G = JSON.parse(readFileSync("tahmin.json", "utf8")).map((d) => { const gercek = d.sabit + d.kullanim.reduce((s, [, tutar, r]) => s + tutar * Math.pow(r, 2 * rast() - 1), 0); return { ...d, gercek, hata: gercek - d.tahmin }; }); console.log(`${"kod".padEnd(5)}${"tahmin kb".padStart(11)}${"gerceklesen kb".padStart(16)}` + `${"hata kb".padStart(10)}${"hata %".padStart(9)}${"kaynak".padStart(20)}`); for (const d of G) { const yuzde = d.tahmin === 0 ? 0 : (100 * d.hata) / Math.abs(d.tahmin); console.log(`${d.kod.padEnd(5)}${d.tahmin.toFixed(1).padStart(11)}${d.gercek.toFixed(1).padStart(16)}` + `${d.hata.toFixed(1).padStart(10)}${yuzde.toFixed(0).padStart(9)}` + `${(d.kullanim.length ? `${d.kullanim.length} kullanim kalemi` : "yalniz sabit").padStart(20)}`); } const hatasiz = G.filter((d) => d.hata === 0); const sapan = G.filter((d) => d.hata !== 0); const oran = (d) => d.gercek / d.tahmin; console.log(`\n${hatasiz.length} degisiklikte hata tam olarak sifir (yalniz sabit birim fiyatli ` + `kalem tasiyorlar); ${sapan.length} degisiklikte sapma var`); console.log(`sapma araligi ${Math.min(...sapan.map(oran)).toFixed(2)}x ile ` + `${Math.max(...sapan.map(oran)).toFixed(2)}x arasinda; ` + `toplam tahmin ${G.reduce((s, d) => s + d.tahmin, 0).toFixed(1)} kb, ` + `gerceklesen ${G.reduce((s, d) => s + d.gercek, 0).toFixed(1)} kb ` + `(toplamda %${(100 * (G.reduce((s, d) => s + d.hata, 0) / G.reduce((s, d) => s + d.tahmin, 0))).toFixed(0)})`); // AD23: esik kurali plandaki tahmine bakar: tahmin esigi asarsa degisiklik durur ve elle onaya gider console.log(`\n${"esik kb".padStart(8)}${"duran".padStart(7)}${"gereksiz duran".padStart(16)}` + `${"gecen pahali".padStart(14)}${"dogru duran".padStart(13)}${"onaya giden butce".padStart(19)}`); for (const E of [200, 300, 500, 800]) { const duran = G.filter((d) => d.tahmin > E); const gereksiz = duran.filter((d) => d.gercek <= E); const gecen = G.filter((d) => d.tahmin <= E && d.gercek > E); console.log(`${String(E).padStart(8)}${String(duran.length).padStart(7)}` + `${String(gereksiz.length).padStart(16)}${String(gecen.length).padStart(14)}` + `${String(duran.length - gereksiz.length).padStart(13)}` + `${duran.reduce((s, d) => s + d.tahmin, 0).toFixed(0).padStart(19)}`); } for (const E of [200, 300]) { const ad = (f) => G.filter(f).map((d) => d.kod).join(", ") || "yok"; console.log(`\nesik ${E} kb: duran ${ad((d) => d.tahmin > E)}; ` + `gereksiz duran ${ad((d) => d.tahmin > E && d.gercek <= E)}; ` + `gecen pahali ${ad((d) => d.tahmin <= E && d.gercek > E)}`); }
kod tahmin kb gerceklesen kb hata kb hata % kaynak
G01 204.0 192.3 -11.7 -6 1 kullanim kalemi
G02 460.0 461.5 1.5 0 1 kullanim kalemi
G03 290.0 362.5 72.5 25 1 kullanim kalemi
G04 240.0 121.6 -118.4 -49 1 kullanim kalemi
G05 90.0 90.0 0.0 0 yalniz sabit
G06 370.0 535.2 165.2 45 1 kullanim kalemi
G07 270.0 288.3 18.3 7 1 kullanim kalemi
G08 1120.0 1120.0 0.0 0 yalniz sabit
G09 800.0 958.4 158.4 20 2 kullanim kalemi
G10 -150.0 -150.0 0.0 0 yalniz sabit
G11 408.0 368.8 -39.2 -10 1 kullanim kalemi
G12 260.0 316.2 56.2 22 2 kullanim kalemi
3 degisiklikte hata tam olarak sifir (yalniz sabit birim fiyatli kalem tasiyorlar); 9 degisiklikte sapma var
sapma araligi 0.51x ile 1.45x arasinda; toplam tahmin 4362.0 kb, gerceklesen 4664.7 kb (toplamda %7)
esik kb duran gereksiz duran gecen pahali dogru duran onaya giden butce
200 10 2 0 8 4422
300 5 0 2 5 3158
500 2 0 1 2 1920
800 1 0 1 1 1120
esik 200 kb: duran G01, G02, G03, G04, G06, G07, G08, G09, G11, G12; gereksiz duran G01, G04; gecen pahali yok
esik 300 kb: duran G02, G06, G08, G09, G11; gereksiz duran yok; gecen pahali G03, G12
Hata tablosunun ilk okunuşu yanıltıcıdır. Toplamda tahmin 4.362, gerçekleşen 4.664,7 kurgu birim — yüzde yedi sapma, kabul edilebilir görünür. Ama bu yüzde yedi, tek tek değişikliklerdeki sapmaların birbirini götürmesiyle oluşur: G04 yarı yarıya düşük gerçekleşir (0,51x), G06 yüzde kırk beş yüksek (1,45x), G03 yüzde yirmi beş yüksek. Toplam doğruluk, karar veren kişiye hiçbir şey vermez, çünkü kararlar tek tek değişikliklerde verilir. Bir bütçe raporu için yüzde yedi iyi bir sayıdır; bir kapı için sapmanın 0,51x–1,45x aralığı asıl sayıdır.
İkinci okuma hatanın kaynağını gösterir. Üç değişiklikte hata tam olarak sıfırdır ve bu bir şans değil, yapısal bir sonuçtur: o üçü yalnız sabit birim fiyatlı kalem taşır. Kalan dokuzun hepsinde sapma vardır ve sapmanın büyüklüğü kalemin belirsizlik çarpanıyla birlikte artar. Tahmin hatasını düşürmenin yolu daha iyi bir tahmin aracı değildir; kullanıma bağlı kalemin payını bilmektir. Bir değişikliğin tahmini yanında “bu tutarın yüzde kaçı kullanıma bağlı” sayısı yazılmıyorsa, tahmin tek başına ne kadar güvenilir olduğunu söylemez.
Tabloda tek negatif kalem de aynı kurala uyar: arşiv sınıfının soğuğa alınması aylık 150 birim tasarruf olarak tahmin edilir ve tam olarak 150 birim tasarruf eder. Sabit birim fiyatlı bir kalemde tasarruf da gider kadar kesindir; kullanıma bağlı bir kalemde vaat edilen düşüş ise aynı belirsizliği taşır ve gerçekleşmeyebilir.
Eşik Kuralı
Tahmin bir kapıya bağlandığı anda karar üretmeye başlar: tahmin eşiği aşarsa değişiklik durur ve elle onaya gider. Kuralın iki hatası vardır ve ikisi de tabloda sayılıdır. Gereksiz duran, tahmini eşiği aşan ama gerçekte aşmayan değişikliktir; bedeli bekleyen bir katkı ve harcanan onay zamanıdır. Geçen pahalı, tahmini eşiğin altında kalan ama gerçekte üstüne çıkan değişikliktir; bedeli bütçedeki sürprizdir.
İki yüz birimlik eşik on iki değişikliğin onunu durdurur. Bu eşikte hiçbir pahalı değişiklik kaçmaz — ama duranların ikisi (G01 ve G04) gerçekte eşiğin altında kalır ve onaya giden bütçe 4.422 birime, yani neredeyse toplamın tamamına çıkar. Kapı bu noktada bir kapı olmaktan çıkar: her şey durur, onay bir tören hâline gelir. Üç yüz birimlik eşikte duran sayısı beşe iner ve o beşin beşi de gerçekte eşiği aşar — ama bu kez iki değişiklik (G03 ve G12) tahmini altında kaldığı için geçer ve gerçekte üstüne çıkar.
Sekiz yüz birimlik eşikte yalnız bir değişiklik durur ve bir tanesi kaçar. Hiçbir eşik ikisini birden sıfırlamaz, çünkü eşik bir sayıya bakar ve o sayının kendisi belirsizdir. Kuralı iyileştirmenin yolu eşiği daha iyi seçmek değil, eşiğe bakılan sayıyı değiştirmektir: tahminin kendisi yerine tahminin kullanıma bağlı payı da eşiğe girerse, yalnız sabit kalem taşıyan G08 gibi bir değişiklik yüksek tutarına rağmen otomatik geçebilir; çünkü onun tahmini kesindir.
Bir sınır daha var. Bu tahminlerin girdisi plandır ve plan da uygulamayı tam öngörmez: planda hiç görünmeyen bir kalem tahmine de girmez, planda görünüp gerçekleşmeyen bir kalem ise tahmini şişirir. Buradaki bütün sayılar planın doğru olduğunu varsayar; kayıt ile gerçek arasındaki fark, tahmin hatasının üstüne binen ikinci bir hatadır.
Özet
- On iki değişikliğin tahmini plan farkından üretildi: 24 kalemin 13’ü sabit birim fiyatlı (tahmin edilebilir), 11’i kullanıma bağlıdır (tahmin edilemez); tutarın %56’sı sabit, %44’ü kullanıma bağlıdır ve 12 değişikliğin 9’u en az bir kullanıma bağlı kalem taşır.
- Yalnız sabit kalem taşıyan 3 değişiklikte hata tam olarak sıfırdır; kalan 9’unda sapma 0,51x ile 1,45x arasındadır.
- Toplam sapma %7’dir ve bu sayı aldatıcıdır: tek tek sapmalar birbirini götürür, oysa kararlar değişiklik başına verilir.
- Eşik kuralının iki hatası ters yönde çalışır: 200 birimlik eşik 12 değişikliğin 10’unu durdurur (2’si gereksiz, onaya giden bütçe 4.422 birim) ve hiçbir pahalı değişikliği kaçırmaz; 300 birimde 5 durur ve 2 pahalı değişiklik geçer; 800 birimde 1 durur ve 1 pahalı değişiklik geçer.
- Tahmin hatasını düşürmenin yolu daha iyi bir araç değil, tahminin kullanıma bağlı payını raporlamaktır; eşik o paya da bakarsa kesin tahminli yüksek tutarlı değişiklik gereksiz yere durmaz.
Sonraki Adım
Bu konunun buraya kadarki dersleri kaydı denetledi, politikadan geçirdi, sınadı ve fiyatladı. Dördü de aynı şeye baktı: kaynağın kendisine — makine, disk, ağ, kuyruk, depo. Ama bir makine yaratıldığında içi boştur; üzerinde hangi paketin kurulu olacağı, hangi dosyanın hangi izinle duracağı, hangi servisin açık kalacağı sağlama katmanının kaydında yazmaz. Sonraki ders bu sınırın öte yanını ele alır: makine yaratıldıktan sonra devreye giren yapılandırma yönetimi katmanı, ajanlı ve ajansız yaklaşımların farkı ve bu katmanın kendi kaydının sağlama kaydıyla nerede örtüşüp nerede ayrıldığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.