İçeriğe geç
academia.sh

Ders 04 / 15

Servisler Arası Sözleşme

Şema değişikliğinin maliyetinin kırılan tüketici sayısıyla ölçülmesi: beş iç tüketicinin okuma kümeleriyle tek adımda yayının kaç servisi birlikte yayımlamaya zorladığının sayılması, aynı değişikliğin genişlet–daralt sırasına bölünerek kırılan tüketicinin sıfıra indirilmesi ve geçiş penceresinde iki adın birden taşınmasının kayıt baytına ve iç sınır bant genişliğine ödettiğinin hesaplanması.

İçindekiler

Önceki ders çağıranın bilmek zorunda olduğu adları saydı ve sayının altı ile sekiz arasında değiştiğini gösterdi. Sayının kendisi tek başına bir şey söylemez; anlamı, o adlardan biri değiştiğinde ortaya çıkar. Ağ geçidi de tek çağıran değildir — aynı gönderi kaydını gün sonu ücretlendirme işi, ön yüz için kurulan arayüz, rapor akışı ve teslimat planlayıcı da okur ve her biri farklı bir alan altkümesine bakar.

Kırıcı değişikliğin tanımı, şemanın iki yönü ve sürüm numarasının farktan türetilmesi Web API Tasarımı kursunda kuruldu; burada yeniden anlatılmaz. O kurs maliyeti tek bir eski istemcide ölçmüştü. Servis sınırında ölçü değişir: tüketiciler bizim servislerimizdir, sayıları bellidir ve okuma kümeleri okunabilir. Bu dersin ölçtüğü sayı kaç tüketicinin kırıldığı, karşılığında ölçtüğü sayı kaç birimin birlikte yayımlanmak zorunda kaldığıdır.

Şema, Tüketiciler ve Değişiklik

Ele alınan şema önceki dersin gönderi kaydıdır. Değişiklik önerisi dört adı düzeltiyor ve bir alan ekliyor: bölge alanı hangi bölge olduğunu söylemiyordu, ağırlık gram cinsindendi, rota yalnız bölge kodları taşıyordu ve taşıyıcı alanının bir kod olduğu adından anlaşılmıyordu.

// sozlesme/sema.mjs — gonderi kaydinin iki semasi, bes ic tuketici ve kirilma kurali
export const V1 = { takipNo: "dizgi", durum: "dizgi", bolge: "dizgi", guncellenme: "dizgi",
  rota: "dizi-dizgi", tasiyici: "dizgi", agirlikGram: "tamsayi", hacimDm3: "tamsayi",
  sozlesmeNo: "dizgi", cikisSubesi: "dizgi", varisSubesi: "dizgi" };

// V2 dort adi degistiriyor ve bir alan ekliyor; bolge->varisBolge, agirlikGram->agirlikKg,
// rota->rotaAdimlari (tip de degisiyor), tasiyici->tasiyiciKodu.
export const V2 = { takipNo: "dizgi", durum: "dizgi", varisBolge: "dizgi", guncellenme: "dizgi",
  rotaAdimlari: "dizi-nesne", tasiyiciKodu: "dizgi", agirlikKg: "sayi", hacimDm3: "tamsayi",
  sozlesmeNo: "dizgi", cikisSubesi: "dizgi", varisSubesi: "dizgi", teslimTahmini: "dizgi" };

export const ESLEME = { bolge: "varisBolge", agirlikGram: "agirlikKg",
  rota: "rotaAdimlari", tasiyici: "tasiyiciKodu" };

export const TUKETICI = {
  "ag-gecidi": ["durum", "bolge", "guncellenme", "rota"],
  "on-yuz-arayuzu": ["durum", "guncellenme", "varisSubesi"],
  "rapor-akisi": ["takipNo", "durum", "bolge", "tasiyici"],
  "toplu-ucretlendirme": ["takipNo", "sozlesmeNo", "agirlikGram", "hacimDm3"],
  "teslimat-planlayici": ["rota", "tasiyici", "cikisSubesi", "varisSubesi"],
};

// Bir tuketici, okudugu ad semada yoksa ya da tipi degistiyse kirilir.
export const kirilan = (sema, okuma) => Object.entries(okuma)
  .filter(([, alanlar]) => alanlar.some((a) => sema[a] === undefined || sema[a] !== V1[a]))
  .map(([ad]) => ad);

