Mengoptimalkan performa aplikasi Yii dengan caching bertingkat: data caching lewat component File, Redis, Memcached, dan APCu; query caching untuk query berat; fragment caching untuk potongan view; serta HTTP caching dengan Last-Modified dan ETag di level browser.

Di episode-episode sebelumnya aplikasi kalian sudah berfungsi penuh: CRUD, form, autentikasi, dan API. Sekarang pertanyaannya berubah dari "apakah berfungsi?" menjadi "apakah bertahan saat ribuan user?" Jawabannya ada pada caching — menyimpan hasil komputasi yang mahal agar tidak diulang.
Yii menangani caching dengan cara yang sangat terstruktur: satu komponen cache yang seragam, dan empat lapis penggunaannya — data caching (hasil query/hitung), query caching (SQL mentah yang di-cache database), fragment caching (potongan view), dan HTTP caching (di level browser). Kita bedah semuanya di episode ini.
Semua lapis caching memakai component cache. Perbedaannya hanya di backend penyimpanan. Template basic memakai FileCache; untuk production, pilih yang sesuai:
// FileCache - default template, tanpa dependensi eksternal
'cache' => [
'class' => 'yii\caching\FileCache',
'cachePath' => '@runtime/cache',
],
// APCu - paling cepat untuk single server
'cache' => [
'class' => 'yii\caching\ApcCache',
'useApcu' => true,
],
// Redis - untuk multi server dan session sharing
'cache' => [
'class' => 'yii\redis\Cache',
'redis' => 'redis', // component redis (episode 21)
],
// Memcached - backend cache klasik
'cache' => [
'class' => 'yii\caching\MemcachedCache',
'servers' => [
['host' => '127.0.0.1', 'port' => 11211],
],
],| Backend | Kecepatan | Multi-server | Setup |
|---|---|---|---|
| FileCache | Rendah | Tidak | Tanpa dependensi |
| APCu | Tertinggi (in-memory) | Tidak | Ekstensi PHP |
| Memcached | Tinggi | Ya | Server memcached |
| Redis | Tinggi | Ya | Server redis (episode 21) |
Mulai dari FileCache saat belajar, lalu pindah ke Redis saat production — kita detailkan Redis di episode 21.
Data caching adalah penyimpanan nilai arbitrer dengan masa berlaku (TTL). Pola dasarnya:
use Yii;
// simpan: query mahal dihitung sekali
Yii::$app->cache->set('top_posts', Post::find()->limit(5)->all(), 60);
// ambil
$topPosts = Yii::$app->cache->get('top_posts');
// hapus
Yii::$app->cache->delete('top_posts');Namun pola yang lebih idiomatis adalah getOrSet — cek cache, hitung jika kosong:
$topPosts = Yii::$app->cache->getOrSet('top_posts', function () {
return Post::find()
->where(['status' => 'published'])
->orderBy(['read_count' => SORT_DESC])
->limit(5)
->all();
}, 300);getOrSet menggabungkan get, hitung, dan set dalam satu pemanggilan — menjaga kode tetap bersih dan mencegah cache stampede (banyak request menghitung nilai yang sama saat cache kosong).
Tip
Aturan penting data caching: nama kunci harus spesifik dan versi-aware. top_posts akan basi jika logika berubah — gunakan top_posts_v2, atau sertakan identitas variabel seperti top_posts:user:{id}. Saat ada event yang mengubah data (misal post baru), panggil Yii::$app->cache->delete('top_posts') agar cache tidak menampilkan data basi.
Query caching otomatis meng-cache hasil query database untuk durasi tertentu — kalian tidak perlu menyentuh kode query-nya sama sekali:
use Yii;
$result = Yii::$app->db->cache(function ($db) {
return Post::find()->with('comments')->all(); // di-cache otomatis
}, 60);Bisa juga diaktifkan global di konfigurasi:
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=blog',
'username' => 'root',
'password' => '',
'enableQueryCache' => true,
'queryCacheDuration' => 60,
'queryCache' => 'cache', // pakai component cache
],Query caching bekerja per-DB-connection: dalam blok cache(), semua query DQL di-cache, sedangkan DML (INSERT/UPDATE) otomatis membatalkan cache. Efektif untuk query JOIN berat yang jarang berubah.
Fragment caching menyimpan potongan view yang sudah di-render. Cocok untuk area yang mahal tapi jarang berubah — sidebar, header, daftar rekomendasi:
<?php if ($this->beginCache('sidebar_recent', ['duration' => 300])): ?>
<h3>Post Terbaru</h3>
<ul>
<?php foreach ($recentPosts as $post): ?>
<li><?= Html::encode($post->title) ?></li>
<?php endforeach; ?>
</ul>
<?php $this->endCache(); endif; ?>Perbedaan dari data caching: fragment cache menyimpan output HTML akhir, jadi tidak perlu re-render sama sekali. Fragmen yang bergantung pada user tertentu bisa di-key dengan parameter variasi:
<?php if ($this->beginCache('user_menu_' . Yii::$app->user->id, ['duration' => 60])): ?>
... menu user ...
<?php $this->endCache(); endif; ?>Lapis terluar dan paling hemat: memanfaatkan cache browser lewat header HTTP. Yii menyediakan filter HttpCache dengan dukungan Last-Modified dan ETag:
use yii\filters\HttpCache;
public function behaviors(): array
{
return [
[
'class' => HttpCache::class,
'lastModified' => function ($action, $params) {
// timestamp terakhir data berubah
return (int) Post::find()->max('updated_at');
},
'etagSeed' => function ($action, $params) {
// nilai unik yang berubah saat data berubah
$post = $this->findModel(Yii::$app->request->get('id'));
return $post->updated_at . '|' . $post->title;
},
],
];
}Cara kerjanya: browser mengirim If-Modified-Since/If-None-Match. Jika data belum berubah, Yii membalas 304 Not Modified tanpa me-render halaman — penghematan maksimal di sisi server dan jaringan.
Warning
HTTP caching hanya boleh untuk halaman yang benar-benar sama untuk semua user — jangan cache halaman dashboard yang menampilkan data per-user. Kombinasi yang keliru menghasilkan kebocoran data antar user. Selalu gunakan lastModified/etagSeed berdasarkan data yang relevan dengan halaman tersebut.
Mari terapkan pada skenario nyata: daftar post populer di halaman beranda yang query-nya JOIN comments dan COUNT:
public function actionIndex(): string
{
$popular = Yii::$app->cache->getOrSet('popular_posts', function () {
return Post::find()
->alias('p')
->select(['p.*', 'COUNT(c.id) AS comment_count'])
->joinWith('comments c')
->where(['p.status' => 'published'])
->groupBy('p.id')
->orderBy(['comment_count' => SORT_DESC])
->limit(5)
->all();
}, 300);
return $this->render('index', ['popular' => $popular]);
}Saat post baru dibuat, batalkan cache-nya:
public function afterSave($insert, $changedAttributes): void
{
parent::afterSave($insert, $changedAttributes);
Yii::$app->cache->delete('popular_posts');
}Ukur dampaknya — dengan 100.000 baris di tabel posts, query JOIN COUNT di atas bisa memakan ratusan milidetik; dengan cache, request berikutnya hanya perlu beberapa milidetik karena hasil diambil dari memori.
Inti yang harus dibawa pulang:
cache; pilih backend sesuai kebutuhan (File → APCu → Redis).getOrSet) menyimpan nilai hasil komputasi dengan TTL dan nama kunci versioned.HttpCache + Last-Modified/ETag) menghemat di level browser.afterSave agar data tidak basi.Di episode 12 selanjutnya, kita memindahkan kerja berat ke belakang layar: queue & background tasks — yiisoft/yii2-queue dengan driver Redis, DB, AMQP, dan SQS, konsep job, cara mem-push dan memproses pekerjaan asinkron seperti email dan laporan, serta scheduling worker. Sampai jumpa di episode 12!