İçeriğe geç
academia.sh

Ders 05 / 19

Görsel ve Veri Önbelleği

Bellek ve disk önbelleğinin tahliye kuralı; aynı işlem çiftinin boş bir önbellekte sağlam, dolu bir önbellekte sıraya duyarlı çıkmasının ölçümü.

Yayın Güncelleme

Bu derste

  • Bellek önbelleği ile disk önbelleğinin rollerini ayırır
  • Kapasiteli bir önbellekte tahliye kuralının hangi girdinin atılacağını belirlediğini gösterir
  • Aynı işlem çiftinin boş önbellekte sağlam, dolu önbellekte sıraya duyarlı olduğunu ölçer
  • Sistem önbelleğinin tahliye sırasını belgelenmemiş bir davranış olarak ayırır
İçindekiler

Aynı görseli her ekran açılışında yeniden indirmek hem veriyi hem pili harcar. Önbellek (cache) bu yüzden vardır: daha önce alınmış yanıtı yakında tutar. İki tür önbellek ayırt edilir. Bellek önbelleği hızlıdır ama uygulama kapanınca kaybolur. Disk önbelleği daha yavaştır ama uygulama kapandıktan sonra da durur. İkisi de sınırlıdır; sınır dolunca bir girdi tahliye edilir.

Soru şudur: önbelleğe yazmak ne zaman güvenlidir? Boş bir önbellekte neredeyse her yazma güvenlidir. Dolu bir önbellekte ise hangi girdinin atılacağı, işlemlerin sırasına bağlı olur. Çekirdek aynıdır.

// 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)")
    }
}

Kapasiteli Bir Önbellek

Model, kapasitesi sınırlı bir önbellektir ve en uzun süre kullanılmayan girdiyi atar. Girdiler “anahtar=değer” dizgileridir; listenin başındaki girdi en uzun süredir kullanılmayandır. Durum karşılaştırması ve çıktı, girdileri sıralı gösterir.

// Cache.swift — kapasiteli, en uzun suredir kullanilmayani atan onbellek
struct Cache: Equatable, CustomStringConvertible {
    var capacity: Int
    var entries: [String] = []

    static func == (lhs: Cache, rhs: Cache) -> Bool {
        lhs.capacity == rhs.capacity && lhs.entries.sorted() == rhs.entries.sorted()
    }

    var description: String { "[" + entries.sorted().joined(separator: ",") + "]" }
}

func entryKey(_ entry: String) -> String {
    String(entry.split(separator: "=", maxSplits: 1).first ?? "")
}

func put(_ key: String, _ value: String) -> (Cache) -> Cache {
    { cache in
        var next = cache
        next.entries.removeAll { entryKey($0) == key }
        next.entries.append("\(key)=\(value)")
        while next.entries.count > next.capacity { next.entries.removeFirst() }
        return next
    }
}

func get(_ key: String) -> (Cache) -> Cache {
    { cache in
        var next = cache
        if let index = next.entries.firstIndex(where: { entryKey($0) == key }) {
            let entry = next.entries.remove(at: index)
            next.entries.append(entry)
        }
        return next
    }
}

put aynı anahtarlı eski girdiyi çıkarır, yenisini sona ekler ve kapasite aşılırsa baştakini atar. get bulduğu girdiyi sona taşır; okumak da bir kullanımdır. Model, işlemin duruma ne yaptığını kurar; sistemin önbelleğinin ya da diskin kendisi değildir. == yalnız girdilerin içeriğini karşılaştırır; hangi girdinin ne kadar eski olduğu görünmez.

Boş Önbellekte Üç İşlem

// main.swift — bos onbellekte uc islem
let empty = Cache(capacity: 2)

let sameKey = replay("ayni-anahtar", from: empty, put("profil", "v1"), then: put("profil", "v1")).report()
let twoKeys = replay("iki-anahtar", from: empty, put("profil", "v1"), then: put("akis", "v1")).report()
let writeRead = replay("yaz-oku", from: empty, put("gorsel", "v1"), then: get("profil")).report()

