İçeriğe geç
academia.sh

Ders 15 / 15

Paralel Çalıştırma

Uçtan uca takımın işçilere bölünmesiyle kazanılan sürenin ve kaybedilen yalıtımın ölçülmesi; seri payın hızlanmaya koyduğu tavan, paylaşılan düzeneğin ürettiği çakışmalar, yalnız paralelde görünen yarış kusuru ve yalıtımın hem bedeli hem kör noktası.

İçindekiler

Önceki dersin bütün testleri tek tek koştu ve karantina tartışmasının arkasında hep aynı sayı duruyordu: koşum süresi. Buradaki soru artık hangi testin yazılacağı değil, testlerin sırayla koşmasının bir zorunluluk mu yoksa bir seçim mi olduğudur.

Seçimdir, ve bedeli vardır. Bir takımı işçilere bölmek süreyi kısaltır; aynı işlem test yalıtımını (test isolation) da zorunlu kılar, çünkü sırayla koşarken zararsız olan her paylaşım artık aynı anda kullanılır. Test yalıtımı, İleri SQL kursundaki yalıtım düzeyiyle karıştırılmamalıdır: orada eşzamanlı işlemlerin birbirinin verisini görme kuralı, burada iki testin birbirinin düzeneğine dokunmaması söz konusudur. Bu ders iki tarafı da sayar: kazanılan süreyi ve yalıtım için ödenen bedeli.

Süre ve Seri Pay

Toplam süre yalnız işin bölünmesiyle değil, bölünemeyen parçayla da belirlenir: takım kurulumu, derleme ve raporlama her koşumda bir kez ve tek parça çalışır. Bu, Sistem Tasarımına Giriş kursundaki Yatay ve Dikey Ölçekleme dersinde kurulan seri paydır; aritmetiği burada tekrarlanmaz, yalnız uçtan uca takıma uygulanır.

PC1 — kırk test: yirmisi üç birim, on ikisi altı birim, sekizi on beş birim; seri pay on iki birim. Süreler model birimidir, makineye bağlı değildir. Dağıtım en uzun testten başlayarak en boş işçiye yerleştirir.

// zamanlama.mjs — kirk testin isci sayisina gore toplam suresi (model birimi)
const sureler = [
  ...Array(20).fill(3), ...Array(12).fill(6), ...Array(8).fill(15),
];
const SERI = 12;

const dagit = (isciSayisi) => {
  const yuk = Array(isciSayisi).fill(0);
  for (const sure of [...sureler].sort((a, b) => b - a)) {
    const enAz = yuk.indexOf(Math.min(...yuk));
    yuk[enAz] += sure;
  }
  return Math.max(...yuk);
};

const toplamIs = sureler.reduce((a, b) => a + b, 0);
const enUzun = Math.max(...sureler);
const tek = SERI + dagit(1);
const s = (n, g) => String(n).padStart(g);
console.log(`${'isci'.padEnd(6)}${s('paralel', 9)}${s('toplam', 8)}${s('hizlanma', 10)}${s('seri pay', 10)}`);
for (const isci of [1, 2, 4, 8, 16, 40]) {
  const paralel = dagit(isci);
  const toplam = SERI + paralel;
  console.log(`${String(isci).padEnd(6)}${s(paralel, 9)}${s(toplam, 8)}`
    + `${s(`${(tek / toplam).toFixed(2)}x`, 10)}${s(`%${((SERI / toplam) * 100).toFixed(1)}`, 10)}`);
}
console.log(`is ${toplamIs} birim, seri ${SERI} birim, en uzun test ${enUzun} birim, taban ${SERI + enUzun} birim`);
isci    paralel  toplam  hizlanma  seri pay
1           252     264     1.00x      %4.5
2           126     138     1.91x      %8.7
4            63      75     3.52x     %16.0
8            33      45     5.87x     %26.7
16           18      30     8.80x     %40.0
40           15      27     9.78x     %44.4
is 252 birim, seri 12 birim, en uzun test 15 birim, taban 27 birim

İlk iki satır beklendiği gibidir: iki işçi süreyi neredeyse yarıya indirir. Sonrası öyle değil. Sekiz işçide hızlanma beş virgül seksen yedi, kırk işçide dokuz virgül yetmiş sekiz kalır — test sayısı kadar işçi verilse bile onda birin altına inilemez. İki sınır çalışıyor: seri pay toplamın yüzde kırk dörde kadar çıkan bir parçasını tutuyor, ve hiçbir dağıtım en uzun testten kısa süremez. Yirmi yedi birimlik taban budur.

