İçeriğe geç
academia.sh

Ders 15 / 21

Veritabanı Göçleriyle Eşgüdüm

Altı göç türü gerçekten uygulanıp tersi koşturulur ve geri alınabilirlik, komutun hatasız çalışmasıyla değil tablo içeriğinin özdeşliğiyle ölçülür. Genişlet–taşı–daralt düzeni adım adım koşar; her adımda eski ile yeni sürümün birlikte çalışıp çalışamadığı sayılır ve uyumsuz adımın yayın penceresine ve geri alma planına etkisi yazılır.

İçindekiler

Önceki dersin üç senaryosundan ikisinde dönüşü olmayan bir nokta vardı ve veri senaryosunda o nokta ters göç adımıydı. Bir geri alma planı ancak içindeki her adım kadar geri alınabilirdir; şema değişikliği o adımların en kırılganıdır. Kod bir çıktı seçilerek, yapılandırma bir değer yazılarak geri gelir. Şema için bu her zaman doğru değildir.

Bu dersin ekseni geri alınabilirliktir. Göç ile kod yayınının sırası ve o sıranın hata alan istek sayısı M16/K08’de ölçüldü; burada sorulan soru sıra değil, sınıftır: hangi göç türü tam olarak geri alınabilir, hangisi çalışır görünüp veriyi eksik getirir, hangisinden dönüş yoktur. İkinci ölçü, adımların iki sürümü birlikte taşıyıp taşımadığıdır — geri alma planının yayın penceresiyle kesiştiği yer burasıdır.

  • YS37. Şema tek bir veritabanında durur ve bütün sürümler aynı tabloyu okuyup yazar.
  • YS38. Göç adımları uygulama yayınından ayrı koşar; ikisi aynı işlemin parçası değildir.
  • YS39. Geri alınabilirlik, ters işlem koşturulduktan sonra tablo içeriğinin özdeş olmasıyla ölçülür. Komutun hatasız çalışması yeterli sayılmaz.
  • YS40. İki sürümün uyumluluğu, her birinin ötekinin yazdığı kaydı doğru okuyabilmesidir. Sorgunun hata vermemesi yeterli değildir; okunan değer de doğru olmalıdır.
  • YS41. Geçiş sürümlerinin okuması geri düşüşlüdür: yeni sütun boşsa eski sütuna bakar.
  • YS42. Kurgu, bölgesel ölçüm ağının sayaç okuma tablosudur; okuma değeri metreküp cinsinden tek bir sütunken litre cinsinden ilk ve son endeks çiftine bölünmektedir. Sayılar kurgudur.

Göç Türlerinin Geri Alınabilirliği

Altı göç türü gerçek bir tabloda uygulanır, hemen ardından tersi koşturulur ve tablo içeriği başlangıçtakiyle karşılaştırılır. Ölçü, ters komutun çalışıp çalışmadığı değil, satırların özdeş kalıp kalmadığıdır.

// goc-turleri.mjs — alti goc turu gercekten uygulanir, tersi kosturulur, icerik karsilastirilir
// Bolgesel olcum agi kurgusu: sayac okuma tablosu. Olcu: ters islemden sonra ozdeslik.
import { DatabaseSync } from 'node:sqlite';
import { createHash } from 'node:crypto';

const OKUMA = [
  ['R-101', 'S-0431', 8.432, 'gorsel'], ['R-102', 'S-0432', 17.06, 'gorsel'],
  ['R-103', 'S-0433', 34.915, 'uzaktan'], ['R-104', 'S-0434', 52.5, 'uzaktan'],
  ['R-105', 'S-0435', 6.004, 'gorsel'], ['R-106', 'S-0436', 21.37, 'tahmin'],
];

function kur() {
  const db = new DatabaseSync(':memory:');
  db.exec('CREATE TABLE okuma (id TEXT PRIMARY KEY, sayac TEXT, m3 REAL, kaynak TEXT)');
  const ekle = db.prepare('INSERT INTO okuma VALUES (?, ?, ?, ?)');
  for (const o of OKUMA) ekle.run(...o);
  return db;
}
const satirlar = (db) => db.prepare('SELECT * FROM okuma ORDER BY id').all()
  .map((s) => Object.keys(s).sort().map((k) => `${k}=${s[k]}`).join('|'));
const ozet = (db) => createHash('sha256').update(satirlar(db).join('\n')).digest('hex').slice(0, 10);