countSensitivities([sameKey, twoKeys, writeRead])
ayni-anahtar | bir=[profil=v1] iki=[profil=v1] ab=[profil=v1] ba=[profil=v1] -> saglam
iki-anahtar | bir=[profil=v1] iki=[profil=v1] ab=[akis=v1,profil=v1] ba=[akis=v1,profil=v1] -> saglam
yaz-oku | bir=[gorsel=v1] iki=[gorsel=v1] ab=[gorsel=v1] ba=[gorsel=v1] -> saglam
saglam: 3
yinelemeye-duyarli: 0
siraya-duyarli: 0
cift-duyarli: 0

Üç işlem de sağlamdır. Aynı anahtara aynı veriyi iki kez yazmak içeriği değiştirmez. İki farklı anahtar, kapasiteyi aşmadığı sürece iki sırada da aynı içeriği bırakır. Olmayan bir anahtarı okumak hiçbir şey yapmaz. Bu bölümde önbellek bir sağlamlık örneğidir ve çoğu zaman da öyledir; bu yüzden önbelleğe yazmak kolay bir iş sanılır.

AG13. Ölçüm kapasiteyi 2 alır. Başka kapasitelerde hangi işlem çiftlerinin dolduracağı değişir.

AG14. Durum karşılaştırması girdilerin yaş sırasını görmez. Sırası farklı ama içeriği aynı iki önbellek eşit sayılır.

Sistemin Önbellekleri

Bellek önbelleği için NSCache, ağ yanıtlarının disk ve bellek önbelleği için URLCache kullanılır.

import Foundation

let memory = NSCache<NSString, NSData>()
memory.countLimit = 100
memory.setObject(Data([1, 2, 3]) as NSData, forKey: "profil" as NSString)
let entry = memory.object(forKey: "profil" as NSString)
import Foundation

let urlCache = URLCache(memoryCapacity: 20_000_000, diskCapacity: 100_000_000, directory: nil)
let configuration = URLSessionConfiguration.default
configuration.urlCache = urlCache
configuration.requestCachePolicy = .returnCacheDataElseLoad

İki parça da KAYNAK kalır. NSCache bellek baskısında girdileri kendisi atabilir ve hangi sırayla atacağını belgelemez; bu yüzden yukarıdaki model onun tahliye politikasını taklit etmez, dersin kendi kapasite kuralını kurar. URLCache yanıtları bellekte ve diskte tutar; hangi yanıtın önbelleğe alınacağını sunucunun başlıkları da etkiler. returnCacheDataElseLoad önbellekte bir şey varsa ağa gitmeden onu verir.

Önbelleğin Neden Sağlam Göründüğü

Üç işlemin üçü de boş bir önbellekte sağlam çıktı. Bu bir önbelleğin her zaman sağlam olduğu anlamına gelmez; yalnız boşken kapasite hiç devreye girmediği için tahliye yok demektir. Boş bir önbellek, aynı anahtarı iki kez yazınca tek girdi tutar ve iki farklı anahtarı hangi sırayla yazarsanız yazın aynı kümeyi verir. Kapasite dolduğunda ise yeni bir girdi eskisini atar ve atılan girdi, işlemlerin sırasına göre değişir. Dersin sınırlayıcı ölçümü tam bunu gösteriyor: aynı işlem çifti dolu önbellekte sıraya duyarlı çıkar.

Bu iki durumun farkını bilmek bir test stratejisini de belirler. Önbelleği boş bir başlangıçla sınayan bir test, sorunun kapasite aşıldığında başladığını görmez. Gerçek bir hata ise çoğunlukla uzun bir kullanımdan sonra, önbellek dolduğunda ortaya çıkar: kullanıcı yüz kadar görsel gezmiş, ilk açtığı görsel tahliye edilmiş ve geri döndüğünde yeniden indirilmiştir. Hatanın niteliği doğruluk değil, performanstır; ama sıra duyarlılığı, hangi girdinin kaybolduğunu belirler.

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

