İçeriğe geç
academia.sh

Ders 18 / 19

Bulut Eşitleme

Aynı kaydı iki aygıtın yazdığı durumda değerlerin geliş sırasının sonucu belirlediğinin, alan başına küme birleşiminin sağlam olduğunun ve sunucu kuralının sınıfı değiştirdiğinin ölçümü.

Yayın Güncelleme

Bu derste

  • Bulut eşitlemenin aynı kaydı birden çok aygıtın yazmasına izin verdiğini ayırır
  • İki aygıtın aynı alana yazdığı değerlerin geliş sırasının sonucu belirlediğini ölçer
  • Alan başına küme birleşiminin sırayı önemsiz kıldığını doğrular
  • Sunucunun uyguladığı kuralın aynı yazma çiftinin sınıfını değiştirdiğini gösterir
İçindekiler

Bu konunun ilk beş dersi tek bir aygıttaki veriyi ele aldı. Kullanıcının telefonu, tableti ve bilgisayarı varsa aynı kayıt birden çok yerde durur. Bulut eşitleme, bu kopyaları bir sunucu üzerinden birbirine yaklaştırır: her aygıt yaptığı değişikliği gönderir, sunucu bunları birleştirir ve diğer aygıtlar sonucu çeker. Yeni bir soru gelmez; mevcut soru yeni bir yerde sorulur: bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi? Fark şudur: iki aygıt birbirini beklemez, bu yüzden yazmaların sunucuya hangi sırayla vardığı uygulamanın elinde olmayabilir.

// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}

bir işlemin başlangıç durumuna bir kez, iki iki kez uygulanmasıdır; ab önce a sonra b, ba ise tersi sıradır. Model sunucudaki tek bir kaydın saf Swift karşılığıdır. Ağı, gecikmeyi ve gerçek teslim sırasını kurmaz; yalnız bir yazmanın kayıtta bıraktığı sonucu kurar.

Çakışma ve Kural

İki aygıt aynı alana farklı değer yazarsa sunucuda bir çakışma doğar. Bir kural gerekir: hangi değer kalacak? En yaygın kural son yazanın kazanmasıdır; sunucuya sonra ulaşan değer öncekinin yerine geçer. Başka bir kural her yazmaya bir damga koymak ve en büyük damgalıyı tutmaktır. Üçüncü bir yol, değerin kendisini birleştirmektir: bir etiket kümesine iki aygıt farklı etiketler eklediyse sonuç ikisinin birleşimidir.

import CloudKit

let recordID = CKRecord.ID(recordName: "k1")
let record = CKRecord(recordType: "Note", recordID: recordID)
record["theme"] = "koyu"

let database = CKContainer.default().privateCloudDatabase
database.save(record) { saved, error in
    if let error = error as? CKError, error.code == .serverRecordChanged {
        // sunucudaki kayit bu arada degismis; uygulama kendi kuralini uygular
    }
}

Bu parça KAYNAK’tır; bir bulut hesabı ve ağ gerektirdiği için burada koşmaz. serverRecordChanged hatası, kaydedilmek istenen kaydın sunucudaki sürümünün bu arada değiştiğini bildirir. Çakışmayı çözmek uygulamaya bırakılır: sunucudaki sürüm hatayla birlikte döner ve uygulama hangi değerin kalacağına karar verir. Bu dersin ölçümü bu kararın üç biçimini karşılaştırır. Hatanın kendisi ve gerçek teslim sırası ölçülmez.

Kaydın Modeli

Model, tek bir kayıttaki skaler alanları ve küme alanlarını tutar. Her skaler değer bir damga taşır. Sunucunun kuralı bir ayardır: gelen yazma ya her zaman kazanır ya da yalnız damgası mevcut damgadan küçük değilse kazanır.

// SyncModel.swift — sunucudaki bir kaydin saf Swift modeli
struct SyncBox: Equatable, CustomStringConvertible {
    enum Rule {
        case lastArrival
        case highestStamp
    }