export const ORNEK = {
  takipNo: "TR-4821", durum: "dagitimda", bolge: "35", guncellenme: "2026-03-11T08:24:00Z",
  rota: ["34", "41", "35"], tasiyici: "T3", agirlikGram: 2400, hacimDm3: 12,
  sozlesmeNo: "S7", cikisSubesi: "34-021", varisSubesi: "35-114",
  varisBolge: "35", rotaAdimlari: [{ bolge: "34", an: "T08" }, { bolge: "41", an: "T14" },
    { bolge: "35", an: "T20" }], tasiyiciKodu: "T3", agirlikKg: 2.4, teslimTahmini: "2026-03-12",
};
export const kayit = (sema) => Object.fromEntries(Object.keys(sema).map((a) => [a, ORNEK[a]]));

Genişlet–daralt (expand–contract), aynı değişikliği üç adıma bölen yayın sırasıdır. Birinci adımda sağlayıcı yeni adları eskilerin yanına ekler ve ikisini birden doldurur. İkinci adımda tüketiciler tek tek yeni ada geçer. Üçüncü adımda sağlayıcı eski adları kaldırır. Aşağıdaki koşum iki yayın düzenini aynı değişiklik üzerinde karşılaştırır.

// sozlesme/olc.mjs — ayni degisiklik iki yayin duzeninde: tek adimda ve genislet-daralt sirasiyla
import { V1, V2, ESLEME, TUKETICI, kirilan, kayit } from "./sema.mjs";

const GECIS = { ...V1, ...V2 };                       // genislet adimi: eski ve yeni adlar bir arada
const gocmus = Object.fromEntries(Object.entries(TUKETICI)
  .map(([ad, a]) => [ad, a.map((x) => ESLEME[x] ?? x)]));
const tuketici = Object.keys(TUKETICI).length;

console.log(`sema alani V1 ${Object.keys(V1).length}, gecis ${Object.keys(GECIS).length}, ` +
  `V2 ${Object.keys(V2).length}; ic tuketici ${tuketici}`);
const sayim = {};
for (const alanlar of Object.values(TUKETICI)) for (const a of alanlar) sayim[a] = (sayim[a] ?? 0) + 1;
const paylasilan = Object.entries(sayim).sort((x, y) => y[1] - x[1]).slice(0, 4);
console.log(`en cok okunan alanlar: ${paylasilan.map(([a, n]) => `${a}=${n}`).join(", ")}\n`);

const k1 = kirilan(V2, TUKETICI);
console.log(`tek adimda yayin -> kirilan tuketici ${k1.length}/${tuketici}: ${k1.join(", ")}`);
console.log(`  birlikte yayimlanmasi gereken birim = ${k1.length + 1} (saglayici + kirilanlar)\n`);

const ADIM = [
  ["1 genislet (iki ad birden)", GECIS, TUKETICI, 1],
  ["2 tuketiciler yeni ada gecer", GECIS, gocmus, Object.entries(TUKETICI)
    .filter(([ad, a]) => a.some((x) => ESLEME[x])).length],
  ["3 daralt (eski adlar kalkar)", V2, gocmus, 1],
];
console.log(`${"adim".padEnd(30)}${"semadaki alan".padStart(14)}${"kirilan tuketici".padStart(18)}` +
  `${"yayimlanan birim".padStart(18)}${"kayit bayt".padStart(12)}`);
let birim = 0;
for (const [ad, sema, okuma, n] of ADIM) {
  birim += n;
  const kirik = Object.entries(okuma).filter(([, alanlar]) =>
    alanlar.some((a) => sema[a] === undefined)).length;
  console.log(`${ad.padEnd(30)}${String(Object.keys(sema).length).padStart(14)}` +
    `${`${kirik}/${tuketici}`.padStart(18)}${String(n).padStart(18)}` +
    `${String(Buffer.byteLength(JSON.stringify(kayit(sema)))).padStart(12)}`);
}
console.log(`toplam: 3 yayin adimi, ${birim} birim yayini, 0 kirilan tuketici\n`);

// K01 Kabaca Buyukluk Hesabi: tepe okuma 416.67 istek/s ve okuma cikisi 1.60 Mbit/s
const OKUMA_TEPE = 416.67, K01_CIKIS = 1.6;
const mbit = (bayt) => (OKUMA_TEPE * bayt * 8) / 1e6;
console.log(`${"sema".padEnd(10)}${"kayit bayt".padStart(12)}${"ic sinir Mbit/s".padStart(17)}` +
  `${"K01 okuma cikisina oran".padStart(26)}`);