Bir akış ekranı, her satır için küçük bir görsel indirir. Kullanıcı aşağı kaydırdıkça görseller tahliye edilir; yukarı döndüğünde aynı görseller yeniden gerekir. Önbellek yoksa her kaydırma yeniden indirmedir: veri harcanır, ekran titrer. Önbellek varsa, yukarı dönüş anlıktır. Kapasite ise iki kuvvetin dengesidir. Çok büyük bir önbellek belleği şişirir ve sistem uygulamayı sonlandırır; çok küçük bir önbellek, ekranın yarısını yeniden indirir.

Bayatlık ikinci bir sorundur. Profil resmini değiştiren bir kullanıcı, eski resmin önbellekte durduğunu görürse değişikliğin kaydedilmediğini düşünür. Bunun için iki yaygın çözüm vardır: resmin adresine bir sürüm eklemek, böylece adres değişince eski girdi hiç kullanılmaz; ya da değiştirme anında ilgili girdiyi açıkça silmek. İkisi de ölçümdeki put işleminin yinelemeye karşı sağlam olmasına dayanır: aynı anahtarı yeniden yazmak eski girdiyi değiştirir, çoğaltmaz.

Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, görsel adresini önbellek anahtarı olarak kullanıp sorgu parametrelerini hesaba katmamaktır.

import Foundation

// Yanlış: anahtar yalnız yol; aynı yola giden farklı boyutlar çakışır
func cacheKey(for url: URL) -> String {
    url.path
}

// Düzeltme: sorgu da anahtara girer; boyut ya da sürüm değişince anahtar değişir
func cacheKey(for url: URL, size: Int) -> String {
    "\(url.path)?\(url.query ?? "")#\(size)"
}

Bu parça KAYNAK kalır. İlk işlev avatar.png?size=64 ile avatar.png?size=512 adreslerini aynı girdiye eşler ve küçük görsel büyük ekranda bulanık görünür. İkinci işlev sorguyu ve boyutu anahtara katar. Ölçümdeki iki-anahtar satırı bunun modelidir: farklı anahtarlar birbirini etkilemez ve hangi sırayla yazılırlarsa yazılsınlar önbellek aynı kümeyi tutar.

İkinci yanlış, önbelleğe ağdan gelen her yanıtı olduğu gibi koymaktır. Hata sayfası da bir yanıttır; 404 yanıtı önbelleğe girerse görsel sunucuda düzelse bile istemci hatayı göstermeye devam eder. Yalnız başarılı yanıtlar ve çözülebilen görseller saklanmalıdır.

Sistem Önbelleklerinin Sınırı

Sistemin iki önbelleği vardır ve ikisi de farklı bir işi yapar. Bellek önbelleği, yani NSCache, bellek baskısında girdileri kendiliğinden atar; hangi girdinin atılacağı ve ne zaman atılacağı sistemin kararıdır. HTTP önbelleği, yani URLCache, yanıtları sunucunun gönderdiği başlıklara göre saklar ve süresi dolmadan yeniden kullanır. Modelin kurduğu en uzun süredir kullanılmayanı atan kural bu ikisinin birebir karşılığı değildir. Sistemin tahliye kararı ölçülmediği için bu davranış bir varsayım bloğuna girer.

Tahliye Kuralının Ne Sakladığı

Modeldeki kural, en uzun süredir kullanılmayanı atmaktır ve bu kuralın iki yönü vardır. Yazma, yeni bir girdiyi listenin sonuna koyar; okuma ise var olan bir girdiyi sona taşır. Bu yüzden okuma, görünürde değişmeyen bir işlem olsa bile önbelleğin durumunu değiştirir. yaz-oku satırı bunu boş önbellekte göstermedi, çünkü okunan anahtar yoktu. Dolu önbellekte ise okuma, hangi girdinin sonraki tahliyeden kurtulacağını belirler. Sınırlayıcı ölçümdeki ba sütunu tam olarak bunu gösterir: a önce okunduğu için en yeni olur ve atılan b olur.

