Caching adalah salah satu teknik paling efektif untuk menurunkan latency, mengurangi beban database, dan meningkatkan throughput aplikasi web. Di CodeIgniter 3, pendekatan caching yang baik bukan sekadar menyalakan satu fitur cache, tetapi menyusun multi-layer caching yang sesuai dengan karakter data dan pola akses aplikasi.

Pada tutorial ini, kita akan membahas strategi caching lanjutan di CodeIgniter 3 meliputi output cache, query cache manual, object cache, dan session-aware cache. Fokusnya bukan hanya cara mengaktifkan cache, tetapi juga kapan sebuah layer sebaiknya dipakai, bagaimana mendesain cache key agar aman dari collision, bagaimana melakukan invalidasi berbasis event, dan bagaimana menyiapkan fallback saat backend cache seperti Redis atau Memcached sedang gagal.

Mengapa Multi-Layer Cache Dibutuhkan

Tidak semua data memiliki pola perubahan dan pola akses yang sama. Halaman landing publik bisa di-cache penuh selama beberapa menit, tetapi profil pengguna yang bergantung pada sesi harus lebih hati-hati. Daftar kategori produk mungkin cocok di object cache, sementara hasil join query berat mungkin perlu disimpan sebagai payload terstruktur di cache backend.

Karena itu, pendekatan yang umum adalah membagi cache menjadi beberapa layer:

  • Output cache untuk menyimpan hasil akhir response HTML.
  • Query cache manual untuk menyimpan hasil query berat di luar mekanisme query cache bawaan database.
  • Object cache untuk menyimpan data terstruktur seperti array, DTO, atau payload API.
  • Session-aware cache untuk response yang berbeda antar pengguna, role, tenant, locale, atau state sesi tertentu.

Prinsip utamanya: cache data yang mahal untuk dihitung, sering dibaca, dan tidak berubah setiap saat. Jangan cache sesuatu hanya karena bisa di-cache.

Arsitektur Caching yang Disarankan

Alur Umum

Arsitektur cache yang sehat di CodeIgniter 3 biasanya mengikuti alur berikut:

  1. Request masuk ke controller.
  2. Cek apakah response bisa diambil dari output cache.
  3. Jika tidak, business logic berjalan.
  4. Model atau service membaca dari object/query cache terlebih dahulu.
  5. Jika cache miss, ambil dari database atau API.
  6. Simpan hasil ke cache dengan TTL yang sesuai.
  7. Render response dan, jika aman, simpan sebagai output cache.

Pemetaan Layer ke Jenis Data

  • Output cache: halaman publik, halaman kategori, dokumentasi statis, daftar artikel.
  • Query cache manual: agregasi dashboard, laporan, query dengan join berat.
  • Object cache: konfigurasi aplikasi, daftar kategori, profil produk, feature flags.
  • Session-aware cache: menu berdasarkan role, keranjang belanja, dashboard per user, preferensi locale.

Memilih Backend Cache: File, APCu, Memcached, atau Redis

CodeIgniter 3 menyediakan Cache Driver dengan beberapa adapter. Pilihan backend sebaiknya mengikuti topologi deployment dan karakter data.

File Cache

Kapan dipakai: aplikasi kecil, single server, deployment sederhana, atau sebagai fallback.

Kelebihan: mudah diaktifkan, tidak perlu service tambahan.

Kekurangan: lambat dibanding memory cache, tidak cocok untuk high concurrency, sulit dipakai optimal di multi-server tanpa shared storage.

APCu

Kapan dipakai: single node PHP-FPM/Apache dengan kebutuhan object cache yang sangat cepat.

Kelebihan: sangat cepat karena in-memory lokal proses/host.

Kekurangan: tidak terdistribusi antar server, data hilang saat process restart, kurang cocok untuk cluster.

Memcached

Kapan dipakai: object cache sederhana dengan skala horizontal dan akses cepat.

Kelebihan: ringan, cepat, cocok untuk cache volatile.

Kekurangan: fitur struktur data terbatas, persistence tidak menjadi fokus utama.

Redis