    struct Stamped {
        let value: String
        let stamp: Int
    }

    private(set) var scalars: [String: Stamped] = [:]
    private(set) var sets: [String: Set<String>] = [:]
    let rule: Rule

    init(rule: Rule = .lastArrival) {
        self.rule = rule
    }

    var description: String {
        let scalarText = scalars.keys.sorted().map { "\($0)=\(scalars[$0]?.value ?? "-")@\(scalars[$0]?.stamp ?? 0)" }
        let setText = sets.keys.sorted().map { "\($0)={\((sets[$0] ?? []).sorted().joined(separator: ","))}" }
        let all = scalarText + setText
        return all.isEmpty ? "-" : all.joined(separator: " ")
    }

    static func == (lhs: SyncBox, rhs: SyncBox) -> Bool {
        lhs.description == rhs.description
    }

    mutating func write(_ value: String, stamp: Int, field: String) {
        if rule == .highestStamp, let current = scalars[field], current.stamp > stamp {
            return
        }
        scalars[field] = Stamped(value: value, stamp: stamp)
    }

    mutating func merge(_ items: [String], field: String) {
        sets[field, default: []].formUnion(items)
    }
}

func writing(_ value: String, stamp: Int, field: String) -> (SyncBox) -> SyncBox {
    { record in
        var next = record
        next.write(value, stamp: stamp, field: field)
        return next
    }
}

func merging(_ items: [String], field: String) -> (SyncBox) -> SyncBox {
    { record in
        var next = record
        next.merge(items, field: field)
        return next
    }
}

Model, iki aygıtı iki ayrı işlem olarak kurar; ab birinci aygıtın yazmasının sunucuya önce, ba ikincinin önce vardığı durumdur.

KL14. Model, sunucunun kuralı tutarlı uyguladığını varsayar: kural bir ayardır ve her yazmaya aynen işler. Gerçek teslim sırası, gecikme ve serverRecordChanged ölçülmedi.

KL15. Damgalar, modele verilen sayılardır; saat okunmaz ve basılmaz. Gerçek aygıt saatlerinin sapması kurulmadı.

KL16. Model her yazmanın sunucuya ulaştığını varsayar. Kaybolan, geciken ve yinelenen teslim kurulmadı.

İki İşlemin Ölçümü

İki işlem kurulur. Birincisi iki aygıtın aynı alana farklı değer yazmasıdır. İkincisi iki aygıtın aynı etiket kümesine farklı etiketler eklemesidir.

