İçeriğe geç
academia.sh

Ders 13 / 14

Sayfalı Veri Yükleme

Uzun bir liste tek seferde değil, sayfa sayfa yüklenir; her sayfa bir sayfa anahtarıyla istenir. Ders henüz yüklenmemiş sayfadaki ögenin okumasının kökenini, sayfa yüklenince kökenin değişimini ve aynı ögenin ağdan ya da diskten gelmesini ölçer.

Yayın Güncelleme

Bu derste

  • Uzun bir listenin neden sayfa sayfa yüklendiğini ve sayfa anahtarının görevini açıklar
  • Sayfa kaynağı ile sayfalayıcının iş bölümünü ayırır
  • Henüz yüklenmemiş sayfadaki ögenin okumasının kökenini varsayılan olarak ölçer
  • Sayfa yüklenince aynı ögenin kökeninin ağ ya da disk olduğunu gösterir
İçindekiler

Önceki derste ekran tek bir ürünü okudu ve disk onu tek seferde tutabildi. Bir ürün listesi binlerce satır olabilir ve hepsini birden istemek hem ağa hem belleğe fazla gelir. Liste bu yüzden sayfa sayfa yüklenir: kullanıcı aşağı indikçe sıradaki sayfa istenir. Bu derste o ana bakıyoruz, yani listenin henüz yüklenmemiş kısmına. Dersin sorusu önceki derslerle aynıdır: bu okumanın yanıtı nereden geliyor?

// Origin.kt — bir okumanin yanitinin hangi basamaktan geldigini siniflandirir
class Tier(val origin: String, private val entries: Map<String, String>) {
    init {
        require(origin in listOf("bellek", "disk", "ag")) { "gecersiz basamak: $origin" }
    }

    fun lookup(key: String): String? = entries[key]
}

class Read(val label: String, val key: String, chain: List<Tier>, fallback: String) {
    private val hit: Tier? = chain.firstOrNull { it.lookup(key) != null }
    val value: String = hit?.lookup(key) ?: fallback
    val origin: String = hit?.origin ?: "varsayilan"

    fun show() {
        println("$label: anahtar=$key | deger=$value -> $origin")
    }
}

fun countOrigins(reads: List<Read>) {
    for (name in listOf("bellek", "disk", "ag", "varsayilan")) {
        println("$name: ${reads.count { it.origin == name }}")
    }
}

Sayfa Sayfa Yükleme

Sayfalı yüklemede iki rol vardır. Sayfa kaynağı (paging source), bir sayfa anahtarı verildiğinde o sayfanın ögelerini getirir ve bir sonraki sayfanın anahtarını söyler. Sayfalayıcı (pager), kullanıcı listede ilerledikçe sayfa kaynağını çağırır ve gelen sayfaları arayüze akış olarak iletir. Sayfa anahtarı bir sayfa numarası, bir imleç ya da son ögenin kimliği olabilir.

// Platform kodu: ProductPagingSource
class ProductPagingSource(private val api: ProductApi) : PagingSource<Int, ProductDto>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ProductDto> {
        val page = params.key ?: 0
        val items = api.products(page = page, size = params.loadSize)
        return LoadResult.Page(
            data = items,
            prevKey = if (page == 0) null else page - 1,
            nextKey = if (items.isEmpty()) null else page + 1,
        )
    }
}

load, anahtarı verilen sayfayı ister; liste boş dönerse sonraki anahtar null olur ve yükleme biter. Bu parça platform kodudur ve burada çıktı, sayfa boyutu ya da süre iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

// Platform kodu: ProductPager
val products: Flow<PagingData<ProductDto>> = Pager(
    config = PagingConfig(pageSize = 20),
    pagingSourceFactory = { ProductPagingSource(api) },
).flow

Sayfalayıcı, sayfa kaynağını bir yapılandırmayla birlikte kurar ve sonucu akış olarak verir; arayüz bu akışı toplar. Bu parça da platform kodudur ve çıktı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Modelde bir sayfa, kaynaktan alınmış ardışık anahtarların eşlemidir. Sayfa n ve boyut s verildiğinde n*s+1 ile (n+1)*s arasındaki ögeler yüklenir. Kaynakta olmayan öge eşleme girmez.