Kapan dipakai: sistem yang butuh fleksibilitas tinggi, invalidasi lebih terkontrol, atau integrasi advanced.

Kelebihan: kaya fitur, mendukung struktur data, bisa dipakai untuk lock, counter, dan pola invalidasi yang lebih kompleks.

Kekurangan: operasional lebih kompleks dibanding file/APCu.

Jika aplikasi Anda single server, APCu + file fallback bisa cukup. Jika multi-server, pertimbangkan Memcached atau Redis agar cache konsisten antar node.

Konfigurasi Cache Driver di CodeIgniter 3

Autoload atau Load Manual

$autoload['drivers'] = array('cache');

Atau load manual di controller/library:

$this->load->driver('cache', array('adapter' => 'redis', 'backup' => 'file'));

Parameter backup penting agar aplikasi tetap berjalan saat backend utama gagal.

Contoh Konfigurasi Memcached

Di application/config/memcached.php:

$config = array(
    'default' => array(
        'hostname' => '127.0.0.1',
        'port'     => 11211,
        'weight'   => 1,
    ),
);

Contoh Inisialisasi Driver dengan Fallback

class MY_Controller extends CI_Controller {

    public function __construct()
    {
        parent::__construct();
        $this->load->driver('cache', array(
            'adapter' => 'apc',
            'backup'  => 'file'
        ));
    }
}

Perlu dicatat bahwa dukungan adapter tergantung environment PHP dan ekstensi yang tersedia. Jika adapter utama tidak tersedia, CodeIgniter akan memakai backup adapter yang ditentukan.

Membangun Cache Wrapper agar Konsisten

Salah satu kesalahan umum adalah menyebar logika cache ke banyak controller dan model. Solusi yang lebih rapi adalah membuat library wrapper untuk standarisasi key naming, TTL, namespace, fallback, dan logging.

Contoh Library Cache Wrapper

class Cache_service {

    protected $CI;
    protected $prefix = 'myapp:v1:';

    public function __construct()
    {
        $this->CI =& get_instance();

        $this->CI->load->driver('cache', array('adapter' => 'redis', 'backup' => 'file'));
    }

    public function key($type, array $parts = array())
    {
        $safeParts = array_map(function($p) {
            return preg_replace('/[^a-zA-Z0-9_\-:.]/', '_', (string) $p);
        }, $parts);

        return $this->prefix . $type . ':' . implode(':', $safeParts);
    }

    public function remember($key, $ttl, callable $callback)
    {
        $value = $this->CI->cache->get($key);

        if ($value !== FALSE) {
            return $value;
        }

        $value = $callback();
        $this->CI->cache->save($key, $value, $ttl);

        return $value;
    }

    public function delete($key)
    {
        return $this->CI->cache->delete($key);
    }
}

Wrapper ini berguna karena semua cache key dibangun dengan pola yang sama dan mudah diubah ketika terjadi perubahan skema key.

Strategi Cache Key Naming yang Aman dari Collision

Cache key yang buruk sering menjadi sumber bug yang sulit dilacak. Gunakan struktur key yang eksplisit dan konsisten.

Format yang Disarankan

app:v1:{domain}:{entity}:{identifier}:{variant}

Contoh:

  • myapp:v1:product:detail:123:id_ID
  • myapp:v1:user:menu:45:admin
  • myapp:v1:report:sales:2026-03:branch_10

Komponen Penting

  • Prefix aplikasi: mencegah tabrakan dengan aplikasi lain.
  • Versi skema key: memudahkan invalidasi massal secara logis saat format berubah.
  • Domain/entity: misalnya product, article, report.
  • Identifier: id, slug, tenant id, locale, role.
  • Variant: bentuk response berbeda seperti mobile/desktop, currency, language.

Jangan gunakan key yang hanya berdasarkan URL mentah tanpa normalisasi. Query parameter yang urutannya berbeda bisa menghasilkan cache terpisah untuk data yang sama.

Output Cache di CodeIgniter 3

Output cache cocok untuk halaman publik yang identik untuk banyak user. Di CodeIgniter 3, Anda bisa memakai:

$this->output->cache(10); // 10 menit

