Cache Laravel: dasar-dasar ditambah kiat & trik

Teknik caching memungkinkan Anda membuat aplikasi yang lebih skalabel, menyimpan hasil beberapa kueri dalam penyimpanan cepat di memori. Namun, caching yang diimplementasikan secara tidak tepat dapat sangat menurunkan kesan pengguna terhadap aplikasi Anda. Artikel ini berisi beberapa konsep dasar tentang caching, berbagai aturan dan tabu yang telah saya pelajari dari beberapa proyek sebelumnya.


Jangan gunakan caching.


Apakah proyek Anda cepat dan tidak memiliki masalah kinerja?
Lupakan tentang caching. Serius :)


Ini akan sangat menyulitkan operasi membaca dari database tanpa manfaat apa pun.


Benar, Mohamed Said di awal artikel ini membuat beberapa perhitungan dan membuktikan bahwa dalam beberapa kasus, mengoptimalkan aplikasi selama milidetik dapat menghemat banyak uang di akun AWS Anda. Jadi, jika penghematan yang diproyeksikan pada proyek Anda lebih dari $ 1,86, maka mungkin caching adalah ide yang bagus.


Bagaimana cara kerjanya?


Ketika sebuah aplikasi ingin mendapatkan beberapa data dari database, misalnya, entitas Post dengan id-nya, ia menghasilkan kunci caching yang unik untuk kasus ini ( 'post_' . $id sangat cocok) dan mencoba untuk menemukan nilai dengan kunci ini di penyimpanan nilai kunci cepat (memcache, redis, atau lainnya). Jika nilainya ada, maka aplikasi menggunakannya. Jika tidak, ini diambil dari database dan disimpan dalam cache oleh kunci ini untuk digunakan di masa depan.



Menyimpan nilai ini dalam cache bukanlah ide yang baik selamanya, karena entitas Post ini dapat diperbarui, tetapi aplikasi akan selalu menerima nilai yang lama, di-cache.
Oleh karena itu, fungsi caching biasanya menanyakan berapa nilai ini harus disimpan.


Setelah waktu ini berakhir, memcache atau redis akan "lupa" tentang hal itu dan aplikasi akan mengambil nilai segar dari database.


Contoh:


 public function getPost($id): Post { $key = 'post_' . $id; $post = \Cache::get($key); if($post === null) { $post = Post::findOrFail($id); \Cache::put($key, $post, 900); } return $post; } 

Di sini saya meletakkan entitas Post dalam cache selama 15 menit (sejak versi 5.8, laravel menggunakan detik dalam parameter ini, sebelum ada menit). Fasad Cache juga memiliki metode remember mudah digunakan untuk kasus ini. Kode ini melakukan hal yang persis sama dengan yang sebelumnya:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { return Post::findOrFail($id); }); } 

Ada bab Cache dalam dokumentasi Laravel yang menjelaskan cara menginstal driver yang diperlukan untuk aplikasi Anda dan fungsi utama.


Data dalam cache


Semua driver Laravel standar menyimpan data sebagai string. Ketika kami meminta Anda untuk membuat cache instance dari model Eloquent, ia menggunakan fungsi serialisasi untuk mendapatkan string dari objek. Fungsi unserialize mengembalikan keadaan suatu objek ketika kita mendapatkannya dari cache.


Hampir semua data dapat di-cache. Bilangan, string, array, objek (jika serialisasi dengan benar, lihat deskripsi fungsi dari tautan sebelumnya).


Entitas yang fasih dan koleksi dapat dengan mudah di-cache dan merupakan nilai yang paling populer di cache aplikasi Laravel. Namun, penggunaan jenis lain juga dipraktikkan cukup luas. Metode Cache::increment populer untuk menerapkan berbagai penghitung. Juga, kunci atom sangat berguna ketika pengembang berjuang melawan kondisi balapan .


Apa yang di-cache?


Calon pertama untuk caching adalah permintaan yang dieksekusi sangat sering, tetapi rencana eksekusi mereka bukan yang termudah. Contoh terbaik adalah 5 artikel teratas di halaman utama, atau berita terbaru. Caching nilai-nilai tersebut dapat sangat meningkatkan kinerja halaman utama.