Buradan çıkan iki iş vardır ve ikisi de işçi eklemekten önce gelir: seri payı küçültmek ve en uzun testi bölmek. İşçi sayısını artırmak, bu ikisi yapılmadan, ödenen bedeli karşılamaz.

Paylaşılan Düzenek

Ödenen bedel yalıtımdır. Aşağıdaki koşum dört testi işçilere dağıtır ve her testi adımlarına ayırıp işçiler arasında sırayla ilerletir; paralel koşumun testleri iç içe geçirmesi burada gerçekten olur. Her test bir üye kaydı kurar, açık ödünç sayısını okur, ödünç yazar, sınırın aşılmadığını doğrular ve düzeneğini yıkar.

Üç seçenek argümanla verilir: kaç işçi, ödünç kararının iki adımda mı (önce oku, sonra yaz) yoksa tek adımda mı verildiği, ve düzeneğin paylaşılıp paylaşılmadığı.

// kosum.mjs — dort testin isci sayisi, servis kipi ve yalitim kipine gore kosumu
import { DatabaseSync } from 'node:sqlite';

const [isciSayisi = '1', servis = 'iki-adim', kip = 'paylasilan'] = process.argv.slice(2);
const ISCI = Number(isciSayisi);
const TEST = 4;
const SINIR = 1;
const olcum = { adim: 0, kurulumSorgusu: 0, cakisma: 0, dusen: [] };

const semaKur = () => {
  const db = new DatabaseSync(':memory:');
  db.exec(`CREATE TABLE uye (uye_no TEXT PRIMARY KEY);
           CREATE TABLE odunc (uye_no TEXT, kitap_no TEXT UNIQUE)`);
  olcum.kurulumSorgusu += 2;
  return db;
};

const veritabanlari = kip === 'ayri-sema'
  ? Array.from({ length: ISCI }, semaKur)
  : Array(ISCI).fill(semaKur());

const say = (db, uyeNo) => db.prepare('SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ?').all(uyeNo)[0].n;

const testYap = (no, isci) => {
  const db = veritabanlari[isci];
  const uyeNo = kip === 'paylasilan' ? 'U-17' : `U-${no}`;
  const kitapNo = `K-${no}`;
  const yerel = {};
  const yaz = () => db.prepare('INSERT INTO odunc VALUES (?, ?)').run(uyeNo, kitapNo);
  const adimlar = [
    () => {
      olcum.kurulumSorgusu += 1;
      try { db.prepare('INSERT INTO uye VALUES (?)').run(uyeNo); } catch { olcum.cakisma += 1; }
    },
    ...(servis === 'iki-adim'
      ? [() => { yerel.n = say(db, uyeNo); }, () => { if (yerel.n < SINIR) yaz(); }]
      : [() => { if (say(db, uyeNo) < SINIR) yaz(); }]),
    () => {
      const n = say(db, uyeNo);
      if (n > SINIR) olcum.dusen.push(`test ${no}: acik odunc ${n}, sinir ${SINIR}`);
    },
    () => {
      db.prepare('DELETE FROM odunc WHERE kitap_no = ?').run(kitapNo);
      db.prepare('DELETE FROM uye WHERE uye_no = ?').run(uyeNo);
    },
  ];
  return { adimlar, i: 0 };
};

const kuyruklar = Array.from({ length: ISCI }, () => []);
for (let no = 1; no <= TEST; no += 1) kuyruklar[(no - 1) % ISCI].push(testYap(no, (no - 1) % ISCI));

while (kuyruklar.some((k) => k.length > 0)) {
  for (const kuyruk of kuyruklar) {
    const t = kuyruk[0];
    if (t === undefined) continue;
    t.adimlar[t.i]();
    olcum.adim += 1;
    t.i += 1;
    if (t.i === t.adimlar.length) kuyruk.shift();
  }
}

console.log(`isci ${ISCI}  servis ${servis}  kip ${kip}`);
console.log(`adim ${olcum.adim}  kurulum sorgusu ${olcum.kurulumSorgusu}`
  + `  kurulum cakismasi ${olcum.cakisma}  dusen test ${olcum.dusen.length}/${TEST}`);
for (const satir of olcum.dusen) console.log(satir);

Tek işçiyle, yani sırayla, takım temizdir.

node kosum.mjs 1 iki-adim paylasilan
isci 1  servis iki-adim  kip paylasilan
adim 20  kurulum sorgusu 6  kurulum cakismasi 0  dusen test 0/4

Yalnız Paralelde Düşen Test

Aynı kod, aynı veri, iki işçi.