// main.swift — iki aygit ayni kaydi yazar
let twoDevices = replay("iki-aygit-ayni-alan", from: SyncBox(), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let tags = replay("kume-birlesimi", from: SyncBox(), merging(["a1"], field: "tags"), then: merging(["b2"], field: "tags")).report()

countSensitivities([twoDevices, tags])
iki-aygit-ayni-alan | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=koyu@1 -> siraya-duyarli
kume-birlesimi | bir=tags={a1} iki=tags={a1} ab=tags={a1,b2} ba=tags={a1,b2} -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0

İlk satırda aynı yazmayı yinelemek bir şey değiştirmiyor: bir ve iki aynı. Ama iki aygıtın değerleri sunucuya farklı sırayla varınca sonuç değişir: ab acik, ba koyu. Son yazan kazanır ve kazananı geliş sırası belirler, yani uygulamanın değil ağın. Sınıf sıraya duyarlıdır. Bu, konunun başından beri biriken zincirin sonudur: iki ekranın tercihi, yinelenen anahtarlık öğesi, dosyaya ekleme, birincil anahtarlı satır, iki kez oluşturulan nesne ve şimdi iki aygıtın yazdığı değer. Her adımda yerel bir sorun, bir sonrakinde başka bir yerde yeniden belirdi.

İkinci satırda iki aygıt aynı kümeye farklı etiketler ekliyor. Yineleme kümeyi değiştirmez, ve sıra da sonucu değiştirmez: iki sıra da {a1,b2} verir. Küme birleşimi sırayı önemsiz kılar, çünkü hiçbir değer ötekinin yerine geçmez. Sınıf sağlamdır. Alan başına birleştirme bu yüzden çakışmaları çözmenin en dayanıklı yoludur; bedeli, her alanın birleştirilebilir bir yapıda olması gerekmesidir. Bir temanın birleşimi olmaz.

Sayım bir sağlam, bir sıraya duyarlıdır.

Neyi Birleştirmeli

Ölçüm uygulamanın veri modeline bir soru olarak döner: bu alan birleştirilebilir mi? Etiketler, okunmuş işaretleri, ekli dosyaların listesi birleşir. Bir tema, bir ad, bir tarih birleşmez; bunlarda bir kazanan seçilmelidir. Kazanan seçerken damga kullanmak sıraya bağımlılığı azaltır ama kaldırmaz. Aşağıdaki sınırlayıcı ölçüm bunu gösterir.

Sık Yapılan Hatalar

  • Son yazanın kazanacağını varsayıp veri kaybını fark etmemek. Kaybeden değer sessizce silinir.
  • Birleştirilebilir bir alanı skaler yazmak. İki aygıtın eklediği etiketlerden biri kaybolur.
  • Aygıt saatine güvenip damgayı oradan almak. Saatler sapar.
  • Çakışma hatasını yoksaymak. Kayıt kaydedilmemiş kalır ve kullanıcı bunu fark etmez.
  • Eşitlemeyi her değişiklikte hemen çalıştırmak. Çağrılar yinelenir ve sıra daha da kontrolsüz olur.
  • Bir aygıtta yapılan silmeyi diğerindeki yazmayla aynı kurala bağlamak. Silme ayrı bir işlemdir ve ayrı düşünülür.

Sınırlayıcı Ölçüm

İlk satırın sıraya duyarlılığı, sunucunun son geleni kazandıran kuralına dayanıyordu. Aynı iki yazmayı damgası büyük olanı tutan bir kuralla ölçelim ve damgalar eşitken de bir kez daha deneyelim.

// main.swift — sunucu kurali
let arrival = replay("kural-gelis-sirasi", from: SyncBox(rule: .lastArrival), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let stamped = replay("kural-damga", from: SyncBox(rule: .highestStamp), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 2, field: "theme")).report()
let tie = replay("kural-damga-esit", from: SyncBox(rule: .highestStamp), writing("koyu", stamp: 1, field: "theme"), then: writing("acik", stamp: 1, field: "theme")).report()

countSensitivities([arrival, stamped, tie])
kural-gelis-sirasi | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=koyu@1 -> siraya-duyarli
kural-damga | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@2 ba=theme=acik@2 -> saglam
kural-damga-esit | bir=theme=koyu@1 iki=theme=koyu@1 ab=theme=acik@1 ba=theme=koyu@1 -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 2
cift-duyarli: 0

Aynı iki yazma, kurala göre iki ayrı sınıfa düştü. Geliş sırası kuralında sıraya duyarlı; en büyük damga kuralında sağlam, çünkü büyük damgalı yazma hangi sırayla gelirse gelsin kazanır. Damgalar eşit olunca sıra geri döndü: ab acik, ba koyu. Damga sırayı kaldırmadı, yalnız sıranın devreye girdiği durumu daralttı.

Sınır KL14’tür. Model, sunucunun kuralı doğru ve tutarlı uyguladığını varsayar. Gerçek bir hizmette kuralın nerede ve nasıl uygulandığı, hatanın ne zaman döndüğü ve yazmaların hangi sırayla vardığı bu modelin dışındadır. Ölçülen şey kuralın sonucudur; kuralın gerçekten işlediği değil.

Gerçek Uygulamada Nerede Görülür

Bir notlar uygulamasını düşünün. Kullanıcı telefonda bir notun başlığını değiştirir, aynı anda tablette aynı notun başlığını başka bir şeye çevirir. İki aygıt da çevrimdışıdır ve birkaç dakika sonra bağlanır. Sunucuya hangisi önce ulaşırsa ulaşsın, ikinci gelen birincinin yerine geçer; kalan başlığı ağın gecikmesi belirler, kullanıcı değil. Ölçümdeki iki-aygit-ayni-alan satırı bu durumun modelidir.

Etiketler ise farklıdır. Telefon bir etiket ekler, tablet başka bir etiket ekler. Alan bir küme olarak tutulur ve sunucu iki kümenin birleşimini kaydeder. Hangi aygıtın önce ulaştığı artık önemli değildir: kume-birlesimi satırında iki sıra da aynı sonucu verdi. Bu fark, bir uygulamanın veri modelini kurarken ilk sorulacak sorudur: bu alan birleştirilebilir mi, yoksa bir kazanan mı seçilmelidir?

Silme işlemi ayrı bir sorun getirir. Bir aygıt kaydı siler, öteki aynı kaydı günceller. Sunucu kaydı silinmiş mi sayacak, güncellenmiş mi? Bu dersin modeli silmeyi kurmaz; ama gerçek bir eşitleme tasarımında bu karar, skaler çakışma kuralı kadar önemlidir ve açıkça verilmelidir.

Yanlış Kurulum ve Düzeltmesi

En yaygın yanlış kurulum, her aygıtın kayıtların tamamını yerel saatiyle damgalayıp en yeni damgayı kazandırmaktır. Aygıt saatleri sapar; kullanıcı saati elle değiştirebilir, ve bir aygıtın saati yanlış ileri olunca onun bütün yazmaları, sonradan yapılan doğru yazmaları ezer. Sınırlayıcı ölçümde gösterdiğimiz gibi, damga kuralı sıra bağımlılığını azaltır ama damgalar eşit olunca sıra geri döner. Gerçek saatler söz konusu olduğunda bu eşitlik nadir değildir.

Düzeltme iki katmanlıdır. Birincisi, damgayı aygıt saatinden değil, sunucunun verdiği artan bir sürüm sayısından almaktır; böylece damgalar karşılaştırılabilir ve sapma etkisizdir. İkincisi, mümkün olan her alanı birleştirilebilir bir yapıya taşımaktır. Etiket kümesi, okundu işareti kümesi, ek dosya listesi bu sınıftandır. Birleşen alanlar için çakışma kuralı gerekmez.

İkinci yanlış, çakışma hatasını yoksaymaktır. Sunucu kaydın bu arada değiştiğini bildirdiğinde uygulama sessiz kalırsa, kullanıcının değişikliği hiç kaydedilmemiş olur ve kullanıcı bunu fark etmez. Doğru kurulum, hatayla dönen sunucu sürümünü alıp kendi kuralını uygulamak ve sonucu yeniden göndermektir. KL15 modelin damgaları dışarıdan verilen sayılar olarak aldığını kaydeder; gerçek saat sapması modelde yoktur.

KAYNAK Parçayı Okumak

Dersin başındaki adsız parça, bir kaydı oluşturur, bir alanına değer yazar ve özel bir veritabanına kaydeder. Kaydetme çağrısının kapanışı, işlemin sonucunu bir hata ya da kaydedilmiş kayıt olarak bildirir. Hata türlerinden biri, kaydedilmek istenen kaydın sunucudaki sürümünün bu arada değiştiğini söyler. Bu hata bir istisna değil, normal bir sonuçtur: çok aygıtlı bir uygulamada çakışma er ya da geç olur.

Modelimiz bu hatayı ve gerçek teslim sırasını kurmaz; KL14 sunucunun kuralı tutarlı uyguladığını varsayar, KL16 ise her yazmanın sunucuya ulaştığını. Gerçek ağda yazmalar kaybolur, gecikir ve yinelenir. Yinelenen bir yazmanın sunucuda ikinci kez uygulanması, yinelemeye duyarlılık sorusunun yeniden gündeme gelmesidir ve bir sonraki derste, bildirimlerin iki kez gelmesi bağlamında ele alınacaktır.

Ölçümden Tasarıma

Eşitleme tasarımında ölçümün söylediği şey, alan alan karar vermek gerektiğidir. Her alan için üç seçenekten biri seçilir: birleştirilebilir bir yapıya çevirmek, damgayla bir kazanan seçmek ya da çakışmayı kullanıcıya sormak. Etiketler, işaretler ve listeler birinci gruba girer; ölçümde sağlam çıktılar. Ad, tema ve tarih gibi alanlar ikinci gruptadır ve en iyi ihtimalle sıra bağımlılığını daraltır. Üçüncü grup nadirdir ama bazı veriler için tek dürüst seçenektir; kullanıcının iki farklı metin arasında seçim yapması, birini sessizce silmekten iyidir.

Kural her zaman yazılı olmalıdır. “Son yazan kazanır” bir kuraldır ve doğru bir kuraldır, yeter ki bilinçli seçilmiş olsun. Tehlikeli olan, kuralın hiç düşünülmemesi ve sunucunun varsayılanının ürünün kuralı hâline gelmesidir.

Bu konunun zinciri burada kapanır. Basit tercihte iki ekranın yazısı sıraya bağlıydı, anahtarlıkta ekleme yinelemeye, dosyada hem yinelemeye hem sıraya, tabloda anahtara, nesne çerçevesinde kimliğe ve eşitlemede alanın doğasına. Her katmanda aynı iki soru sorulmuştur: yinelenirse ve sırası değişirse ne olur. Cevaplar değişti, soru değişmedi. Kalıcılığın her katmanı yeni bir kusur getirmez; aynı iki kusuru yeni bir yerde barındırır.

Son bir not: eşitleme başarısı kullanıcıya nasıl gösterilir sorusu da tasarımın parçasıdır. Değişiklik yerelde hemen görünür, sunucuya sonra gider. Kullanıcı, değerin henüz gönderilmediğini bilmezse ve aygıtı kapatırsa, değişiklik bekleyen bir kuyrukta kalır. Bekleyen işlemleri saklamak ve bağlantı dönünce yeniden göndermek gerekir; yeniden gönderme de yinelemedir. Bu yüzden eşitlemenin bekleyen yazmaları, yinelemeye karşı sağlam olmak zorundadır.

Özet

  • Bulut eşitleme aynı kaydı birden çok aygıtın yazmasına izin verir; çakışmada hangi değerin kalacağını bir kural belirler.
  • İki aygıtın aynı alana yazdığı farklı değerler geliş sırasına bağlıdır: ab acik, ba koyu; sınıf sıraya duyarlıdır.
  • Alan başına küme birleşimi sağlamdır: iki sıra da {a1,b2} verir.
  • En büyük damga kuralı aynı yazma çiftini sağlam yapar, ama damgalar eşit olunca sıra geri döner.
  • Sunucu kuralı, gerçek teslim sırası ve çakışma hatası ölçülmedi; KL14 olarak kaldı.
  • Bu dersin duyarlılığı: sıraya duyarlı.

Sonraki Adım

Eşitleme, değerin aygıtlar arasında nasıl yürüdüğünü anlattı. Sıradaki derste, Uzak Bildirimler, sunucunun uygulamaya kendiliğinden haber vereceği yola geçeceğiz: bir aygıt belirteci, bir izin ve bir rozetin yanında, aynı bildirimin iki kez gelmesinin sonucu değiştirip değiştirmediğini ölçeceğiz.

İ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