const TUR = [
  ['sutun ekleme', (db) => db.exec('ALTER TABLE okuma ADD COLUMN litre INTEGER'),
    (db) => db.exec('ALTER TABLE okuma DROP COLUMN litre')],
  ['indeks ekleme', (db) => db.exec('CREATE INDEX ix_sayac ON okuma(sayac)'),
    (db) => db.exec('DROP INDEX ix_sayac')],
  ['sutun yeniden adlandirma', (db) => db.exec('ALTER TABLE okuma RENAME COLUMN m3 TO tuketim'),
    (db) => db.exec('ALTER TABLE okuma RENAME COLUMN tuketim TO m3')],
  ['geri doldurma', (db) => { db.exec('ALTER TABLE okuma ADD COLUMN litre INTEGER');
    db.exec('UPDATE okuma SET litre = CAST(m3 * 1000 AS INTEGER)'); },
    (db) => db.exec('ALTER TABLE okuma DROP COLUMN litre')],
  ['yerinde hassasiyet daraltmasi', (db) => db.exec('UPDATE okuma SET m3 = ROUND(m3, 1)'),
    (db) => db.exec('UPDATE okuma SET m3 = m3')],
  ['sutun silme', (db) => db.exec('ALTER TABLE okuma DROP COLUMN kaynak'),
    (db) => db.exec('ALTER TABLE okuma ADD COLUMN kaynak TEXT')],
];

console.log('goc turu                        once       sonra      geri donusten sonra  ozdes  farkli satir');
for (const [ad, ileri, geri] of TUR) {
  const db = kur();
  const o1 = satirlar(db), h1 = ozet(db);
  ileri(db);
  const h2 = ozet(db);
  geri(db);
  const o3 = satirlar(db), h3 = ozet(db);
  const fark = o1.filter((s, i) => s !== o3[i]).length;
  console.log(`${ad.padEnd(31)} ${h1} ${h2} ${h3}           ` +
    `${(h1 === h3 ? 'evet' : 'hayir').padEnd(6)} ${fark}`);
  db.close();
}
goc turu                        once       sonra      geri donusten sonra  ozdes  farkli satir
sutun ekleme                    39a9eddd27 773061e459 39a9eddd27           evet   0
indeks ekleme                   39a9eddd27 39a9eddd27 39a9eddd27           evet   0
sutun yeniden adlandirma        39a9eddd27 92df9b4475 39a9eddd27           evet   0
geri doldurma                   39a9eddd27 188c3af8a8 39a9eddd27           evet   0
yerinde hassasiyet daraltmasi   39a9eddd27 dfea77da56 dfea77da56           hayir  5
sutun silme                     39a9eddd27 e244ca795a c5d40ca8c9           hayir  6

Altı türün dördü tam olarak geri alınıyor: özet önceki değerine dönüyor ve hiçbir satır farklı değil. Bu dördünün ortak yanı, veriyi hiç kaybetmemeleridir. Sütun ve indeks eklemek mevcut satırlara dokunmaz. Yeniden adlandırma yalnız adı değiştirir. Geri doldurma yeni bir sütuna yazar ve eski sütun yerinde durur.

Son iki tür geri alınmıyor ve ikisi ayrı nedenlerle geri alınmıyor. Hassasiyet daraltması değeri yerinde ezdi: ondalıklar kesildikten sonra onları geri getirecek bir ters komut yoktur. Ters işlem yerine kimlik işlemi konuldu ve beş satırın değeri eski hâline dönmedi — ikinci ile üçüncü özet aynı çıktı. Sütun silme ise komutun hatasız çalıştığı hâlde geri getirmeyen türün örneğidir: sütun yeniden eklendi, altı satırın hepsi boş geldi.

Ayrım şurada duruyor: geri alma komutunun var olması, geri alınabilirlik demek değildir. Her iki başarısız satırda da ters komut çalıştı ve sıfır kodla döndü. Ölçüyü özdeşlik üzerinden kurmak, “geri alma betiği yazıldı” ifadesini sınanabilir bir şeye çeviriyor.