Biasanya dipasang di controller method:

public function category($slug)
{
    $this->output->cache(5);
    $data['category'] = $this->category_model->find_by_slug($slug);
    $data['products'] = $this->product_model->list_by_category($slug);

    $this->load->view('catalog/category', $data);
}

Kapan Tidak Cocok

  • Halaman berbeda per user.
  • Halaman berisi CSRF token, notifikasi session, atau data personal.
  • Response memiliki fragmen dinamis yang berubah sangat cepat.

Jika halaman hampir sama tetapi ada sebagian kecil yang personal, lebih baik cache data non-personal di object cache, lalu render fragmen personal secara terpisah.

Query Cache Manual

Untuk query berat, lebih aman melakukan cache manual terhadap hasil query daripada mengandalkan cache SQL engine lama atau mekanisme yang sulit dikendalikan. Simpan hasil query sebagai array terstruktur.

public function top_selling_products($month)
{
    $key = $this->cache_service->key('report', array('top_selling', $month));

    return $this->cache_service->remember($key, 900, function() use ($month) {
        return $this->db
            ->select('product_id, SUM(qty) AS total_qty')
            ->from('order_items')
            ->where('order_month', $month)
            ->group_by('product_id')
            ->order_by('total_qty', 'DESC')
            ->limit(20)
            ->get()
            ->result_array();
    });
}

Pola ini efektif karena TTL dan invalidasi bisa Anda kontrol penuh dari aplikasi.

Object Cache untuk Entitas dan Referensi

Object cache sangat cocok untuk data yang sering dipakai ulang: profil produk, daftar kategori, setting aplikasi, atau hasil transformasi data.

public function get_product($id)
{
    $key = $this->cache_service->key('product', array('detail', $id));

    return $this->cache_service->remember($key, 600, function() use ($id) {
        return $this->db->get_where('products', array('id' => $id))->row_array();
    });
}

Rekomendasi TTL per Jenis Data

  • Konfigurasi aplikasi: 1-24 jam, tergantung frekuensi perubahan.
  • Kategori/tag/reference data: 30 menit sampai beberapa jam.
  • Detail produk/artikel: 5-30 menit.
  • Dashboard agregat: 1-15 menit.
  • Session-aware data: 1-10 menit atau lebih pendek jika sensitif.

TTL tidak boleh dipilih asal. Semakin besar risiko stale data, semakin pendek TTL atau semakin ketat invalidasinya.

Session-Aware Cache

Session-aware cache digunakan ketika response dipengaruhi oleh konteks pengguna. Kesalahan terbesar di sini adalah memakai key generik untuk data yang sebenarnya spesifik per user atau role.

public function get_user_menu($userId, $role)
{
    $key = $this->cache_service->key('user_menu', array($userId, $role));

    return $this->cache_service->remember($key, 300, function() use ($role) {
        return $this->menu_model->by_role($role);
    });
}

Jika menu sebenarnya hanya bergantung pada role, jangan tambahkan userId karena akan menurunkan hit ratio. Sebaliknya, jika ada badge jumlah notifikasi per user, maka key wajib memasukkan userId.

Masukkan ke cache key semua variabel yang benar-benar memengaruhi hasil. Jangan lebih, jangan kurang.

Invalidasi Cache Berbasis Event

TTL saja tidak cukup. Ketika admin mengubah produk, cache detail produk dan daftar produk terkait harus dibersihkan segera. Ini disebut event-based invalidation.

Contoh Alur Invalidasi

  1. Produk diupdate di model/service.
  2. Event atau hook internal dipanggil.
  3. Key terkait dihapus: detail produk, kategori produk, rekomendasi, dan mungkin halaman publik yang relevan.
public function update_product($id, $payload)
{
    $this->db->where('id', $id)->update('products', $payload);

    $this->cache_service->delete(
        $this->cache_service->key('product', array('detail', $id))
    );

    // Hapus cache daftar atau indeks terkait bila perlu
    // Misalnya berdasarkan category_id lama/baru
}

Untuk invalidasi massal, gunakan versi namespace pada key, misalnya myapp:v2:, ketika Anda ingin “mengganti generasi” cache tanpa menghapus semua key satu per satu.