// Pages.kt — bir sayfanin ogelerini kaynaktan alip esleme cevirir
fun loadPage(source: Tier, page: Int, size: Int): Map<String, String> =
    ((page * size + 1)..((page + 1) * size)).mapNotNull { n ->
        source.lookup("urun.$n")?.let { "urun.$n" to it }
    }.toMap()

Ekran sıralı ögeleri okur. Her ögenin yedek değeri aynı yer tutucudur.

// List.kt — liste ekraninin okudugu ilk n oge
fun readList(chain: List<Tier>, count: Int): List<Read> =
    (1..count).map { n -> Read("oge$n", "urun.$n", chain, "yukleniyor") }

Yüklenmemiş Sayfa Varsayılandır

Sunucuda dokuz ürün var ve sayfa boyutu üç. Yalnız ilk sayfa ağdan yüklendi; ekran ise altı satırlık bir liste çiziyor.

// Main.kt — yuklenmemis sayfadaki oge yer tutucudan okunur
fun main() {
    val names = listOf("Kalem", "Defter", "Silgi", "Cetvel", "Makas", "Bant", "Zimba", "Mum", "Toka")
    val server = Tier("ag", names.withIndex().associate { (i, name) -> "urun.${i + 1}" to name })
    val loaded = Tier("ag", loadPage(server, 0, 3))
    val reads = readList(listOf(loaded), 6)
    reads.forEach { it.show() }
    countOrigins(reads)
}
oge1: anahtar=urun.1 | deger=Kalem -> ag
oge2: anahtar=urun.2 | deger=Defter -> ag
oge3: anahtar=urun.3 | deger=Silgi -> ag
oge4: anahtar=urun.4 | deger=yukleniyor -> varsayilan
oge5: anahtar=urun.5 | deger=yukleniyor -> varsayilan
oge6: anahtar=urun.6 | deger=yukleniyor -> varsayilan
bellek: 0
disk: 0
ag: 3
varsayilan: 3

Yüklenen sayfadaki üç öge ağdan, yüklenmeyen sayfadaki üç öge yer tutucudan geldi. Bu dersin kökeni varsayılandır: sunucuda urun.4 olsa bile sayfası yüklenmemiş bir ögeyi hiçbir basamak taşımaz. Kural ögenin sunucuda var olup olmadığına değil, zincirin içeriğine bakar.

VK1. Model, sayfayı tek adımda ve bütünüyle yüklenmiş sayar: bir sayfanın üç ögesi ya hep birlikte gelir ya hiç gelmez. Platformda sayfanın yüklenirken hata vermesi, yeniden denenmesi, listenin ortasına ekleme yapılması ve yer tutucuların arayüzde nasıl çizildiği modelde yoktur.

Kullanıcı İndikçe

Kullanıcı listede aşağı indi ve ikinci sayfa da yüklendi. Zincirdeki ağ basamağı artık iki sayfanın ögelerini taşıyor.

// Main.kt — ikinci sayfa yuklenince yer tutucular ag olur
fun main() {
    val names = listOf("Kalem", "Defter", "Silgi", "Cetvel", "Makas", "Bant", "Zimba", "Mum", "Toka")
    val server = Tier("ag", names.withIndex().associate { (i, name) -> "urun.${i + 1}" to name })
    val loaded = Tier("ag", loadPage(server, 0, 3) + loadPage(server, 1, 3))
    val reads = readList(listOf(loaded), 8)
    reads.forEach { it.show() }
    countOrigins(reads)
}
oge1: anahtar=urun.1 | deger=Kalem -> ag
oge2: anahtar=urun.2 | deger=Defter -> ag
oge3: anahtar=urun.3 | deger=Silgi -> ag
oge4: anahtar=urun.4 | deger=Cetvel -> ag
oge5: anahtar=urun.5 | deger=Makas -> ag
oge6: anahtar=urun.6 | deger=Bant -> ag
oge7: anahtar=urun.7 | deger=yukleniyor -> varsayilan
oge8: anahtar=urun.8 | deger=yukleniyor -> varsayilan
bellek: 0
disk: 0
ag: 6
varsayilan: 2

Dördüncü, beşinci ve altıncı öge artık ağdan geliyor. Yedinci ve sekizinci ögenin sayfası hâlâ yüklenmedi ve yer tutucu olarak kalıyor. Sayfa yüklendikçe varsayılan sayısı düşer ve ağ sayısı artar.

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