Tablonun üçüncü satırı ayrıca bir uyarı taşıyor. Sütun yeniden adlandırma tam olarak geri alınıyor — sıfır farklı satır — ama bu, adımın güvenli olduğu anlamına gelmiyor. Eski adı kullanan bir sürüm, adlandırma geçtiği anda sütunu bulamaz. Geri alınabilirlik bir adımın sonrasına bakar; uyumluluk sırasına. Bir adım geri alınabilir olduğu hâlde iki sürümü birlikte taşımayabilir, ya da tersi. İkinci ölçü bu yüzden ayrı koşturulur.

Genişlet–Taşı–Daralt, Adım Adım

Metreküp cinsinden tek bir okuma sütunu, litre cinsinden ilk ve son endeks çiftine bölünüyor. Düzen altı adım: şema genişler, veri taşınır, kod dört sürümde ilerler, şema daralır. Her adımda o an koşan sürüm ile bir önceki sürüm birlikte sınanır — biri yazar, öteki okur, sonra tersi.

// esgudum.mjs — genislet-tasi-daralt adim adim kosar; her adimda iki surum birlikte sinanir
// Kurgu: sayac okumasi m3 tek sutunundan litre cinsinden ilk/son endeks ciftine bolunur.
import { DatabaseSync } from 'node:sqlite';

const M3 = 12.345, ILK = 100000, SON = 112345;
const OKU_GERI_DUSUSLU = 'SELECT COALESCE((son_endeks - ilk_endeks) / 1000.0, m3) AS deger' +
  ' FROM okuma WHERE id = ?';
const SURUM = {
  v1: { alan: 'm3', oku: 'SELECT m3 AS deger FROM okuma WHERE id = ?',
    yaz: 'INSERT INTO okuma (id, sayac, m3) VALUES (?, ?, ?)' },
  v2: { alan: 'm3+endeks', oku: OKU_GERI_DUSUSLU,
    yaz: 'INSERT INTO okuma (id, sayac, m3, ilk_endeks, son_endeks) VALUES (?, ?, ?, ?, ?)' },
  v3: { alan: 'endeks', oku: OKU_GERI_DUSUSLU,
    yaz: 'INSERT INTO okuma (id, sayac, ilk_endeks, son_endeks) VALUES (?, ?, ?, ?)' },
  v4: { alan: 'endeks', oku: 'SELECT (son_endeks - ilk_endeks) / 1000.0 AS deger FROM okuma WHERE id = ?',
    yaz: 'INSERT INTO okuma (id, sayac, ilk_endeks, son_endeks) VALUES (?, ?, ?, ?)' },
};
const DEGER = { m3: ['S-0440', M3], 'm3+endeks': ['S-0440', M3, ILK, SON], endeks: ['S-0440', ILK, SON] };

const SEMA = {
  genislet: [(db) => db.exec('ALTER TABLE okuma ADD COLUMN ilk_endeks INTEGER;' +
    ' ALTER TABLE okuma ADD COLUMN son_endeks INTEGER'), true],
  tasi: [(db) => db.exec('UPDATE okuma SET ilk_endeks = 0,' +
    ' son_endeks = CAST(m3 * 1000 AS INTEGER) WHERE son_endeks IS NULL'), true],
  daralt: [(db) => db.exec('ALTER TABLE okuma DROP COLUMN m3'), false],
};
// [ad, sema adimi, kosan surum, bir surum geri alma hedefi, iki surum geri alma hedefi]
const ADIM = [
  ['genislet: endeks sutunlari eklenir', 'genislet', 'v1', 'v1'],
  ['cift yazan surum yayinlanir', null, 'v2', 'v1'],
  ['tasi: eski satirlar geri doldurulur', 'tasi', 'v2', 'v1'],
  ['m3 yazmayi birakan surum yayinlanir', null, 'v3', 'v2', 'v1'],
  ['geri dususu kaldiran surum yayinlanir', null, 'v4', 'v3'],
  ['daralt: m3 sutunu silinir', 'daralt', 'v4', 'v3'],
];

const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE okuma (id TEXT PRIMARY KEY, sayac TEXT, m3 REAL)');
db.prepare('INSERT INTO okuma (id, sayac, m3) VALUES (?, ?, ?)').run('R-101', 'S-0431', 8.432);

