โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 3 ยท Arsitektur Laravel

Cache

Cache mudah dipasang dan sulit dibersihkan. Materi ini tentang memilih driver yang benar, memberi nama kunci yang bisa dihapus, dan menghindari dua jebakan klasik: stampede dan data basi.

Sumber asli laravel.com Resmi Rangkuman ~7 menit baca

Intisari

  • Cache::remember($kunci, $ttl, $closure) menutup 90% kebutuhan.
  • Driver file dan array tidak dibagi antar kontainer โ€” di AWS berarti Redis.
  • Rancang kunci-nya sejak awal; kunci yang tidak berpola tidak bisa dihapus secara selektif.
  • TTL pendek untuk data yang berubah; invalidasi eksplisit untuk data yang harus benar.
  • Cache stampede: saat entri populer kedaluwarsa, semua permintaan menghitung ulang bersamaan. Obatnya Cache::lock() atau flexible().

Pola yang dipakai sehari-hari

$kategori = Cache::remember('kategori:aktif', now()->addHour(), function () {
    return Kategori::where('aktif', true)->orderBy('urutan')->get();
});

Cache::rememberForever('pengaturan', fn () => Pengaturan::pluck('nilai', 'kunci'));

Cache::put('kunci', $nilai, now()->addMinutes(10));
Cache::get('kunci', 'nilai default');
Cache::forget('kunci');
Cache::flush();                        // hampir tidak pernah benar di produksi

Cache::increment('kunjungan:'.date('Y-m-d'));

// Laravel 13: perpanjang TTL tanpa membaca dan menulis ulang nilainya
Cache::touch('sesi:'.$id, now()->addMinutes(30));

Memilih driver

DriverDibagi antar proses?Kapan
arrayTidak โ€” hilang di akhir permintaanTes
fileHanya dalam satu mesinPengembangan lokal
databaseYaSkala kecil; menambah beban ke database yang justru ingin kamu ringankan
redisYaPilihan produksi. Mendukung lock dan tag
dynamodbYaServerless penuh, tanpa mengelola node
octanePer proses workerSangat cepat, tapi tiap worker punya salinannya sendiri (Fase 8)

Ini penyebab bug "kadang benar, kadang basi". Dengan driver file dan tiga kontainer, Cache::forget() hanya menghapus salinan di kontainer yang kebetulan menangani permintaan itu. Dua kontainer lain tetap menyajikan data lama sampai TTL-nya habis โ€” dan gejalanya berubah-ubah tergantung ke mana load balancer mengirim permintaan. Begitu ada lebih dari satu proses aplikasi, cache harus bersama.

Rancang kuncinya, bukan cuma nilainya

// Buruk โ€” tidak bisa dihapus secara selektif
Cache::remember('data', 3600, ...);

// Baik โ€” berpola, mengandung semua yang membedakan hasilnya
Cache::remember("produk:{$produk->id}:v2", 3600, ...);
Cache::remember("katalog:kategori:{$k->id}:halaman:{$halaman}", 600, ...);
Cache::remember("dasbor:tenant:{$tenantId}:harian:".today()->toDateString(), 3600, ...);

Dua hal yang wajib masuk ke kunci. Pertama, segala sesuatu yang mengubah hasilnya โ€” termasuk ID tenant dan ID pengguna. Kunci yang lupa menyertakan tenant adalah cara paling langsung menyajikan data satu pelanggan kepada pelanggan lain. Kedua, sebuah penanda versi: saat bentuk datanya berubah, naikkan v2 jadi v3 dan seluruh entri lama menjadi tidak relevan tanpa perlu dihapus satu per satu.

Invalidasi

// 1. TTL pendek โ€” paling sederhana, cocok untuk data yang tidak harus persis
Cache::remember('statistik', now()->addMinutes(5), ...);

// 2. Eksplisit saat data berubah โ€” untuk data yang harus benar
class ProdukObserver
{
    public function saved(Produk $p): void   { $this->bersihkan($p); }
    public function deleted(Produk $p): void { $this->bersihkan($p); }

    private function bersihkan(Produk $p): void
    {
        Cache::forget("produk:{$p->id}:v2");
        Cache::forget("katalog:kategori:{$p->kategori_id}:halaman:1");
    }
}

// 3. Tag โ€” hapus sekelompok sekaligus (Redis dan Memcached saja)
Cache::tags(['produk', "kategori:{$id}"])->remember('daftar', 3600, ...);
Cache::tags(["kategori:{$id}"])->flush();

Cache stampede

Sebuah entri populer kedaluwarsa pada pukul 09:00:00. Dalam detik itu, dua ratus permintaan menemukan cache kosong, dan dua ratus di antaranya menjalankan query mahal yang sama secara bersamaan. Database yang tadinya santai langsung tersedak.

// Cara 1 โ€” kunci: hanya satu yang menghitung, sisanya menunggu hasilnya
$nilai = Cache::lock('hitung:laporan', 10)->block(5, function () {
    return Cache::remember('laporan', 3600, fn () => $this->hitungBerat());
});

// Cara 2 โ€” nilai basi disajikan dulu, penyegarannya dilakukan di latar
$nilai = Cache::flexible('laporan', [300, 3600], fn () => $this->hitungBerat());

flexible() menerima dua angka: selama 300 detik nilainya dianggap segar; antara 300 dan 3600 detik nilainya tetap disajikan sambil diperbarui di latar belakang. Tidak ada satu pun pengguna yang menunggu perhitungan ulang.

Apa yang layak di-cache

KandidatLayak?Catatan
Data referensi (kategori, wilayah)SangatJarang berubah, sering dibaca
Hasil agregasi mahalSangatPakai flexible()
Respons API pihak ketigaYaSekaligus melindungi dari rate limit mereka
Detail satu produkKadangUkur dulu โ€” sering kali index sudah cukup
Data khusus penggunaHati-hatiID pengguna wajib ada di kunci
Saldo, stok, harga aktifTidakBasi beberapa detik saja sudah merugikan

Cache bukan pengganti index. Query 800 ms yang di-cache tetaplah query 800 ms โ€” kamu cuma memindahkan penderitaannya ke pengguna yang apes mendapat cache miss. Perbaiki query-nya dulu (Fase 7), baru pertimbangkan cache.

Latihan: cache daftar kategori dengan kunci berpola dan TTL satu jam, lalu tambahkan observer yang menghapusnya saat kategori berubah. Setelah itu ganti CACHE_STORE jadi file, jalankan dua proses php artisan serve di port berbeda, dan buktikan sendiri bahwa forget() di satu proses tidak berpengaruh pada yang lain.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.