Bu davranışın gerçek uygulamadaki karşılığı şudur: bir görseli göstermek de onu önbellekte canlı tutar. Kullanıcının sık baktığı profil resmi, yalnız bir kez gösterilen akış görsellerinden daha uzun yaşar. Kural basittir ve çoğu iş yükünde iyi sonuç verir. Ama bir tarama gibi her girdiyi yalnız bir kez okuyan bir iş, sık kullanılan girdileri de dışarı atabilir. Yüzlerce görseli art arda gezen bir kullanıcı, başlangıçta önbellekte duran profil resmini ve simgeleri tahliye eder; geri döndüğünde hepsi yeniden indirilir.

Kapasitenin Seçimi

Kapasite iki kaynak arasında bir uzlaşmadır. Büyük seçilirse bellek tüketimi artar; sistem bellek baskısı altında uygulamayı sonlandırabilir ve önbelleğin sağladığı kazanç bir çöküşe dönüşür. Küçük seçilirse isabet oranı düşer ve önbellek neredeyse hiç iş görmez. İyi bir başlangıç, girdi sayısını değil toplam boyutu sınırlamaktır: yüz küçük simge ile yüz büyük fotoğraf aynı belleği harcamaz. Sistemin bellek önbelleği hem sayı hem de maliyet sınırı alır; modeldeki yalnız sayıya bağlı kapasite bunun basit bir karşılığıdır.

Disk önbelleği için de ayrı bir sınır belirlenir. Disk büyük ve ucuz görünür, ama kullanıcının telefonundaki yer ortak bir kaynaktır ve sistem diski boşaltmak için önbellek dosyalarını silebilir. Bu yüzden diskteki önbellek, kaybolması uygulamayı bozmayan veriyi tutmalıdır; kaybolunca yeniden indirilebilen görseller buna uygun, kullanıcının oluşturduğu içerik uygun değildir.

Bayat Girdi ve Geçerlilik

Önbelleğin en güvenilmez yanı, tuttuğu verinin sunucudaki gerçeğin gerisinde kalabilmesidir. Bunu yönetmenin üç yaygın yolu vardır. İlki ömür sınırıdır: her girdiye bir son kullanma zamanı konur ve süresi dolan girdi okunmaz. İkincisi doğrulamadır: istemci girdiyi kullanmadan önce sunucuya değişip değişmediğini sorar ve değişmediyse yanıt kısa bir onayla döner. Üçüncüsü anahtara sürüm eklemektir; içerik değişince adres de değişir ve eski girdi sahipsiz kalır, sonunda tahliyeyle silinir.

Hangisinin seçileceği verinin ne kadar sık değiştiğine bağlıdır. Hiç değişmeyen bir görsel için sürümlü adres yeterlidir. Sık değişen bir liste için kısa bir ömür sınırı ve doğrulama birlikte kullanılır. Hiçbiri tek başına yeterli değildir: ömür sınırı gereksiz yere veri indirtir, doğrulama her seferinde bir istek gönderir, sürümlü adres ise sunucunun adresleri üretmesini gerektirir.

Önbelleği Sınamak

Önbellekli kodu sınamak, boş önbellekle yetinmemeyi gerektirir. Sağlam çıkan bir ölçümün buradaki uyarısı budur: boş durumda her işlem güvenlidir. İyi bir sınama önbelleği kapasitesine kadar doldurur, sonra tahliyeyi tetikleyen bir işlem ekler ve hangi girdinin kaldığını denetler. Okumanın girdiyi canlı tuttuğunu ve yazmanın en eskiyi attığını ayrı ayrı doğrulamak gerekir. Bu iki davranışı ayrı sınamadan bırakan bir ekip, tahliye sırasındaki bir hatayı ancak üretimde, kullanıcı şikâyet ettiğinde öğrenir.