Biasanya, mengambil entitas dengan id menggunakan Model::find($id) sangat cepat, tetapi jika tabel ini sarat dengan banyak pembaruan, masukkan dan hapus kueri, mengurangi jumlah kueri pemilihan akan memberikan kelonggaran yang baik untuk database. Entitas yang memiliki banyak hubungan yang akan dimuat setiap saat juga merupakan kandidat yang baik untuk caching. Ketika saya mengerjakan sebuah proyek dengan 10+ juta pengunjung sehari, kami men-cache hampir semua permintaan pilih.


Pembatalan cache


Pembusukan kunci setelah waktu tertentu membantu memperbarui data dalam cache, tetapi ini tidak terjadi segera. Pengguna dapat mengubah data, tetapi untuk beberapa waktu ia akan terus melihat versi lama dari mereka dalam aplikasi. Dialog biasa pada salah satu proyek masa lalu saya:


 :   ,     ! : ,  15 ( ,  )... 

Perilaku ini sangat merepotkan bagi pengguna, dan keputusan yang jelas untuk menghapus data lama dari cache ketika kami memperbaruinya dengan cepat terlintas dalam pikiran. Proses ini disebut disabilitas. Untuk kunci sederhana seperti "post_%id%" , "post_%id%" tidak terlalu sulit.


Peristiwa fasih dapat membantu, atau jika aplikasi Anda menghasilkan acara khusus seperti PostPublished atau UserBanned itu bisa lebih sederhana. Contoh dengan acara Eloquent. Pertama, Anda perlu membuat kelas acara. Untuk kenyamanan, saya akan menggunakan kelas abstrak untuk mereka:


 abstract class PostEvent { /** @var Post */ private $post; public function __construct(Post $post) { $this->post = $post; } public function getPost(): Post { return $this->post; } } final class PostSaved extends PostEvent{} final class PostDeleted extends PostEvent{} 

Tentu saja, menurut PSR-4, setiap kelas harus dalam file sendiri. Atur kelas Post Eloquent (menggunakan dokumentasi ):


 class Post extends Model { protected $dispatchesEvents = [ 'saved' => PostSaved::class, 'deleted' => PostDeleted::class, ]; } 

Buat pendengar untuk acara ini:


 class EventServiceProvider extends ServiceProvider { protected $listen = [ PostSaved::class => [ ClearPostCache::class, ], PostDeleted::class => [ ClearPostCache::class, ], ]; } class ClearPostCache { public function handle(PostEvent $event) { \Cache::forget('post_' . $event->getPost()->id); } } 

Kode ini akan menghapus nilai yang di-cache setelah setiap pembaruan atau penghapusan Entitas pos. Daftar entitas yang tidak valid, seperti 5 artikel teratas atau berita terbaru, akan sedikit lebih rumit. Saya melihat tiga strategi:


Jangan Nonaktifkan Strategi


Hanya saja, jangan menyentuh nilai-nilai ini. Biasanya, ini tidak membawa masalah. Tidak apa-apa bahwa berita baru akan muncul dalam daftar yang terakhir nanti (tentu saja, jika ini bukan portal berita besar). Tetapi untuk beberapa proyek, sangat penting untuk memiliki data baru dalam daftar ini.


Temukan dan Defuse Strategy


Setiap kali Anda memperbarui publikasi, Anda dapat mencoba menemukannya di daftar yang di-cache, dan jika ada, hapus nilai yang di-cache ini.


 public function getTopPosts() { return \Cache::remember('top_posts', 900, function() { return Post::/*   top-5*/()->get(); }); } class CheckAndClearTopPostsCache { public function handle(PostEvent $event) { $updatedPost = $event->getPost(); $posts = \Cache::get('top_posts', []); foreach($posts as $post) { if($updatedPost->id == $post->id) { \Cache::forget('top_posts'); return; } } } } 

Terlihat jelek, tetapi berhasil.


Strategi "id penyimpanan"


Jika urutan item dalam daftar tidak penting, maka dalam cache Anda hanya dapat menyimpan entri id. Setelah menerima id, Anda dapat membuat daftar kunci dari form 'post_'.$id dan mendapatkan semua nilai menggunakan metode Cache::many , yang mendapatkan banyak nilai dari cache dalam satu permintaan (ini juga disebut multi get).