Fallback Saat Backend Cache Down

Cache adalah akselerator, bukan sumber kebenaran utama. Jika Redis atau Memcached down, aplikasi harus tetap berfungsi walau lebih lambat. Karena itu:

  • Gunakan backup adapter seperti file cache.
  • Pastikan kode tetap bisa mengambil data dari database saat cache miss atau cache error.
  • Jangan membuat alur bisnis yang bergantung absolut pada cache untuk kebenaran data.
  • Tambahkan logging untuk mendeteksi lonjakan miss atau kegagalan backend cache.

Pada sistem yang lebih sensitif, Anda juga bisa menambahkan circuit breaker sederhana: jika backend cache gagal berkali-kali, hentikan percobaan ke cache sementara waktu dan langsung fallback ke database agar request tidak habis di timeout cache.

Anti-Pattern yang Sering Menyebabkan Stale Data

  • TTL terlalu panjang tanpa invalidasi event.
  • Key tidak memasukkan konteks seperti locale, role, tenant, atau currency.
  • Mencache seluruh halaman personal menggunakan output cache global.
  • Menyimpan object yang tidak stabil atau sulit diserialisasi dengan aman.
  • Tidak menghapus cache turunan setelah data induk berubah.
  • Cache stampede: banyak request miss bersamaan lalu semuanya menembak database.

Untuk mengurangi stampede, Anda bisa menerapkan TTL acak kecil, lock per key, atau pola stale-while-revalidate secara manual.

Profiling Sebelum dan Sesudah

Jangan menganggap caching berhasil hanya karena kode sudah ditulis. Ukur dampaknya. CodeIgniter 3 menyediakan profiler yang bisa membantu melihat query dan waktu eksekusi.

$this->output->enable_profiler(TRUE);

Sebelum cache diterapkan, amati:

  • Jumlah query per request.
  • Query mana yang paling lambat.
  • Total waktu render controller/view.
  • Pola request yang paling sering diakses.

Sesudah cache diterapkan, periksa:

  • Apakah query berat berkurang saat cache hit.
  • Apakah response time lebih konsisten.
  • Apakah cache miss melonjak karena key terlalu spesifik.
  • Apakah stale data muncul setelah update admin.

Jika memungkinkan, tambahkan logging sederhana di wrapper cache untuk memantau hit/miss ratio per domain data.

Checklist Best Practice untuk Production

  • Gunakan cache wrapper agar TTL, key format, dan logging konsisten.
  • Tentukan TTL berdasarkan karakter data, bukan satu angka untuk semua.
  • Masukkan semua dimensi penting ke cache key: locale, tenant, role, currency, filter utama.
  • Pisahkan data publik dan personal; jangan pakai output cache global untuk halaman sesi.
  • Siapkan fallback backend dan pastikan aplikasi tetap benar saat cache gagal.
  • Implementasikan invalidasi berbasis event pada operasi create/update/delete.
  • Gunakan prefix aplikasi dan versi namespace untuk mencegah collision.
  • Monitor hit/miss ratio, latency backend cache, dan error timeout.
  • Uji skenario stale data setelah update melalui panel admin atau job sinkronisasi.
  • Dokumentasikan peta key cache penting agar tim mudah melakukan debugging.

Penutup

Implementasi caching multi-layer di CodeIgniter 3 akan jauh lebih efektif jika dirancang sebagai bagian dari arsitektur aplikasi, bukan tambalan sesaat. Output cache mempercepat halaman publik, query cache manual menekan query berat, object cache menyimpan data reusable, dan session-aware cache menjaga personalisasi tetap aman.

Pilih backend berdasarkan kebutuhan operasional: file untuk fallback atau sistem sederhana, APCu untuk cache lokal super cepat, Memcached untuk cache distributed yang ringan, dan Redis untuk fleksibilitas serta pola invalidasi yang lebih canggih. Yang paling penting, selalu pikirkan tiga hal bersamaan: key, TTL, dan invalidasi. Sebagian besar bug caching muncul ketika salah satu dari tiga hal itu diabaikan.