Zamana bağlı kurallar için gerçek saat yerine sahte bir saat kullanılır; ömür sınırı dolmuş bir girdiyi sınamak için testin saniyelerce beklemesi gerekmez. Dersin modelinin süreyi sayaçla ya da sıralamayla kurması, bu bakımdan da bir sınama kolaylığıdır.

Sık Yapılan Hatalar

  • Önbelleği sınırsız bırakmak. Bellek önbelleği şişer ve sistem uygulamayı sonlandırabilir.
  • Önbellekteki değeri tek doğruluk kaynağı saymak. Önbellek her zaman bayat olabilir.
  • Disk önbelleğindeki dosyaları kullanıcının silemeyeceği önemli veri gibi saklamak. Sistem diski boşaltmak için onları siler.
  • Sistem önbelleğinin tahliye sırasına güvenmek. Sıra belgelenmemiştir.
  • Anahtara yalnız adresi koymak ve sürümü ya da boyutu katmamak. Aynı adresin iki boyutu karışır.

Ölçülen Sonucun Yorumu

Üç işlem de sağlam çıktı. Bu bir sonuç değil, bir başlangıç noktasıdır: işlemlerin hiçbiri önbelleğin sınırına dokunmadı. Önbelleğin ayırt edici özelliği sınırıdır ve sınır dolduğunda yazma artık yalnız bir ekleme olmaz, bir çıkarma da olur.

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

Aşağıda aynı iki işlem, c anahtarını yazmak ve a anahtarını okumak, iki önbellekte uygulanır. Biri boştur, öteki kapasitesine kadar doludur ve a en uzun süredir kullanılmayan girdidir.

// main.swift — ayni islem cifti, bos ve dolu onbellek
let full = put("b", "2")(put("a", "1")(Cache(capacity: 2)))

let onEmpty = replay("bos-onbellek", from: Cache(capacity: 2), put("c", "3"), then: get("a")).report()
let onFull = replay("dolu-onbellek", from: full, put("c", "3"), then: get("a")).report()

countSensitivities([onEmpty, onFull])
bos-onbellek | bir=[c=3] iki=[c=3] ab=[c=3] ba=[c=3] -> saglam
dolu-onbellek | bir=[b=2,c=3] iki=[b=2,c=3] ab=[b=2,c=3] ba=[a=1,c=3] -> siraya-duyarli
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 0

Boş önbellekte a yoktur; okuma bir şey yapmaz ve iki sıra aynı sonucu verir. Dolu önbellekte sıra belirleyicidir. Önce yazılırsa c en eski girdi olan a’yı atar ve okuma boşa gider. Önce okunursa a en yeni olur ve yazma b’yi atar. İki sıra farklı içerik bırakır. Kural önbelleğin dolu olduğunu bilmez, yalnız iki sonucun farklı olduğunu görür.

AG15. Sistemin bellek önbelleği için bu sıra davranışı varsayılmaz. Gerçek bir NSCache girdileri kendi politikasıyla atar ve ölçülen sıra yalnız dersin modeli için geçerlidir.

Özet

  • Bellek önbelleği hızlı ve geçicidir; disk önbelleği daha yavaş ama kalıcıdır; ikisi de sınırlıdır ve dolunca tahliye eder.
  • Aynı anahtarla aynı veriyi yazmak ve iki farklı anahtar yazmak boş önbellekte sağlam çıktı.
  • Dolu önbellekte yazma ile okumanın sırası hangi girdinin atılacağını belirledi.
  • Aynı işlem çifti boş önbellekte sağlam, dolu önbellekte sıraya duyarlıdır.
  • Sistem önbelleğinin tahliye sırası belgelenmemiştir; model dersin kendi kapasite kuralıdır.
  • Bu dersin duyarlılığı: sağlam.

Sonraki Adım

Önbellek, uygulama açıkken çalışan bir mekanizmadır. Sıradaki derste Arka Plan Aktarımı, uygulama arka plandayken sistemin sürdürdüğü indirmelerin yeniden kuyruğa alınmasının aynı dosyayı neden iki kez indirebildiğini ölçecek.

İ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