function sina(A, B, ek) {
  const bulgu = [];
  for (const [j, [yazan, okuyan]] of [[A, B], [B, A]].entries()) {
    const id = `T-${ek}-${j}`;
    try { db.prepare(SURUM[yazan].yaz).run(id, ...DEGER[SURUM[yazan].alan]); }
    catch (e) { bulgu.push(`${yazan} yazamadi: ${e.message}`); continue; }
    try {
      const d = db.prepare(SURUM[okuyan].oku).get(id)?.deger;
      if (Math.abs((d ?? -1) - M3) > 1e-9) bulgu.push(`${okuyan}, ${yazan} kaydini ${d} okudu`);
    } catch (e) { bulgu.push(`${okuyan} okuyamadi: ${e.message}`); }
  }
  return bulgu;
}

console.log('adim                                    sema      kosan  hedef  uyumlu  sema geri alinir');
let uyumlu = 0, geriAlinir = 0, semaAdimi = 0;
ADIM.forEach(([ad, sema, A, B, C], i) => {
  if (sema) { SEMA[sema][0](db); semaAdimi += 1; if (SEMA[sema][1]) geriAlinir += 1; }
  const bulgu = sina(A, B, i + 1);
  if (bulgu.length === 0) uyumlu += 1;
  console.log(`${(i + 1)} ${ad.padEnd(38)} ${(sema ?? '—').padEnd(9)} ${A}     ${B}     ` +
    `${(bulgu.length ? 'hayir' : 'evet').padEnd(7)} ${sema ? (SEMA[sema][1] ? 'evet' : 'hayir') : '—'}`);
  for (const b of bulgu) console.log(`    ${b}`);
  if (C) {
    const iki = sina(A, C, `${i + 1}x`);
    console.log(`    iki surum geri (${A} ile ${C}): ${iki.length ? 'uyumsuz' : 'uyumlu'}`);
    for (const b of iki) console.log(`      ${b}`);
  }
});
console.log('');
console.log(`iki surumun birlikte kostugu adim : ${uyumlu} / ${ADIM.length}`);
console.log(`kosamadigi adim                   : ${ADIM.length - uyumlu}`);
console.log(`sema adimi                        : ${semaAdimi}, geri alinabilen ${geriAlinir}`);
adim                                    sema      kosan  hedef  uyumlu  sema geri alinir
1 genislet: endeks sutunlari eklenir     genislet  v1     v1     evet    evet
2 cift yazan surum yayinlanir            —         v2     v1     evet    —
3 tasi: eski satirlar geri doldurulur    tasi      v2     v1     evet    evet
4 m3 yazmayi birakan surum yayinlanir    —         v3     v2     evet    —
    iki surum geri (v3 ile v1): uyumsuz
      v1, v3 kaydini null okudu
5 geri dususu kaldiran surum yayinlanir  —         v4     v3     evet    —
6 daralt: m3 sutunu silinir              daralt    v4     v3     hayir   hayir
    v3 okuyamadi: no such column: m3

iki surumun birlikte kostugu adim : 5 / 6
kosamadigi adim                   : 1
sema adimi                        : 3, geri alinabilen 2

Altı adımın beşinde iki sürüm birlikte koştu. Uyumsuz tek adım sonuncusudur ve geri alınamayan tek şema adımı da odur. İki özellik aynı adımda buluşuyor: eski sütunu silmek hem önceki kod sürümünü çalışamaz hâle getiriyor hem de kendisi geri alınamıyor.

Dördüncü adımdaki ek satır, uyumluluğun kaç sürüm geriye sorusuna bağlı olduğunu gösteriyor. Bir sürüm geri — koşan sürüm ile ondan önceki — uyumlu. İki sürüm geri uyumsuz: metreküp sütununa yazmayı bırakan sürümün kaydını, iki kuşak önceki sürüm boş okuyor. Hata yok, kayıt var, değer yok. Geri alma penceresi bu yüzden tek sürümdür; iki sürüm geri dönmek isteyen bir plan, kod adımlarını tek tek çıkmak zorundadır.

Üç şema adımının ikisi geri alınabilir. Genişletme sütunları düşürerek, taşıma yeni sütunları boşaltarak geri alınır — değerler eski sütunda durmaya devam ettiği için ikisinde de kayıp yok. Daraltma katalogdaki altıncı satırın kendisidir.

Kod merdiveninin dört basamaklı olması da bu ölçümden çıkıyor. Çift yazan sürüm eski sütunu doldurmaya devam ettiği için önceki sürüm okumaya devam edebiliyor. Metreküp yazmayı bırakan sürüm bir sonraki basamak, geri düşüşü kaldıran sürüm ondan sonraki. Dördüncü basamak olmadan daraltma adımı koşamaz: geri düşüşlü okuma silinen sütuna başvurur ve o an üretimde koşan sürüm kırılır. Basamaklardan biri atlanırsa uyumsuz adım sayısı birden ikiye çıkar.