for (const [ad, s] of [["V1", V1], ["gecis", GECIS], ["V2", V2]]) {
  const b = Buffer.byteLength(JSON.stringify(kayit(s)));
  console.log(`${ad.padEnd(10)}${String(b).padStart(12)}${mbit(b).toFixed(3).padStart(17)}` +
    `${(mbit(b) / K01_CIKIS).toFixed(2).padStart(26)}`);
}
const gB = Buffer.byteLength(JSON.stringify(kayit(GECIS)));
const vB = Buffer.byteLength(JSON.stringify(kayit(V1)));
console.log(`gecis penceresinin bedeli = ${gB - vB} bayt/kayit, ` +
  `${(mbit(gB) - mbit(vB)).toFixed(3)} Mbit/s (kayit ${(gB / vB).toFixed(2)} katina cikiyor)`);
sema alani V1 11, gecis 16, V2 12; ic tuketici 5
en cok okunan alanlar: durum=3, bolge=2, guncellenme=2, rota=2

tek adimda yayin -> kirilan tuketici 4/5: ag-gecidi, rapor-akisi, toplu-ucretlendirme, teslimat-planlayici
  birlikte yayimlanmasi gereken birim = 5 (saglayici + kirilanlar)

adim                           semadaki alan  kirilan tuketici  yayimlanan birim  kayit bayt
1 genislet (iki ad birden)                16               0/5                 1         406
2 tuketiciler yeni ada gecer              16               0/5                 4         406
3 daralt (eski adlar kalkar)              12               0/5                 1         334
toplam: 3 yayin adimi, 6 birim yayini, 0 kirilan tuketici

sema        kayit bayt  ic sinir Mbit/s   K01 okuma cikisina oran
V1                 228            0.760                      0.48
gecis              406            1.353                      0.85
V2                 334            1.113                      0.70
gecis penceresinin bedeli = 178 bayt/kayit, 0.593 Mbit/s (kayit 1.78 katina cikiyor)

Kırılan Tüketici Sayısı

Bu sayılar hesap sınıfındadır: okuma kümelerinden ve şema tanımlarından aritmetikle çıkar.

Tek adımda yayında beş tüketicinin dördü kırılıyor. Kırılmayan tek tüketici, adı değişen dört alandan hiçbirini okumayan ön yüz arayüzü. Bu satırın sonucu bir sürüm numarası değil, bir yayın kısıtıdır: sağlayıcı ve dört tüketici aynı anda yayımlanmak zorundadır. Beş birimin tek bir yayında birlikte hareket etmesi, servislere bölmenin gerekçesini geri alır — bölünmenin amacı birimlerin ayrı ayrı yayımlanabilmesiydi.

İkinci satır maliyetin nereden geldiğini gösteriyor: durum alanını üç tüketici, bolge, guncellenme ve rota alanlarını ikişer tüketici okuyor. Bir alanın değiştirilme maliyeti onu okuyan tüketici sayısıdır. Önceki dersin ayrımı burada sayıya dönüşüyor — paylaşılan bir kayıt alanı, tek bir çağıran için yazılmış bir yordam adından pahalıdır.

Sıraya Bölmenin Sonucu

Alttaki tablo aynı değişikliği üç adıma böldüğünde kırılan tüketicinin her adımda 0 kaldığını gösteriyor. Birinci adımda şema on bir alandan on altıya çıkıyor ve eski adlar yerinde kaldığı için hiçbir tüketici değişmiyor. İkinci adımda dört tüketici yeni adlara geçiyor ve bunu ayrı ayrı yapabiliyor; sıraları önemsizdir, çünkü iki ad da doludur. Üçüncü adımda eski adlar kalkıyor ve şema on iki alana iniyor.

İki düzenin karşılaştırması iki sayıyla yapılır. Tek adımda yayın: 1 yayın adımı, 5 birim aynı anda, 4 kırılan tüketici. Genişlet–daralt: 3 yayın adımı, 6 birim yayını, 0 kırılan tüketici. Bir birim fazla yayımlanıyor ve karşılığında hiçbir birim ötekini beklemiyor.

