Arsitektur: Merancang Arsitektur Read-Heavy API untuk Product Page Skala Tinggi
8 min read

Arsitektur: Merancang Arsitektur Read-Heavy API untuk Product Page Skala Tinggi

Pada banyak aplikasi modern—terutama e‑commerce, katalog, atau content platform—product detail page adalah endpoint dengan traffic read paling tinggi. Masalah klasiknya hampir selalu sama:

  • Read QPS sangat besar
  • Database resource terbatas
  • Banyak side-effect ikut terjadi (view count, last viewed, analytics)
  • Data yang ditampilkan bukan satu jenis, tapi gabungan banyak domain

Jika semua ini diproses secara sinkron dan langsung ke database, sistem akan cepat mencapai bottleneck.

Artikel ini membahas pendekatan arsitektur yang pragmatis, scalable, dan production-proven untuk menangani read-heavy API, khususnya product page.


Gambaran Arsitektur yang Direkomendasikan

Bagan berikut memperlihatkan pendekatan yang disarankan: read path yang ringan, ter-cache, dan side-effect yang dipisahkan secara async.

Arsitektur: Merancang Arsitektur Read-Heavy API untuk Product Page Skala Tinggi


Prinsip Utama: Pisahkan Read Murni dan Side Effect

Read API Harus Deterministik dan Murah

Read API yang sehat memiliki karakteristik:

  • Deterministik (request yang sama → response yang sama)
  • Tidak memicu transaksi write
  • Tidak bergantung pada proses eksternal
  • Aman untuk di-cache agresif

Begitu read API mulai:

  • menulis ke database
  • mengupdate counter sinkron
  • memanggil service lain untuk analytics

maka read path tersebut kehilangan sifat skalabilitasnya.

Product page idealnya adalah pure read operation. Artinya:

  • Tidak mengubah state utama
  • Tidak menunggu proses lain selesai
  • Tidak memicu transaksi berat

Contoh side-effect yang harus dikeluarkan dari jalur utama:

  • Save view
  • Update last viewed
  • Analytics event
  • Recommendation signal

Pendekatan yang sehat adalah fire-and-forget event ke message broker atau queue.

Hasilnya:

  • Latency rendah
  • Throughput tinggi
  • Database tidak tersedak

Cache sebagai Senjata Utama

Cache Bukan Optimasi, Tapi Desain

Pada sistem dengan read operation sangat tinggi, cache bukan lagi lapisan tambahan, melainkan bagian dari desain arsitektur itu sendiri. Tanpa cache, database—sebaik apa pun dioptimalkan—akan tetap menjadi bottleneck.

Hal penting yang sering terlewat adalah: cache harus dirancang sejak awal, bukan ditempel belakangan. Ini mencakup:

  • Penentuan cache key yang stabil
  • TTL yang sesuai karakteristik data
  • Strategi invalidasi yang jelas
  • Observability terhadap cache hit/miss

Cache yang buruk bisa lebih berbahaya daripada tidak ada cache sama sekali.

Pada sistem read-heavy, cache bukan optimasi tambahan—cache adalah fondasi.

Layer Cache yang Disarankan

  1. CDN / Edge Cache Cocok untuk response yang relatif stabil (HTML / JSON product detail).

  2. Application Cache (Redis / Memcached) Menyimpan hasil query per domain data.

  3. Database Digunakan hanya saat cache miss.

Target realistis:

80–95% traffic berhenti di cache


Kenapa Product Page Sebaiknya Tidak Satu Endpoint

Masalah TTL Terendah Menentukan Segalanya

Jika satu endpoint menggabungkan semua data product page, maka TTL cache endpoint tersebut harus mengikuti data yang paling cepat berubah.

Akibatnya:

  • Product detail yang jarang berubah ikut sering di-refresh
  • Cache churn tinggi
  • DB load meningkat tanpa perlu

Ini adalah hidden cost yang sering baru terasa saat traffic sudah tinggi.

Satu product page biasanya terdiri dari beberapa domain data:

KomponenKarakteristikPerubahanTTL Ideal
Product detailStabilJarangPanjang
Price / stockSensitifSeringPendek
Latest reviewAppend-onlySedangPendek–menengah
RecommendationDinamisSangat seringSangat pendek

Jika semua dipaksa dalam satu endpoint:

  • TTL ditarik ke yang paling sensitif
  • Cache menjadi tidak efektif
  • Setiap perubahan kecil memicu invalidasi besar

Pendekatan yang Lebih Sehat: Composable Read APIs

Domain-Oriented Read Model

Composable Read API pada dasarnya adalah penerapan ringan dari CQRS (Command Query Responsibility Segregation) di sisi read.

Setiap endpoint:

  • Mewakili satu domain data
  • Bisa memiliki schema dan storage berbeda
  • Tidak harus 100% konsisten dengan write model

Pendekatan ini memberi kebebasan untuk:

  • Denormalisasi agresif
  • Materialized view
  • Bahkan storage berbeda (Redis-only, read DB, atau search index)

Decision Table: Kapan Perlu Split Endpoint?

Tabel berikut dapat digunakan sebagai panduan praktis untuk memutuskan apakah sebuah product page sebaiknya dipecah menjadi beberapa endpoint atau tetap satu endpoint.

Pertanyaan TeknisJika YAJika TIDAK
Apakah data di halaman memiliki TTL ideal yang berbeda jauh?Split endpointMasih aman satu endpoint
Apakah sebagian data berubah jauh lebih sering dari yang lain?Split endpointSatu endpoint cukup
Apakah sebagian data bisa di-cache sangat lama, sementara yang lain tidak?Split endpointSatu endpoint masih masuk akal
Apakah kegagalan satu bagian data tidak boleh merusak seluruh halaman?Split endpointSatu endpoint bisa dipertimbangkan
Apakah traffic read sudah tinggi dan cache churn mulai terasa?Split endpointTunda split (belum perlu)
Apakah frontend/client siap menangani banyak request?Gunakan BFF + split endpointPertahankan satu endpoint
Apakah domain data sudah jelas dan stabil?Split endpointJangan split dulu

Rule praktis:

Jika lebih dari dua pertanyaan di atas jawabannya YA, maka split endpoint biasanya adalah keputusan yang tepat.

Domain-Oriented Read Model

Composable Read API pada dasarnya adalah penerapan ringan dari CQRS (Command Query Responsibility Segregation) di sisi read.

Setiap endpoint:

  • Mewakili satu domain data
  • Bisa memiliki schema dan storage berbeda
  • Tidak harus 100% konsisten dengan write model

Pendekatan ini memberi kebebasan untuk:

  • Denormalisasi agresif
  • Materialized view
  • Bahkan storage berbeda (Redis-only, read DB, atau search index)

Alih-alih satu endpoint besar, gunakan beberapa endpoint kecil berdasarkan domain:

GET /product/{id}/detail
GET /product/{id}/price
GET /product/{id}/reviews/latest
GET /product/{id}/recommendations

Setiap endpoint:

  • Memiliki cache sendiri
  • TTL sesuai karakteristik data
  • Mudah diskalakan dan diobservasi

Jangan Lempar Kompleksitas ke Client: Gunakan BFF

BFF Sebagai Boundary Kontrak

BFF bukan sekadar aggregator, tapi boundary kontrak antara frontend dan kompleksitas backend.

Tanggung jawab BFF mencakup:

  • Parallel fetching
  • Timeout isolation (satu domain gagal tidak merusak seluruh page)
  • Partial response
  • Default/fallback value
  • Versioning response tanpa memecah client

Dengan BFF, perubahan internal backend tidak bocor ke frontend.

Jika frontend harus memanggil banyak endpoint sendiri:

  • Latency meningkat
  • Error handling kompleks
  • UX mudah terdampak

Solusinya adalah Backend for Frontend (BFF).

BFF bertugas:

  • Menggabungkan response
  • Fetch secara paralel
  • Menyediakan fallback
  • Menjaga contract tetap sederhana untuk client