Uyumsuz Adımın Bedeli

Uyumsuz adım iki yerde fatura kesiyor. Birincisi yayın penceresidir. İki sürümün birlikte koşamadığı bir adım, aşamalı olarak dağıtılamaz: düğümlerin bir kısmının eski, bir kısmının yeni sürümde olduğu her an bir arıza anıdır. Beş uyumlu adımın her biri düğüm düğüm ilerleyebilir; altıncı adım tek seferde ve bütün düğümlerde aynı anda geçmek zorundadır. Aşamalı yayının sağladığı kademeli gözlem penceresi bu adımda yoktur.

İkincisi geri alma planıdır. İlk beş adımda geri alma önceki dersin “yalnız kod” senaryosudur: beş adım, sekiz tur, dönüşü olmayan nokta yok. Altıncı adımdan sonra plan “kod ile veri” senaryosuna geçer: sekiz adım, provasız yirmi tur ve altıncı adımda kapanan bir dönüş. Tek bir göç adımı, geri alma planını iki buçuk kat uzatıyor ve içine geri dönülemez bir nokta koyuyor.

Buradaki kapı, göç adımını üretime bırakmadan önce eski sürümün sorgularını yeni şemaya karşı koşturan uyumluluk denetimidir. Tuttuğu bir adımdır: altı adımın altıncısı. Bu adımı yalnız bu kapı tutar — göç adımının kendi sınamaları yeni şemada koşar ve eski sorguyu hiç görmez. Kaçırdığı ise katalogdaki beşinci satırdır: hassasiyet daraltması her iki sürümle de uyumludur, kapı onu geçirir, ama geri alınamaz. Uyumluluk ile geri alınabilirlik ayrı eksenlerdir ve bu kapı yalnız birine bakar.

Kapının yanlış tutması hedef seçiminden doğuyor. Denetim, koşan sürümü bir önceki yerine en eski dağıtılabilir sürümle karşılaştırırsa dördüncü adım da durdurulur — çıktının gösterdiği gibi o karşılaştırma uyumsuz çıkar. Altı adımın biri boşuna beklemiş olur. Eklediği süre üç şema adımı için üç turdur; kaçırdığında ödenen ise yirmi turluk bir geri alma planıdır.

Özet

  • Altı göç türünün dördü ters işlemden sonra tabloyu özdeş bıraktı; hassasiyet daraltması beş satırı, sütun silme altı satırı geri getirmedi — ikisinde de ters komut hatasız çalıştı.
  • Geri alınabilirliğin ölçüsü komutun varlığı değil içeriğin özdeşliğidir; bu ölçüyle “geri alma betiği yazıldı” ifadesi sınanabilir hâle gelir.
  • Genişlet–taşı–daralt altı adımın beşinde iki sürümü birlikte taşıdı; uyumsuz tek adım, geri alınamayan tek şema adımıyla aynı adımdı.
  • Geri alma penceresi tek sürümdür: bir sürüm geri uyumlu çıkan dördüncü adım, iki sürüm geri gidildiğinde eski sürüme boş değer okuttu.
  • Uyumsuz adım yayın penceresini aşamalıdan tek seferliğe indirdi ve geri alma planını beş adım sekiz turdan sekiz adım yirmi tura çıkardı.

Sonraki Adım

Bu konu boyunca yayın kararının nereden geldiği ölçüldü: hangi ölçüte bakılarak terfi ediliyor, geri alma birimi ne, plan kaç adım, göç adımı iki sürümü taşıyor mu. Bütün bu kararların sonucunda ortaya bir istenen durum çıkıyor — şu sürüm, şu bayrak değeri, şu şema. Ama o istenen durumun kendisi hiçbir yerde yazılı değil. Onu üretime taşıyan şey hâlâ bir arayüzde tıklanan düğme ya da bir kişinin elindeki komut; üretimde gerçekte ne koştuğunu öğrenmenin yolu da üretime bakmak. Sonraki konu bu soruyla açılır: istenen durum nerede durur, kim değiştirir ve gerçek durumun ondan sapıp sapmadığı nasıl ölçülü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