node kosum.mjs 2 iki-adim paylasilan
isci 2  servis iki-adim  kip paylasilan
adim 20  kurulum sorgusu 6  kurulum cakismasi 2  dusen test 4/4
test 1: acik odunc 2, sinir 1
test 2: acik odunc 2, sinir 1
test 3: acik odunc 2, sinir 1
test 4: acik odunc 2, sinir 1

İki ayrı olay birlikte görünüyor. Birincisi kurulum çakışmasıdır: iki test aynı üye kaydını aynı anda kurmaya çalıştı ve ikisinden biri teklik kısıtına takıldı. Bu, testin kendi düzeneğine ait bir sorundur.

İkincisi bir ürün kusurudur ve adı kontrol-sonra-yaz yarışıdır: ödünç kararı iki adımda veriliyor — önce açık ödünç sayısı okunuyor, sonra kayıt yazılıyor. İki adım arasında başka bir işçinin yazması, okunan sayıyı geçersiz kılar. İki test de sıfır okudu, ikisi de yazdı, üyenin bir olan sınırı ikiye çıktı. Sıralı koşumda bu kusur hiçbir zaman görünmez; önceki koşumun sıfır düşüşü onun kanıtıdır. Paralel çalıştırmanın yakaladığı sınıf budur.

Düzeltme, kararı bölünemez tek bir adımda vermektir.

node kosum.mjs 2 tek-adim paylasilan
isci 2  servis tek-adim  kip paylasilan
adim 16  kurulum sorgusu 6  kurulum cakismasi 2  dusen test 0/4

Testler yeşile döndü, ama kurulum çakışması iki olarak duruyor — ve bu satır ikinci bir uyarıdır. Çakışmaya uğrayan iki test kendi üyesini kuramadı; ödünç istekleri sınıra takılıp sessizce reddedildi ve doğrulama yine de geçti. Yeşil oldukları hâlde sınamak istedikleri şeyi sınamadılar. Düzeneğin paylaşılması, testi düşürmediğinde bile anlamını boşaltır.

Yalıtımın Bedeli ve Kör Noktası

Yalıtımın iki düzeyi vardır: her teste ayrı kayıt kümesi vermek ya da her işçiye ayrı şema kurmak. İkisi de aynı kusurla, iki adımlı servisle koşuluyor.

node kosum.mjs 2 iki-adim ayri-kayit
isci 2  servis iki-adim  kip ayri-kayit
adim 20  kurulum sorgusu 6  kurulum cakismasi 0  dusen test 0/4
node kosum.mjs 2 iki-adim ayri-sema
isci 2  servis iki-adim  kip ayri-sema
adim 20  kurulum sorgusu 8  kurulum cakismasi 0  dusen test 0/4

Bedel iki sayıda görünür. Ayrı kayıt kümesi çakışmayı sıfıra indirir ve hiçbir şey eklemez; ayrı şema kurulum sorgusunu altıdan sekize çıkarır, çünkü her işçi kendi tablolarını yaratır. İşçi sayısı arttıkça bu fark doğrusal büyür: şema kurulumu işçi başına ödenir, kayıt kümesi ise test başına.

Kaçan sınıf da tam burada. İki koşum da yeşil, oysa kusur yerinde duruyor — servis hâlâ iki adımda karar veriyor. Yalıtım, iki testin aynı üyeye aynı anda dokunmasını engellediği için yarışın koşulunu ortadan kaldırdı. Yalıtım kırılganlığı giderirken, kırılganlığı üreten paylaşımın kendisine bağlı kusur sınıfını da görünmez kılar. Gerçek kullanıcılar kayıtları paylaşır; test takımı paylaşmadığı sürece o yarış yalnız üretimde ortaya çıkar.

Bu yüzden yalıtım kararı bir denge kararıdır: koşum düzeninin yalıtımı tam, ürünün eşzamanlılık denetimi ise ayrıca sınanmalıdır. Bedelin koşuma bağlı yanı bu koşumda küçüktür — beş yapılandırmanın her biri yirmi milisaniyenin altında sürdü, çünkü koşan şey süreç içi bir modeldir. Koşumdan bağımsız sayılar ölçümlerdedir: yirmi adım, altı ile sekiz arası kurulum sorgusu, iki çakışan kayıt ve yüzde dört buçuktan yüzde kırk dörde çıkan seri pay.