Client tetap satu request, backend tetap modular.


Strategi Cache per Endpoint (Contoh Praktis)

Product Detail

  • TTL: 10–30 menit
  • Invalidation: event ProductUpdated
  • Key: product:detail:{id}

Latest Review

  • TTL: 30–120 detik
  • Key: product:reviews:latest:{id}

Recommendation

  • TTL: 5–15 detik atau tanpa cache
  • Key: product:recommendation:{id}:{segment}

Price / Stock

  • TTL: 5–10 detik
  • Atau gunakan stale-while-revalidate

Anti-Pattern yang Sering Terjadi pada Read-Heavy Product Page

Bagian ini penting untuk melengkapi pembahasan sebelumnya. Banyak sistem mengalami masalah skalabilitas bukan karena kurang fitur, tetapi karena terjebak pada pola desain yang kelihatannya sederhana namun berbahaya di skala besar.

Anti-Pattern 1: Satu Endpoint Gemuk untuk Semua Kebutuhan

Contoh:

GET /product/{id}

Endpoint ini:

  • Mengambil detail produk
  • Mengambil harga & stok
  • Mengambil review
  • Mengambil rekomendasi
  • Sekaligus menulis view count

Masalah yang muncul:

  • TTL cache dipaksa mengikuti data paling sensitif
  • Cache churn tinggi
  • Setiap perubahan kecil memicu invalidasi besar
  • Latency tidak stabil

Ini adalah anti-pattern paling umum pada sistem yang awalnya kecil lalu tumbuh cepat.

Anti-Pattern 2: Side Effect Sinkron di Jalur Read

Contoh yang sering dianggap “sepele”:

  • Increment view counter di DB
  • Update last viewed user
  • Insert analytics event langsung

Dampaknya:

  • Read API kehilangan sifat deterministik
  • Latency naik seiring traffic
  • DB write contention

Side-effect seperti ini harus selalu async dan dikeluarkan dari critical path.

Anti-Pattern 3: Mengandalkan Database sebagai Cache

Ciri-cirinya:

  • Query dioptimasi habis-habisan
  • Index ditambah terus
  • Tapi read QPS tetap langsung ke DB

Masalahnya:

  • Database bukan komponen murah untuk read masif
  • Vertical scaling cepat mentok
  • Setiap spike traffic langsung terasa

Jika database mulai “terasa berat”, biasanya masalahnya bukan di query, tapi di absennya cache yang efektif.

Anti-Pattern 4: Terlalu Dini Memakai Elasticsearch untuk Detail Page

Kesalahan umum:

  • DB mulai berat → langsung pindah ke Elasticsearch
  • ES dipakai sebagai primary read store untuk detail by ID

Risikonya:

  • Konsistensi data sulit dijelaskan
  • Operasional cluster bertambah kompleks
  • Debug data mismatch memakan waktu

Elasticsearch seharusnya digunakan karena kebutuhan query, bukan sekadar performa.

Anti-Pattern 5: Membebankan Orkestrasi ke Frontend

Contoh:

  • Frontend harus memanggil 5–6 endpoint
  • Frontend menggabungkan response
  • Frontend meng-handle timeout satu per satu

Dampaknya:

  • UX tidak stabil
  • Logic tersebar di client
  • Sulit melakukan perubahan backend

Masalah ini seharusnya diselesaikan dengan BFF, bukan dengan memperumit client.


Tentang Elasticsearch: Perlu atau Tidak?

Elasticsearch Bukan Shortcut Skala

Elasticsearch sering dianggap solusi cepat saat database mulai berat. Padahal, ia bukan pengganti cache dan bukan solusi read murah untuk primary-key lookup.

Beberapa risiko jika terlalu dini menggunakan Elasticsearch:

  • Operasional lebih kompleks (cluster, shard, reindex)
  • Konsistensi eventual yang sulit dijelaskan ke bisnis
  • Debugging data mismatch

Elasticsearch seharusnya masuk karena kebutuhan query, bukan karena performa semata.