Aynı ögeyi iki basamak taşıyabilir. İlk sayfa önceki oturumdan diske yazılmıştır ve kullanıcı indikçe iki sayfa da ağdan yüklenir. Hangi basamağın önce geldiği, aynı ögenin kökenini belirler.

// Main.kt — ayni oge diskte de agda da varsa zincirdeki sira kokeni belirler
fun main() {
    val names = listOf("Kalem", "Defter", "Silgi", "Cetvel", "Makas", "Bant", "Zimba", "Mum", "Toka")
    val server = Tier("ag", names.withIndex().associate { (i, name) -> "urun.${i + 1}" to name })
    val cached = Tier("disk", loadPage(server, 0, 3))
    val fetched = Tier("ag", loadPage(server, 0, 3) + loadPage(server, 1, 3))
    val chains = listOf("diskten once" to listOf(cached, fetched), "agdan once" to listOf(fetched, cached))
    for ((name, chain) in chains) {
        println("zincir: $name")
        val reads = readList(chain, 6)
        reads.forEach { it.show() }
        countOrigins(reads)
    }
}
zincir: diskten once
oge1: anahtar=urun.1 | deger=Kalem -> disk
oge2: anahtar=urun.2 | deger=Defter -> disk
oge3: anahtar=urun.3 | deger=Silgi -> disk
oge4: anahtar=urun.4 | deger=Cetvel -> ag
oge5: anahtar=urun.5 | deger=Makas -> ag
oge6: anahtar=urun.6 | deger=Bant -> ag
bellek: 0
disk: 3
ag: 3
varsayilan: 0
zincir: agdan once
oge1: anahtar=urun.1 | deger=Kalem -> ag
oge2: anahtar=urun.2 | deger=Defter -> ag
oge3: anahtar=urun.3 | deger=Silgi -> ag
oge4: anahtar=urun.4 | deger=Cetvel -> ag
oge5: anahtar=urun.5 | deger=Makas -> ag
oge6: anahtar=urun.6 | deger=Bant -> ag
bellek: 0
disk: 0
ag: 6
varsayilan: 0

Değerler iki zincirde de aynı; değişen yalnız kökendir. Disk önce gelince ilk sayfanın üç ögesi diskten, ikinci sayfanın üç ögesi ağdan okunur. Ağ önce gelince altı ögenin altısı ağdandır. Sayfa yüklendikten sonra aynı ögenin kökeni, sayfanın hangi basamaktan yüklendiğine ve zincirin sırasına bağlıdır.

Ölçümün söylemediği şey, iki zincirden hangisinin doğru olduğudur ve disktekinin ağdakinden eski olup olmadığıdır. Kural sırayı görür, tazeliği görmez.

VK2. Model, iki basamağın aynı sayfa sınırlarını kullandığını varsayar. Platformda ağ ile diskteki sayfa boyutlarının farklı olması, sayfa kaynağının uzak ve yerel kaynağı birleştirmesi ve yenilemenin listeyi baştan yüklemesi modelde yoktur.

Özet

  • Bu dersin kökeni: varsayılan. Henüz yüklenmemiş bir sayfadaki ögeyi hiçbir basamak taşımaz ve ekran yer tutucuyu gösterir.
  • Sayfa kaynağı bir sayfa anahtarıyla sayfayı getirir; sayfalayıcı kullanıcı indikçe kaynağı çağırır ve sayfaları akış olarak iletir.
  • Yalnız ilk sayfa yüklenmişken altı satırlık listede ağ 3, varsayılan 3 çıktı.
  • İkinci sayfa yüklenince dördüncü ile altıncı öge ağ olur: sekiz satırda ağ 6, varsayılan 2.
  • Aynı öge hem diskte hem ağda varsa kökeni zincirin sırası belirler: diskten önce gelirse disk 3, ağ 3; ağ önce gelirse ağ 6.

Sonraki Adım

Bu derste sayfalar kullanıcı indikçe, uygulama açıkken yüklendi. Bazı veri ise kullanıcı ekrana bakmazken de yenilenmelidir: ağın uygun olduğu bir anda, şarj sırasında ya da uygulama kapalıyken. Sıradaki ders, Arka Plan İşleri, bu işlerin hangi kısıt sağlandığında çalıştığını ve çalıştığında diske yazan bir işin okumayı hangi kökene getirdiğini ölçer.

İ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