Cache Invalidation - Adakah Solusi Umum?

118

"Hanya ada dua masalah sulit dalam Ilmu Komputer: pembatalan cache dan penamaan."

Phil Karlton

Apakah ada solusi atau metode umum untuk membuat cache tidak valid; tahu kapan entri sudah basi, jadi dijamin Anda akan selalu mendapatkan data terbaru?

Misalnya, pertimbangkan fungsi getData()yang mendapatkan data dari file. Itu cache berdasarkan waktu modifikasi terakhir dari file, yang diperiksa setiap kali dipanggil.
Kemudian Anda menambahkan fungsi kedua transformData()yang mengubah data, dan menyimpan hasilnya untuk kali berikutnya fungsi tersebut dipanggil. Itu tidak memiliki pengetahuan tentang file - bagaimana Anda menambahkan ketergantungan bahwa jika file diubah, cache ini menjadi tidak valid?

Anda dapat memanggil getData()setiap kali transformData()dipanggil dan membandingkannya dengan nilai yang digunakan untuk membuat cache, tetapi itu bisa menjadi sangat mahal.

Greg
sumber
6
Saya percaya dia ada hubungannya dengan menulis X Windows
Greg
1
Saya pikir judul itu akan lebih baik sebagai "Cache Invalidation - Apakah ada Solusi Umum?" karena mengacu pada kelas masalah caching tertentu.
RBarryYoung
71
Tidak, dia tidak tahu banyak tentang ilmu komputer. Saya yakin keterlibatannya dalam membuat OpenGL, X11, dan SSLv3 membuatnya terlalu sibuk untuk benar-benar mempelajarinya. :-)
Tim Lesher
80
Hanya ada 2 masalah sulit dalam ilmu komputer: Pembatalan cache. Memberi nama sesuatu. Dan kesalahan off-by-one.
The Dag
8
Saya pernah mendengar ini sebagai"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Jonathon Reinhart

Jawaban:

55

Apa yang Anda bicarakan adalah rantai ketergantungan seumur hidup, bahwa satu hal bergantung pada hal lain yang dapat dimodifikasi di luar kendalinya.

Jika Anda memiliki fungsi idempoten dari a, bke cmana, jika adan bsama maka csama tetapi biaya pemeriksaannya btinggi maka Anda:

  1. Terimalah bahwa Anda kadang-kadang beroperasi dengan informasi yang kedaluwarsa dan tidak selalu memeriksa b
  2. lakukan yang terbaik untuk melakukan pengecekan bsecepat mungkin

Anda tidak dapat memiliki kue dan memakannya ...

Jika Anda dapat melapisi cache tambahan berdasarkan di aatas maka ini mempengaruhi masalah awal tidak sedikit pun. Jika Anda memilih 1 maka Anda memiliki kebebasan apa pun yang Anda berikan pada diri Anda sendiri dan dengan demikian dapat menyimpan lebih banyak, tetapi harus ingat untuk mempertimbangkan validitas nilai yang di-cache b. Jika Anda memilih 2, Anda masih harus memeriksa bsetiap saat tetapi dapat kembali ke cache ajika bcheck out.

Jika Anda melapisi cache, Anda harus mempertimbangkan apakah Anda telah melanggar 'aturan' sistem sebagai akibat dari perilaku gabungan.

Jika Anda tahu bahwa aselalu memiliki validitas jika bdemikian maka Anda dapat mengatur cache Anda seperti itu (pseudocode):

private map<b,map<a,c>> cache // 
private func realFunction    // (a,b) -> c

get(a, b) 
{
    c result;
    map<a,c> endCache;
    if (cache[b] expired or not present)
    {
        remove all b -> * entries in cache;   
        endCache = new map<a,c>();      
        add to cache b -> endCache;
    }
    else
    {
        endCache = cache[b];     
    }
    if (endCache[a] not present)     // important line
    {
        result = realFunction(a,b); 
        endCache[a] = result;
    }
    else   
    {
        result = endCache[a];
    }
    return result;
}

Jelas layering berturut-turut (katakanlah x) adalah sepele selama, pada setiap tahap validitas input baru ditambahkan sesuai dengan a: bhubungan untuk x: bdan x: a.

Namun sangat mungkin bahwa Anda bisa mendapatkan tiga masukan yang validitasnya sepenuhnya independen (atau siklik), jadi tidak ada pelapisan yang mungkin dilakukan. Ini berarti garis bertanda // penting harus diubah menjadi

jika (endCache [a] kedaluwarsa atau tidak ada)

ShuggyCoUk
sumber
3
atau mungkin, jika biaya pengecekan b tinggi, gunakan pubsub sehingga ketika b berubah ia memberi tahu c. Pola Observer biasa terjadi.
user1031420
15

Masalah dalam pembatalan cache adalah hal-hal berubah tanpa kita menyadarinya. Jadi, dalam beberapa kasus, solusi dimungkinkan jika ada hal lain yang mengetahui tentang hal itu dan dapat memberi tahu kami. Dalam contoh yang diberikan, fungsi getData dapat menghubungkan ke sistem file, yang mengetahui tentang semua perubahan pada file, terlepas dari proses apa yang mengubah file, dan komponen ini pada gilirannya dapat memberi tahu komponen yang mengubah data.

Saya tidak berpikir ada perbaikan ajaib umum untuk membuat masalah hilang. Tetapi dalam banyak kasus praktis mungkin ada peluang untuk mengubah pendekatan berbasis "pemungutan suara" menjadi pendekatan berbasis "interupsi", yang dapat membuat masalah hilang begitu saja.

The Dag
sumber
3

Jika Anda akan getData () setiap kali Anda melakukan transformasi, Anda telah menghilangkan seluruh manfaat cache.

Untuk contoh Anda, sepertinya solusinya adalah ketika Anda menghasilkan data yang diubah, untuk juga menyimpan nama file dan waktu modifikasi terakhir dari file yang menghasilkan data (Anda sudah menyimpan ini dalam struktur data apa pun yang dikembalikan oleh getData ( ), jadi Anda cukup menyalin record itu ke dalam struktur data yang dikembalikan oleh transformData ()) lalu saat Anda memanggil transformData () lagi, periksa waktu terakhir file yang diubah.

Alex319
sumber
3

IMHO, Pemrograman Reaktif Fungsional (FRP) dalam arti tertentu adalah cara umum untuk mengatasi pembatalan cache.

Inilah alasannya: data basi dalam terminologi FRP disebut glitch . Salah satu tujuan FRP adalah untuk menjamin tidak adanya gangguan.

FRP dijelaskan lebih rinci dalam pembicaraan 'Esensi FRP' ini dan dalam jawaban SO ini .

Dalam pembicaraan itu Cells mewakili Objek / Entitas yang di-cache dan di- Cellrefresh jika salah satu dependensinya di-refresh.

FRP menyembunyikan kode pipa yang terkait dengan grafik ketergantungan dan memastikan bahwa tidak ada yang basi Cell.


Cara lain (berbeda dari FRP) yang dapat saya pikirkan adalah membungkus nilai yang dihitung (tipe b) menjadi semacam penulis Monad di Writer (Set (uuid)) bmana Set (uuid)(notasi Haskell) berisi semua pengidentifikasi dari nilai-nilai yang dapat berubah di mana nilai yang dihitung bbergantung. Jadi, uuidadalah semacam pengenal unik yang mengidentifikasi nilai / variabel yang bisa berubah (katakanlah baris dalam database) tempat bbergantung yang dihitung .

Gabungkan ide ini dengan kombinator yang beroperasi pada jenis penulis Monad ini dan itu mungkin mengarah pada semacam solusi pembatalan cache umum jika Anda hanya menggunakan kombinator ini untuk menghitung yang baru b. Kombinator seperti itu (katakanlah versi khusus dari filter) mengambil monad dan (uuid, a)-s Writer sebagai input, di mana adata / variabel yang bisa berubah, diidentifikasi oleh uuid.

Jadi, setiap kali Anda mengubah data "asli" (uuid, a)(misalnya data yang dinormalisasi dalam database yang bdihitung) yang menjadi dasar penghitungan nilai jenis, bAnda dapat membatalkan cache yang berisi bjika Anda mengubah nilai apa pun ayang menjadi tempat bbergantung nilai yang dihitung. , karena berdasarkan pada Set (uuid)Penulis Monad Anda dapat mengetahui kapan hal ini terjadi.