Elasticsearch sangat cocok untuk:

  • Full‑text search
  • Filtering kompleks
  • Listing produk

Namun untuk:

  • GET /product/{id}

Biasanya tidak perlu di tahap awal karena:

  • Eventually consistent
  • Operational cost tinggi
  • Bukan source of truth

Gunakan Elasticsearch jika use case-nya memang search-driven, bukan sekadar primary key lookup.


Urutan Evolusi yang Bijak

Jangan Lompat ke Kompleksitas Tinggi Terlalu Dini

Banyak sistem gagal bukan karena kurang canggih, tapi karena terlalu cepat kompleks.

Urutan evolusi yang sehat:

  1. Pisahkan read dan side-effect
  2. Terapkan cache berlapis dengan TTL tepat
  3. Pastikan read API deterministik dan mudah di-cache
  4. Denormalisasi read model jika perlu
  5. Baru pertimbangkan search index atau storage khusus

Setiap langkah seharusnya dipicu oleh bukti bottleneck, bukan asumsi.

  1. Cache + async side effects
  2. Optimasi query & schema
  3. Read model terpisah (denormalized)
  4. Elasticsearch (jika memang dibutuhkan)

Hindari premature complexity.


Checklist Sebelum Masuk Production

Sebelum menerapkan arsitektur read-heavy seperti yang dibahas di artikel ini ke environment production, gunakan checklist berikut untuk memastikan desain yang dibuat benar-benar dibutuhkan dan siap dijalankan.

Checklist Teknis

  • Apakah read traffic sudah atau diproyeksikan jauh lebih besar dibanding write?
  • Apakah endpoint product page sudah dipastikan bebas side-effect sinkron?
  • Apakah setiap domain data memiliki TTL yang jelas dan masuk akal?
  • Apakah strategi cache invalidation sudah ditentukan (event-based / TTL)?
  • Apakah cache hit ratio dapat dimonitor (metrics & alert)?
  • Apakah fallback behavior saat cache miss atau partial failure sudah didefinisikan?
  • Apakah BFF mampu melakukan parallel fetch dan timeout isolation?

Checklist Operasional

  • Apakah tim siap mengoperasikan Redis / cache layer dengan baik?
  • Apakah message broker / queue untuk side-effect async sudah tersedia?
  • Apakah observability (log, metrics, tracing) mencakup read path?
  • Apakah tim memahami trade-off eventual consistency pada read model?

Jika sebagian besar checklist di atas belum terpenuhi, ada baiknya menunda kompleksitas dan memperbaiki fondasi terlebih dahulu.


Kapan Arsitektur Ini Tidak Perlu (Hindari Over-Engineering)

Tidak semua sistem membutuhkan arsitektur read-heavy yang kompleks. Dalam kondisi tertentu, pendekatan ini justru bisa menjadi beban.

Arsitektur ini belum perlu diterapkan jika:

  • Traffic masih rendah dan stabil
  • Product page jarang diakses bersamaan
  • Data masih sering berubah struktur
  • Tim kecil dan fokus pada validasi produk
  • Bottleneck belum terbukti berasal dari read load

Pada fase awal, solusi yang sering lebih tepat adalah:

  • Satu endpoint sederhana
  • Query yang jelas dan terukur
  • Index database yang memadai
  • Cache minimal atau bahkan tanpa cache

Prinsip pentingnya:

Jangan memecahkan masalah yang belum ada.

Arsitektur yang baik adalah arsitektur yang cukup, bukan yang paling canggih.


Penutup

Arsitektur read-heavy yang baik bukan soal teknologi paling canggih, tapi soal:

  • Memahami karakteristik data
  • Menghormati jalur read sebagai hot path
  • Memisahkan concern dengan jelas
  • Mengutamakan cache dan async processing

Pendekatan composable read API + BFF + cache berlapis telah terbukti mampu menangani traffic besar dengan resource database yang terbatas.

Jika product page adalah jantung aplikasi, maka read API adalah nadinya—buatlah ia ringan, cepat, dan tahan tekanan.