Pembatalan cache tidak sia-sia disebut salah satu dari dua kesulitan dalam pemrograman dan sangat sulit dalam beberapa kasus.


Caching Hubungan


Caching entitas dengan hubungan membutuhkan peningkatan perhatian.


 $post = Post::findOrFail($id); foreach($post->comments...) 

Kode ini melakukan dua kueri SELECT . Mendapatkan entitas dengan id dan komentar oleh post_id . Kami menerapkan caching:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { return Post::findOrFail($id); }); } $post = getPost($id); foreach($post->comments...) 

Permintaan pertama di-cache, dan yang kedua tidak. Ketika driver cache menulis Posting ke cache, comments belum dimuat. Jika kita ingin menyimpannya juga, maka kita harus memuatnya secara manual:


 public function getPost($id): Post { return \Cache::remember('post_' . $id, 900, function() use ($id) { $post = Post::findOrFail($id); $post->load('comments'); return $post; }); } 

Kedua permintaan sekarang di-cache, tetapi kita harus membatalkan nilai 'post_'.$id setiap kali komentar ditambahkan. Ini tidak terlalu efisien, oleh karena itu lebih baik menyimpan cache komentar secara terpisah:


 public function getPostComments(Post $post) { return \Cache::remember('post_comments_' . $post->id, 900, function() use ($post) { return $post->comments; }); } $post = getPost($id); $comments = getPostComments($post); foreach($comments...) 

Terkadang esensi dan sikap sangat terkait satu sama lain dan selalu digunakan bersama (memesan dengan perincian, publikasi dengan terjemahan ke dalam bahasa yang diinginkan). Dalam hal ini, menyimpannya dalam satu cache cukup normal.


Sumber kebenaran tunggal untuk kunci cache


Jika proyek mengimplementasikan pembatalan, kunci cache dihasilkan di setidaknya dua tempat: untuk memanggil Cache::get / Cache::remember dan untuk memanggil Cache::forget . Saya sudah mengalami situasi ketika kunci ini diubah di satu tempat, tetapi tidak di tempat lain, dan cacatnya pecah. Saran yang biasa untuk kasus seperti ini adalah konstanta, tetapi kunci cache dihasilkan secara dinamis, jadi saya menggunakan kelas khusus yang menghasilkan kunci:


 final class CacheKeys { public static function postById($postId): string { return 'post_' . $postId; } public static function postComments($postId): string { return 'post_comments' . $postId; } } \Cache::remember(CacheKeys::postById($id), 900, function() use ($id) { $post = Post::findOrFail($id); }); // .... \Cache::forget(CacheKeys::postById($id)); 

Masa hidup kunci juga dapat diberikan dalam konstanta demi keterbacaan yang lebih baik. 900 atau 15 * 60 ini meningkatkan beban kognitif saat membaca kode.


Jangan gunakan cache dalam operasi tulis


Saat menerapkan operasi tulis, seperti mengubah judul atau teks publikasi, tergoda untuk menggunakan metode getPost yang ditulis sebelumnya:


 $post = getPost($id); $post->title = $newTitle; $post->save(); 

Tolong jangan lakukan itu. Nilai dalam cache mungkin kedaluwarsa, bahkan jika pembatalan dilakukan dengan benar. Kondisi ras kecil dan penerbitan akan kehilangan perubahan yang dibuat oleh pengguna lain. Kunci optimis akan membantu setidaknya untuk tidak kehilangan perubahan, tetapi jumlah permintaan yang salah dapat sangat meningkat.


Solusi terbaik adalah dengan menggunakan logika pemilihan entitas yang sangat berbeda untuk operasi baca dan tulis (halo, CQRS). Dalam operasi tulis, Anda selalu perlu memilih nilai terbaru dari database. Dan jangan lupa tentang kunci (optimis atau pesimis) untuk data penting.


Saya pikir ini cukup untuk artikel pengantar. Caching adalah topik yang sangat kompleks dan panjang, dengan jebakan untuk pengembang, tetapi peningkatan kinerja terkadang melebihi semua kesulitannya.

Source: https://habr.com/ru/post/id463495/


All Articles