Jadi setiap kali Anda memutasi sesuatu dengan yang diberikan uuid, Anda menyiarkan mutasi ini ke semua cache dan mereka membatalkan nilai byang bergantung pada nilai yang bisa berubah yang diidentifikasi dengan kata uuidkarena monad Writer di mana yang bdibungkus dapat mengetahui apakah itu btergantung pada kata uuidatau tidak.

Tentu saja, ini hanya bermanfaat jika Anda membaca lebih sering daripada menulis.


Pendekatan ketiga, praktis, adalah dengan menggunakan tampilan terwujud dalam database dan menggunakannya sebagai cache. AFAIK mereka juga bertujuan untuk menyelesaikan masalah pembatalan. Ini tentu saja membatasi operasi yang menghubungkan data yang bisa berubah ke data yang diturunkan.

jhegedus.dll
sumber
2

Saya sedang mengerjakan pendekatan sekarang berdasarkan fungsi PostSharp dan memoizing . Saya telah menjalankannya melewati mentor saya, dan dia setuju bahwa ini adalah implementasi cache yang baik dengan cara konten-agnostik.

Setiap fungsi dapat ditandai dengan atribut yang menentukan periode kedaluwarsanya. Setiap fungsi yang ditandai dengan cara ini akan dimo dan hasilnya disimpan ke dalam cache, dengan hash dari pemanggilan fungsi dan parameter yang digunakan sebagai kuncinya. Saya menggunakan Velocity untuk backend, yang menangani distribusi data cache.

Chris McCall
sumber
1

Apakah ada solusi atau metode umum untuk membuat cache, untuk mengetahui kapan entri sudah usang, sehingga Anda dijamin akan selalu mendapatkan data baru?

Tidak, karena semua data berbeda. Beberapa data mungkin "basi" setelah satu menit, beberapa setelah satu jam, dan beberapa mungkin baik-baik saja selama berhari-hari atau berbulan-bulan.

Mengenai contoh spesifik Anda, solusi paling sederhana adalah memiliki fungsi 'pemeriksaan cache' untuk file, yang Anda panggil dari keduanya getDatadan transformData.

Kambing Tidak Puas
sumber
1

Tidak ada solusi umum tetapi:

  • Anda cache dapat bertindak sebagai proxy (tarik). Asumsikan cache Anda mengetahui stempel waktu perubahan asal terakhir, ketika seseorang memanggil getData(), cache menanyakan asal stempel waktu perubahan terakhirnya, jika sama, ia mengembalikan cache, jika tidak ia memperbarui isinya dengan yang sumber dan mengembalikan isinya. (Variasi adalah klien untuk langsung mengirim stempel waktu pada permintaan, sumber hanya akan mengembalikan konten jika stempel waktunya berbeda.)

  • Anda masih dapat menggunakan proses pemberitahuan (push), cache mengamati sumber, jika sumber berubah, ia mengirimkan pemberitahuan ke cache yang kemudian ditandai sebagai "kotor". Jika seseorang memanggil getData()cache pertama-tama akan diperbarui ke sumbernya, hapus tanda "kotor"; lalu kembalikan isinya.

Pilihan secara umum tergantung pada:

  • Frekuensi: banyak panggilan aktif getData()akan memilih push, jadi untuk menghindari sumber dibanjiri oleh fungsi getTimestamp
  • Akses Anda ke sumber: Apakah Anda memiliki model sumber? Jika tidak, kemungkinan Anda tidak dapat menambahkan proses notifikasi apa pun.

Catatan: Karena menggunakan stempel waktu adalah cara tradisional proxy http bekerja, pendekatan lain adalah membagikan hash konten yang disimpan. Satu-satunya cara yang saya tahu untuk 2 entitas untuk mendapatkan pembaruan bersama adalah saya menelepon Anda (tarik) atau Anda menelepon saya… (dorong) itu saja.

Flavien Volken
sumber
-2

Mungkin algoritme yang tidak menyadari cache akan menjadi yang paling umum (Atau setidaknya, lebih sedikit bergantung pada konfigurasi perangkat keras), karena mereka akan menggunakan cache tercepat terlebih dahulu dan melanjutkan dari sana. Berikut adalah kuliah MIT tentang itu: Cache Oblivious Algorithms

CookieOfFortune
sumber
3
Saya pikir dia tidak berbicara tentang cache perangkat keras - dia berbicara tentang kode getData () miliknya yang memiliki fitur yang "menyimpan" data yang dia dapatkan dari file ke dalam memori.
Alex319