Sıranın gerektirdiği bir kural vardır ve tabloda görünmez: birinci adım yayımlandıktan sonra tüketicilerin gerçekten göç ettiğini bilmek gerekir. Yoksa üçüncü adım tek adımda yayının kendisine dönüşür. Bunu bilmenin yolu Web API Tasarımı kursunda kurulan sözleşme sınamalarıdır; tüketici beklentileri yazılıysa üçüncü adımın güvenli olup olmadığı yayından önce sınanabilir.

Hesaba Geri Dönüş

Alttaki tablo geçişin faturasını veriyor. Gönderi kaydı V1’de 228 bayt; geçiş penceresinde iki ad birden taşındığı için 406 bayta çıkıyor, yani 1,78 katına. V2’ye inildiğinde 334 baytta kalıyor — V1’in üstünde, çünkü rota adımları artık nesne taşıyor ve bir alan eklendi.

K01’in tepe okuma hızıyla çarpıldığında iç sınırdan geçen bant genişliği 0,760 Mbit/s’den 1,353 Mbit/s’ye çıkıyor: geçiş penceresinin bedeli 0,593 Mbit/s. Karşılaştırma noktası K01’in okuma çıkışıdır (1,60 Mbit/s); geçiş penceresinde iç sınır bu sayının 0,85 katına yaklaşıyor, oysa V1’de 0,48 katındaydı.

Buradan çıkan sonuç geçiş penceresinin bir süre kararı olmasıdır. Pencere açık kaldığı sürece her kayıt yüzde yetmiş sekiz fazla bayt taşır; kapatmak için bütün tüketicilerin göç etmesi beklenir. Uzun pencere kırılma riskini sıfırda tutar ve sürekli bir bant genişliği vergisi öder; kısa pencere vergiyi keser ve göçü tamamlamayan bir tüketici kalırsa kırılmayı geri getirir. İki uçtan hangisinin seçileceği, üçüncü adımın güvenli olduğunu gösteren sınamaya bağlıdır.

Özet

  • Servis sınırında şema değişikliğinin ölçüsü kırılan tüketici sayısı ve birlikte yayımlanmak zorunda kalan birim sayısıdır; kırıcılık tanımı ve sürüm türetimi Web API Tasarımı kursunda kuruldu ve burada tekrarlanmaz.
  • Dört adı değiştiren bir öneri beş iç tüketicinin dördünü kırıyor ve sağlayıcıyla birlikte beş birimin aynı anda yayımlanmasını gerektiriyor.
  • Bir alanın değiştirilme maliyeti onu okuyan tüketici sayısıdır: durum üç, bolge, guncellenme ve rota ikişer tüketici tarafından okunuyor.
  • Genişlet–daralt sırası aynı değişikliği 3 yayın adımı ve 6 birim yayınıyla, 0 kırılan tüketiciyle yapıyor; ikinci adımda tüketiciler birbirini beklemeden göç ediyor.
  • Sıranın güvenliği üçüncü adıma bağlıdır: göçün tamamlandığı sınanmadan eski adlar kaldırılırsa düzen tek adımda yayına döner.
  • K01’e dönüş: kayıt 228 baytken geçiş penceresinde 406 bayta (1,78 katı) çıkıyor ve iç sınır 0,760’tan 1,353 Mbit/s’ye yükseliyor — 0,593 Mbit/s’lik bir pencere vergisi, K01’in 1,60 Mbit/s okuma çıkışının 0,85 katı.

Sonraki Adım

Dört ders sonunda çağrı yapılabilir durumda: servisler durumsuz, adresleri bulunuyor, konuşma biçimi seçilmiş ve sözleşmeleri kırılmadan evrilebiliyor. Bir soru hâlâ açık ve o soru bu konunun en baştaki ölçüsüne geri döner. Ağ geçidi iki servisi çağırdığında birinden yanıt gelmezse ne kadar bekleyecek, kaç kez deneyecek ve bu bekleme uçtan uca yanıta ne ekleyecek. K01 takip yanıtı için 200 milisaniyelik bir eşik koymuştu ve zincirdeki adım sayısı üçüncü derste 2 olarak ölçülmüştü. Sonraki ders o eşiği bir bütçe olarak ele alır: zaman aşımı ile deneme sayısının çarpımının en kötü durumda ne verdiğini, bütçenin adımlara nasıl bölüşüldüğünü ve deneme bütçesi konmadığında iç istek hızının kaça katlandığını ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat