Belajar Ruby on Rails - Caching & Performance
Episode 12 of 27

Belajar Ruby on Rails - Caching & Performance

Membedah caching dan performa Rails: fragment & page caching, Russian Doll caching, low-level cache, cache stores (Redis & Solid Cache), dan optimasi query dengan includes/joins untuk membasmi N+1 queries

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 11 kalian mempercepat response dengan background job, episode 12 membahas cara lain yang sering jauh lebih berdampak: caching dan optimasi query. Sebelum menambah server, Rails menawarkan berlapis-lapis cache yang bisa membuat halaman loading dari ratusan milidetik menjadi belasan.

Mengapa episode ini penting? Karena performa bukan fitur tambahan — ia pengalaman pengguna dan biaya infrastruktur. Halaman yang lambat berarti user pergi dan biaya server naik. Episode ini mengajarkan hierarki cache Rails, dari yang paling kasar (full page) sampai paling halus (nilai tunggal), plus teknik query yang menyembuhkan penyebab lambat paling umum: N+1 queries.

Hierarki Cache Rails

100%

Semakin ke bawah, semakin halus granularitasnya dan semakin banyak kontrol yang kalian miliki. Yang paling sering memberi ROI terbesar: fragment cache + query optimization.

Fragment Caching

Fragment cache menyimpan potongan HTML yang mahal untuk dirender. Halaman post yang menampilkan komentar adalah kandidat sempurna:

HTMLFragment cache komentar
<% cache @post do %>
  <h1><%= @post.title %></h1>
  <p><%= @post.body %></p>
 
  <section id="comments">
    <%= render @post.comments %>
  </section>
<% end %>

cache @post menyimpan HTML dengan key berdasarkan @post — saat post diubah, key berubah dan cache dianggap stale. Rails memakai cache_key model (posts/1-20260816120000) sebagai basis.

Russian Doll Caching

Nested cache — cache luar memakai key yang berisi key cache dalam — disebut Russian Doll:

HTMLRussian Doll nested cache
<% cache [@post, "index"] do %>
  <%= render @post.comments %>
<% end %>
HTMLPartial comment dengan cache sendiri
<% cache comment do %>
  <p><%= comment.body %></p>
  <small>oleh <%= comment.user.name %></small>
<% end %>

Saat satu komentar berubah, hanya fragment komentar itu yang invalid — fragment post luar tetap dipakai karena cache_key-nya tidak berubah. Ini kunci efisiensi Russian Doll.

Tip

Cek apakah fragment cache benar-benar terpakai dengan bin/rails dev:cache (memaksa cache aktif di development) lalu lihat log: baris Read fragment vs Write fragment menunjukkan hit rate.

Low-Level Cache

Untuk nilai tunggal — bukan HTML — gunakan Rails.cache langsung:

RubyLow-level cache
Rails.cache.fetch("user_#{user.id}_post_count", expires_in: 10.minutes) do
  user.posts.count
end

fetch memakai cache jika ada, menghitung jika tidak, dan menyimpannya. Contoh kasus: hitungan, kalkulasi mahal, atau hasil aggregasi yang jarang berubah.

Cache Stores

Rails 8 memakai Solid Cache secara default — store berbasis database, tanpa Redis:

Rubyconfig/environments/production.rb
config.cache_store = :solid_cache_store

Alternatif: Redis (gem redis) untuk cache yang sangat cepat dan shared antar instance:

RubyRedis cache store
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/1" }
StoreKelebihanKekurangan
Solid Cache (DB)Tanpa infra tambahan, transactionalBergantung kinerja DB
RedisSangat cepat, TTL fleksibelButuh infra Redis
MemoryStoreTercepat, untuk devTidak shared antar proses

Di production multi-instance, cache harus shared (Redis/Solid Cache di database yang sama) — MemoryStore di tiap instance membuat fragment cache tidak konsisten.

HTTP Caching

Sebelum render, Rails bisa menghemat dengan HTTP cache headers:

RubyHTTP caching di controller
def show
  @post = Post.find(params[:id])
  fresh_when @post
end

fresh_when membandingkan If-None-Match/If-Modified-Since — jika data belum berubah, Rails mengirim 304 Not Modified tanpa merender view sama sekali. Ini cache paling efisien karena tidak menyentuh server render.

Membasmi N+1 Queries

N+1 adalah penyebab lambat paling umum: untuk 100 post, aplikasi menjalankan 1 query untuk posts + 100 query untuk user masing-masing. Gejalanya: SELECT posts lalu puluhan SELECT users WHERE id = ... di log.

HTMLPemicu N+1
<% @posts.each do |post| %>
  <p><%= post.user.name %></p>  <!-- query user tiap iterasi! -->
<% end %>

Fix dengan includes

Rubyincludes untuk eager loading
@posts = Post.includes(:user).limit(50)

includes memuat semua user dalam satu query tambahan (WHERE id IN (...)). Total: 2 query untuk 50 post — bukan 51.

joins untuk Filtering

joins dipakai saat kalian memfilter berdasarkan relasi, bukan memuat data:

Rubyjoins untuk filter
Post.joins(:user).where(users: { admin: true })

Perbedaannya: includes = eager loading (hindari N+1), joins = INNER JOIN untuk penyaringan. Untuk filter + eager load sekaligus, gabungkan keduanya.

Bulk counter dengan group

RubyHitung komentar per post
Post.joins(:comments).group(:id).count

Satu query untuk semua post, bukan loop post.comments.count. Pattern ini penting untuk dashboard/reporting (diperdalam di episode 21).

Mengukur Sebelum Mengoptimasi

Jangan menebak. Ukur dulu:

Identifikasi query lambat
bin/rails runner '
  ActiveRecord::Base.logger = Logger.new(STDOUT)
  Post.includes(:user).limit(50).to_a
'

Dan aktifkan deteksi N+1 dengan gem bullet di development — ia menandai query N+1, penggunaan includes yang tidak perlu, dan counter cache yang belum dipakai. Optimasi tanpa data hanyalah tebakan.

Warning

Cache menyimpan hasil render — pastikan data yang di-cache tidak sensitif (misal konten milik user tertentu yang bisa dilihat user lain). Fragment cache yang salah scope bisa menjadi celah kebocoran data; selalu sertakan identitas pengguna pada key bila kontennya personal.

Common Pitfalls

  • Memakai each lalu memanggil relasi di dalamnya — N+1 klasik; pakai includes.
  • Counter query di viewpost.comments.count di loop; gunakan group atau counter cache.
  • Fragment cache tanpa memikirkan pengguna — konten personal tercampur antar user.
  • Cache store MemoryStore di produksi multi-instance — cache tidak konsisten antar server.
  • Mengoptimasi tanpa mengukur — install Bullet dan lihat log; ukur sebelum dan sesudah.

Penutup

Episode 12 membekali kalian caching Rails: hierarki dari HTTP caching (304) hingga low-level cache, fragment cache dan Russian Doll untuk HTML mahal, cache stores (Solid Cache database-driven vs Redis), serta optimasi query dengan includes/joins/group untuk membasmi N+1.

Inti yang harus dibawa pulang:

  • Fragment cache + Russian Doll memberi ROI terbesar untuk halaman dinamis.
  • Low-level cache (Rails.cache.fetch) untuk nilai mahal yang jarang berubah.
  • Solid Cache default Rails 8 (database); Redis untuk kecepatan maksimal.
  • includes untuk eager loading, joins untuk filter, group untuk agregasi.
  • Ukur dulu dengan Bullet/log; optimasi tanpa data hanyalah tebakan.

Di episode 13 selanjutnya kita akan membedah Active Storage & file upload — attachments, image variants, storage services (lokal & S3), dan direct upload. Sampai jumpa di episode 13!

Belajar Ruby on Rails - Caching & Performance | Belajar Ruby on Rails