Özet

  • İşçi eklemek süreyi seri pay ve en uzun test kadar kısaltır: kırk testte kırk işçi bile dokuz virgül yetmiş sekiz kat hızlanma verdi, taban yirmi yedi birimde kaldı.
  • Paylaşılan düzenek paralelde iki kurulum çakışması üretti; çakışan test düşmese bile sınamak istediği şeyi sınamamış olur.
  • Kontrol-sonra-yaz yarışı yalnız paralel koşumda göründü: sıralı koşumda sıfır düşüş, iki işçide dört testin dördü de düştü.
  • Kararı tek adıma indirmek testleri yeşile döndürdü; yalıtım ise aynı kusur yerinde dururken koşumu yeşil yaptı.
  • Yalıtımın bedeli ölçülebilir: ayrı kayıt kümesi kurulum sorgusunu artırmadı, ayrı şema altıdan sekize çıkardı ve bu fark işçi başına büyür.

Kurs Kapanışı

Bu kursun on beş dersi aynı üç soruyu yanıtladı. Tablodaki her satır bir test biçiminin ne gördüğünü, ne göremediğini ve ne kadara mal olduğunu söyler.

Ders Yakaladığı hata sınıfı Yakalamadığı Bedeli
Tümleşim Testinin Kapsamı şema uyuşmazlığı muhatap kimliği uyuşmazlığı tur başına 4 sorgu, 9 satır düzenek
Test Ortamı Yönetimi bilinmeyen muhatabın reddi süreç ömrüne bağlı durum 1 süreç, 6 adım, koşum başına 1 dosya
Test Verisi Yönetimi dağılıma bağlı rapor kusuru içeriğe bağlı dışa aktarma kusuru 772 yükleme sorgusu, 78 kilobayt
Veritabanı Testleri geri göçte veri kaybı geri doldurma değeri uyuşmazlığı 13 göç adımı, 21 sorgu, 1 dosya
Dış Servis Taklidi istek biçimi kayması dondurulmuş yanıtın eskimesi oynatmada 0 süreç, 0 ağ çağrısı
API Testi yapısal uyuşmazlık anlamsal uyuşmazlık 2 süreç, sınama başına 1 istek, 16 kural
Tüketici Güdümlü Sözleşme Testi sözleşmedeki alanın kaybolması çalıştırılmamış dalın okuduğu alan 2 bileşen, 100 istek, 235 baytlık dosya
Şema Uyumluluk Denetimi kırıcı değişiklik biçimi bozmadan anlamı değiştiren değişiklik 0 süreç, 0 istek, 9 satırlık sınıf tablosu
Sahte Sunucularla Geliştirme biçim sapması durum sapması 2 süreç, 10 istek, 15 satır düzenek
Test Piramidi sınır karşılaştırması, birim seviyesinde durum kodu eşlemesi, alt seviyelerde 24 vaka için 242 sorgu ve 24 istek
Tarayıcı Otomasyonu kart durumunun güncellenmemesi düğme etiketinin yanlışlığı 2000 yüklemede 16.477 yoklama
Mobil Otomasyonu sürüme bağlı tarih ayrıştırma hücreye özgü kusurun 24/30’u 12 hücre koşumu, 30 hücrelik matris
Görsel Doğrulama düğme tonunun açılması etkisiz düğme, aynı pikselleri üretir görüntü 800 piksel, dışlamayla sayılan 784
Kırılganlık Yönetimi reddedilen istekte uyarının yokluğu karantinadaki testin koruduğu sınıf 40 test × 200 koşum, 8000 test koşumu
Paralel Çalıştırma kontrol-sonra-yaz yarışı yalıtımın gizlediği paylaşım kusuru kurulum sorgusu 6’dan 8’e, seri pay %4,5’ten %44,4’e

Orta sütun kursun kuralıdır: bir test katmanının değeri ancak sınırıyla birlikte anlatılabilir. On beş satırın hiçbirinde “daha güvenli” bir seçenek yoktur; her satırda yakalanan bir sınıf, kaçan bir sınıf ve ödenen bir bedel vardır. Bir test biçimini savunmanın tek yolu bu üçlüyü yazmaktır, ve bir biçimi eklemenin gerekçesi ancak başka hiçbir katmanda karşılığı olmayan bir kusur sınıfıdır.

Kursun kendi sınırı da aynı kuralla yazılır. Birinci konu sahte bağımlılıkların gerçeğe uyup uymadığını sordu, ikincisi sözleşmenin denetlenebilir parçasını ayırdı, üçüncüsü kullanıcının gördüğü yolu bir bütün olarak koşturdu. Üçünün de sorduğu soru aynıydı: işlev doğru mu? Sistemin yük altında nasıl davrandığı, saldırı altında ne verdiği ve arıza altında nasıl bozulduğu hiçbir derste sınanmadı. İşlevsel Olmayan Test kursu bu üç soruyu ele alır.

İ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