Ders 06 / 13
Göçler
Tablo şemasını sürüm sürüm değiştiren göçlerin ölçümü; eklenen sütunun kalıcı olduğunu, eski satırlara ulaşmadığını ve göçlerin neden numaralandığını gösterir.
Yayın Güncelleme
Bu derste
- Şemaya sütun ekleyen ileri göçü ve onu geri alan göçü saf Ruby ile kurar
- Eklenen sütunun kalışını dört istekte ölçer ve kalıcı olduğunu gösterir
- Aynı göçün iki kez koşmasının şemada iz bıraktığını ve geri almanın satırlara dokunmadığını ölçer
- Göçten önce yazılmış satırlarda yeni sütunun bulunmadığını gösterir
İçindekiler
Önceki ders, kaydedilen bir ürünün başka bir istemcide de okunduğunu gösterdi. O ölçümde ürünün iki niteliği vardı: ad ve fiyat. Bir ürüne stok sayısı da eklenmek istendiğinde ne olur? Tablonun biçimi, yani şema (schema), elle düzeltilen bir dosya olarak değil, kayıtlı adımlarla değişir. Bu adımlara göç (migration) denir: şemayı bir durumdan ötekine taşıyan, adlandırılmış ve sıralanmış bir adımdır. Şemaya eklenen sütun eski satırlarda var mı?
Eksen değişmedi: bu değer hangi isteğe kadar kalıyor? Bir sütun şemada durur; şema, kayıtlar gibi uygulamanın deposundadır. Bu yüzden beklenen kalış kalıcıdır. Ders bunu ölçer, sonra göçün iki sınırını gösterir: aynı göç iki kez koşarsa ne olur ve göçten önce yazılmış satırlar yeni sütunu görür mü? Değişikliğin nereye ulaştığı M12/K02’de ayrıca ölçülmüştü; orada soru “değişiklik hangi hedefe ulaşıyor” idi, burada soru ulaşan değişikliğin ne kadar kaldığıdır.
Ölçüm Çekirdeği
Birinci dosya, kursun her dersinde aynı olan istek döngüsü ve kalış sınıflandırıcıdır. Bu blok kaynaktır; dosya diske yazılır ve require_relative ile çekilir.
# kalis.rb — kucuk bir istek dongusu ve bir degerin hangi istege kadar kaldigini siniflandiran yardimci
# Flash: bu istekte yazilan deger yalniz bir sonraki istege tasinir.
class Flash
def initialize(gelen)
@gelen = gelen
@yeni = {}
end
def [](anahtar) = @yeni.fetch(anahtar) { @gelen[anahtar] }
def []=(anahtar, deger)
@yeni[anahtar] = deger
end
def birakilan = @yeni
end
# Her istek yeni bir baglam alir: params, flash ve istek deposu her istekte bostur.
class Baglam
attr_reader :uygulama, :params, :oturum, :flash, :istek_deposu
def initialize(uygulama, oturum)
@uygulama = uygulama
@params = {}
@oturum = oturum
@flash = Flash.new(oturum.delete(:flash) || {})
@istek_deposu = {}
end
end
# Uygulamanin deposu butun isteklerde ve butun istemcilerde aynidir;
# oturum istemciye gore secilir.
class Uygulama
attr_reader :depo
def initialize
@oturumlar = Hash.new { |h, istemci| h[istemci] = {} }
@depo = { urunler: {}, sema: { urunler: %i[ad fiyat] }, sablonlar: {},
kuyruk: [], onbellek: {}, sayac: 0 }
end
def istek(istemci)
baglam = Baglam.new(self, @oturumlar[istemci])
sonuc = yield baglam
baglam.oturum[:flash] = baglam.flash.birakilan unless baglam.flash.birakilan.empty?
sonuc
end
end
# Kural: istek 1'de (istemci A) deger yazilir; istek 2 ve 3 (A) ile istek 4 (B) okur.
# B goruyorsa kalici; A'nin ucuncu istegi goruyorsa oturum; yalniz ikinci istek
# goruyorsa sonraki; hicbiri gormuyorsa bu_istek.
def kalis(yaz, oku)
uygulama = Uygulama.new
ilk = uygulama.istek("A") { |b| yaz.call(b); oku.call(b) }
raise ArgumentError, "deger yazildigi istekte okunamadi" if ilk.nil?
gorulen = [["A", 2], ["A", 3], ["B", 4]].map do |istemci, sira|
uygulama.istek(istemci) { |b| oku.call(b) }.nil? ? "--" : "#{istemci}#{sira}"
end
tur = if gorulen[2] != "--" then "kalici"
elsif gorulen[1] != "--" then "oturum"
elsif gorulen[0] != "--" then "sonraki"
else "bu_istek"
end
[tur, ["A1", *gorulen].join(" "), ilk]
end
Bu dersin kesiti tek bir yöntemdir. Bir göç, en yalın biçimiyle, şemadaki bir tabloya bir sütun adı ekler. Şema depo[:sema] karma yapısındadır: anahtar tablo adı, değer sütun adlarının listesidir.
# goc.rb — sema degisikligi: bir tabloya bir sutun eklenir require_relative "kalis" def goc(b, tablo, sutun) = b.uygulama.depo[:sema][tablo] << sutun
Bu blok da kaynaktır. Çekirdek bir çerçeve değil, bir çerçevenin modelidir; bu yöntem tek bir ileri göçü temsil eder. Çoğu göç düzeneği her göçü bir sınıf ya da dosya olarak yazar ve ileri yönün yanında geri yönü de tanımlayabilir. Aşağıda bu dersin kendi yardımcıları aynı ayrımı küçük ölçekte kurar.
Sütunun Kalışı
Yazan lambda urunler tablosuna stok sütununu ekler; okuyan lambda şemada bu sütunu arar.
# olcum.rb — sema sutunu gozleminin kalisi
require_relative "goc"
yaz = ->(b) { goc(b, :urunler, :stok) }
oku = ->(b) { b.uygulama.depo[:sema][:urunler].find { |s| s == :stok } }
p kalis(yaz, oku)
["kalici", "A1 A2 A3 B4", :stok]
Kalış kalıcı: B’nin dördüncü isteği de :stok sütununu şemada buldu. Kanıt satırında dört istek de var ve hiçbirinde -- yok. Şema uygulamanın deposundadır ve uygulama bütün istemcilere hizmet eder; bir istemcinin yürüttüğü göç ötekilerin de gördüğü şemayı değiştirir. VK6. Bu ölçüm tek süreçtedir: göçün süreç yeniden başladıktan sonra da kaldığı, çekirdeğin bellekteki deposunda ölçülemez.
Göçü kimin koşturduğu kalışı değiştirmez. Aynı sütun B istemcisinin isteğinde eklenirse A’nın sonraki isteği onu görür.
# baska_istemci.rb — goc bir istemcide kosar, ote istemci okur
require_relative "goc"
uygulama = Uygulama.new
uygulama.istek("B") { |b| goc(b, :urunler, :stok) }
uygulama.istek("A") { |b| p b.uygulama.depo[:sema][:urunler] }
[:ad, :fiyat, :stok]
Şema istemciye değil uygulamaya aittir: bir istemcinin isteğinde değişen şema, sonraki her istekte, hangi istemciden gelirse gelsin, değişmiş hâliyle görünür. Bu, önceki dersteki kayıtların kalıcılığıyla aynı nedene dayanır; ikisi de depoda durur. VK9. Burada göç isteğin içinde koşturuldu çünkü çekirdekte başka bir yer yoktur; göçün gerçek bir uygulamada nerede koşturulduğu bu ölçümün konusu değildir.
İleri ve Geri
Göçler iki yöndedir. İleri göç şemayı değiştirir, geri alma değişikliği eski hâline getirir. Çekirdekte yalnız ileri yön vardır; geri alma bu dersin yardımcısıdır. Önce şemanın göçten önceki ve sonraki durumuna, sonra geri almadan sonraki durumuna bakılır.
# ileri_geri.rb — ileri ve geri goc
require_relative "goc"
def goc_geri(b, tablo, sutun) = b.uygulama.depo[:sema][tablo].delete(sutun)
uygulama = Uygulama.new
uygulama.istek("A") do |b|
sema = b.uygulama.depo[:sema]
p sema[:urunler]
goc(b, :urunler, :stok)
p sema[:urunler]
goc_geri(b, :urunler, :stok)
p sema[:urunler]
end
[:ad, :fiyat] [:ad, :fiyat, :stok] [:ad, :fiyat]
Üç çıktı satırı üç durumdur: göçten önce, ileri göçten sonra, geri almadan sonra. Geri alma şemayı birinci duruma döndürdü. Bir göçün iki yönü olması, şemanın bir sürüm dizisi olduğunu söyler: her sürüm bir öncekinin üstüne kurulur ve bir yönde ilerleyen, öteki yönde geri dönebilen bir yol oluşturur. Bu ders yalnız şema tarafını ölçer; satırların içindeki değerler aşağıdaki sınırlayıcı ölçümde ayrıca sorulur.
Geri almanın etkisi hangi isteğe kadar kalıyor? Aşağıdaki ölçüm sütunu ekler, bir satıra değer yazar, sütunu geri alır ve şemayı ve satırı ayrı ayrı okur. Sonra aynı geri almayı kalış kuralından geçirir.
# geri_kalis.rb — geri alinan sutun ve satirdaki degeri
require_relative "goc"
def goc_geri(b, tablo, sutun) = b.uygulama.depo[:sema][tablo].delete(sutun)
uygulama = Uygulama.new
uygulama.istek("A") do |b|
goc(b, :urunler, :stok)
b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1, stok: 5 }
goc_geri(b, :urunler, :stok)
p b.uygulama.depo[:sema][:urunler].include?(:stok)
p b.uygulama.depo[:urunler][1].key?(:stok)
end
yaz = lambda do |b|
goc(b, :urunler, :stok)
goc_geri(b, :urunler, :stok)
end
oku = ->(b) { b.uygulama.depo[:sema][:urunler].include?(:stok) }
p kalis(yaz, oku)
false true ["kalici", "A1 A2 A3 B4", false]
Şema artık :stok sütununu içermiyor, ama satır anahtarını ve 5 değerini taşımaya devam ediyor. Bu modelde geri alma yalnız şemaya dokunur; satırlardaki veriyi silmez. Kalış satırı, geri alınmış şemanın da kalıcı olduğunu gösterir: B’nin dördüncü isteği bile false okur, yani sütunun yokluğu A’nın isteğiyle sınırlı kalmadı. Gerçek bir göç düzeneğinde geri almanın satırlardaki değeri de silip silmediği düzeneğe bağlıdır ve burada ölçülmemiştir. Bu modelde şemayı eski hâline getiren adım veriyi eski hâline getirmedi.
Yeni Tablo ve Eksik Tablo
goc yalnız var olan bir tabloya sütun ekler. Şemada olmayan bir tabloya aynı çağrı yapılırsa ne olur? Ölçüm, yakalanan istisnanın yalnız sınıfını yazdırır.
# eksik_tablo.rb — var olmayan tabloya sutun eklemek
require_relative "goc"
def tablo_olustur(b, tablo, *sutunlar)
b.uygulama.depo[:sema][tablo] = sutunlar
end
uygulama = Uygulama.new
uygulama.istek("A") do |b|
begin
goc(b, :siparisler, :adet)
rescue => e
p e.class
end
tablo_olustur(b, :siparisler, :urun_id, :adet)
p b.uygulama.depo[:sema].keys
p b.uygulama.depo[:sema][:siparisler]
end
NoMethodError [:urunler, :siparisler] [:urun_id, :adet]
Var olmayan tabloya sütun eklemek NoMethodError verdi: şemada o ad yoktu ve nil üzerinde ekleme yöntemi çağrıldı. Tablo oluşturmak ayrı bir göç türüdür. tablo_olustur tabloyu sütun listesiyle şemaya yazdı; şema artık iki tablo içeriyor. Sonraki derste Siparis kayıtları bu tabloyu kullanacak. VK7. Burada siparisler yalnız şemada tanımlanır; tabloya satır yazılmaz.
Göçler Neden Numaralanır
Aynı ileri göç iki kez koşarsa şema ne olur? goc bir liste sonuna ekler ve eklediğini hatırlamaz.
# iki_kez.rb — ayni goc iki kez
require_relative "goc"
uygulama = Uygulama.new
uygulama.istek("A") do |b|
goc(b, :urunler, :stok)
goc(b, :urunler, :stok)
p b.uygulama.depo[:sema][:urunler]
end
[:ad, :fiyat, :stok, :stok]
Şemada :stok iki kez var. Modelin kendisinde sürüm izi yoktur: hangi göçün koştuğunu hatırlayan bir kayıt yoktur ve ikinci çağrı birincinin tekrarı mı, yeni bir göç mü, anlaşılmaz. Gerçek bir göç düzeneği tam bu sebeple her göçe bir numara verir ve hangi numaraların uygulandığını bir sürüm tablosunda tutar. Bu dersin yardımcısı aynı fikri küçük ölçekte kurar: numarası uygulanmış bir göç ikinci kez koşmaz.
# numarali.rb — uygulanan gocleri hatirlayan yardimci
require_relative "goc"
def numarali_goc(b, no, tablo, sutun)
uygulananlar = (b.uygulama.depo[:uygulananlar] ||= [])
return false if uygulananlar.include?(no)
goc(b, tablo, sutun)
uygulananlar << no
true
end
uygulama = Uygulama.new
uygulama.istek("A") do |b|
p numarali_goc(b, 1, :urunler, :stok)
p numarali_goc(b, 1, :urunler, :stok)
p b.uygulama.depo[:sema][:urunler]
p b.uygulama.depo[:uygulananlar]
end
yaz = ->(b) { numarali_goc(b, 1, :urunler, :stok) }
oku = ->(b) { b.uygulama.depo[:uygulananlar]&.first }
p kalis(yaz, oku)
true false [:ad, :fiyat, :stok] [1] ["kalici", "A1 A2 A3 B4", 1]
İlk çağrı true verdi ve göçü koşturdu; aynı numarayla ikinci çağrı false verdi ve şemaya dokunmadı. Şemada :stok bir kez duruyor. Uygulananlar listesi [1]: göç numarası da şema gibi uygulamanın deposunda durur ve kalışı kalıcıdır, çünkü B’nin dördüncü isteği bile listenin ilk öğesini görür. Sürüm kaydı, göçün sonucunu değil, göçün koştuğunu hatırlar. VK8. Burada numara bir tam sayıdır ve göçler tek tek, sırayla çağrılır; sıra bozulursa ne olacağı ölçülmemiştir.
Kaydın neyi doğruladığı ayrıca sorulabilir. Aynı sütunu iki farklı numarayla eklemek, kaydın numarayı mı yoksa göçün içeriğini mi izlediğini gösterir.
# iki_numara.rb — ayni sutun, iki numara
require_relative "goc"
def numarali_goc(b, no, tablo, sutun)
uygulananlar = (b.uygulama.depo[:uygulananlar] ||= [])
return false if uygulananlar.include?(no)
goc(b, tablo, sutun)
uygulananlar << no
true
end
uygulama = Uygulama.new
uygulama.istek("A") do |b|
p numarali_goc(b, 1, :urunler, :stok)
p numarali_goc(b, 2, :urunler, :stok)
p b.uygulama.depo[:sema][:urunler]
p b.uygulama.depo[:uygulananlar]
end
true true [:ad, :fiyat, :stok, :stok] [1, 2]
İkinci çağrı true verdi ve :stok şemaya ikinci kez girdi. Kayıt numarayı izler, göçün ne yaptığını izlemez: aynı değişikliği başka bir numarayla getiren bir göç engellenmez. Numaralamanın verdiği koruma, numaraların doğru verilmesine dayanır. Gerçek düzeneklerin göç dosyasının içeriğinden bir sağlama değeri tutup tutmadığı burada ölçülmemiştir.
Sınırlayıcı Ölçüm
Şemaya eklenen sütun kalıcıdır. Peki göçten önce yazılmış bir satır bu sütuna sahip mi? Şema ile verinin kalışı ayrı sorulardır. Aşağıdaki ölçüm bir satırı doğrudan tabloya yazar, göçü koşturur ve hem şemaya hem satıra bakar.
# sinir.rb — gocten once yazilmis satir
require_relative "goc"
uygulama = Uygulama.new
uygulama.istek("A") do |b|
b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
goc(b, :urunler, :stok)
p b.uygulama.depo[:sema][:urunler].include?(:stok)
p b.uygulama.depo[:urunler][1].key?(:stok)
b.uygulama.depo[:urunler][2] = { ad: "defter", fiyat: 30, id: 2, stok: 5 }
p b.uygulama.depo[:urunler].values.map { |satir| satir.key?(:stok) }
end
true false [false, true]
Şema :stok sütununu içeriyor, ama göçten önce yazılan satır :stok anahtarını taşımıyor: key? false verdi. Göçten sonra yazılan satırda anahtar var. Şema ile satır tek bir şey değildir: şema, satırların nasıl olması gerektiğini söyler, mevcut satırları değiştirmez. Bu modelde eski satırlar yeni anahtarı hiç taşımaz; ilişkisel bir veritabanında ise yeni sütun eski satırlarda boş (NULL) ya da varsayılan değerle görünür. İki durumda da anlamlı bir değer için eski satırlar ayrıca doldurulmalıdır. Bu yüzden iki iş ayrılır: sütunu ekleyen şema göçü ve eski satırlara bir değer yazan veri göçü. Aşağıdaki yardımcı, eksik anahtarı olan her satıra bir varsayılan yazar.
# doldur.rb — eski satirlara yeni sutunu yazan veri goc
require_relative "goc"
def sutunu_doldur(b, tablo, sutun, deger)
b.uygulama.depo[tablo].each_value { |satir| satir[sutun] = deger unless satir.key?(sutun) }
end
uygulama = Uygulama.new
uygulama.istek("A") do |b|
b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
goc(b, :urunler, :stok)
p b.uygulama.depo[:urunler].values.map { |satir| satir.key?(:stok) }
sutunu_doldur(b, :urunler, :stok, 0)
p b.uygulama.depo[:urunler][1]
end
yaz = lambda do |b|
b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
goc(b, :urunler, :stok)
end
oku = ->(b) { b.uygulama.depo[:urunler][1]&.key?(:stok) }
p kalis(yaz, oku)
[false]
{ad: "kalem", fiyat: 12, id: 1, stok: 0}
["kalici", "A1 A2 A3 B4", false]
Önce eski satır false verdi; doldurmadan sonra satırda stok: 0 var. Son satır iki kalışı yan yana koyar: bu oku lambda’sı satırda sütun anahtarını arar ve false bulur, ama false bir değer olduğu için kalış kuralı onu görülmüş sayar ve her istekte aynı değeri okur. Satıra göçten sonra anahtar eklenmediği sürece B’nin dördüncü isteği de false okur. Aynı göç, şemada kalıcı bir sütun, satırda kalıcı bir yokluk bıraktı. Bu iki kalıcılığı birbirinden ayırmak gerekir: okuyucu şemayı soruyorsa :stok vardır; satıra soruyorsa yoktur. Kalış kuralı bu farkı göstermez, çünkü kural bir değerin davranışını okur; yokluğun bir hata mı, bir varsayılan mı olduğunu satırı okuyan kod belirler.
Özet
- Göç, şemayı bir durumdan ötekine taşıyan adlandırılmış bir adımdır; ileri yönü sütun ekler, geri alma yönü onu siler.
- Bu dersin gözlemi olan şema sütunu, kalışı kalıcı olan değerdir:
:stoksütunu A’nın sonraki iki isteğinde ve B’nin dördüncü isteğinde şemada okundu. - Çekirdek modelde sürüm izi yoktur; aynı göç iki kez koşunca
:stokiki kez yazıldı, bu yüzden göçler numaralanır ve uygulananların listesi tutulur. - Göç şemayı değiştirir, mevcut satırları değiştirmez: göçten önce yazılan satırda yeni anahtar yoktur, doldurma ayrı bir adımdır.
- Bu modelde geri alma şemayı eski hâline getirir, satırlardaki veriye dokunmaz. Kayıt numarayı izler, göçün içeriğini izlemez; aynı numaralı farklı içerik tekrar uygulanmaz.
- Sütunun kalışı ile verinin kalışı ayrı şeylerdir; ikisi de kalıcıdır, ama biri sütunun varlığını, öteki satırdaki değerin yokluğunu korur.
Sonraki Adım
Şema ve satırlar artık tanımlı. Ama bir kayıt tabloya girmeden önce kurallara uymalıdır, ve bir kayıt başka bir kayda bağlanabilir. Reddedilen bir kaydın hatası nerede duruyor? Bir sonraki ders, ilişkileri ve doğrulamaları aynı ölçüyle okur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.