UNDER PRESSURE

Level 2 · Database adalah Leher Botol

Sesi 21: Memisahkan Jalur Baca dan Jalur Tulis: CQRS & Materialized Views

Bagaimana jika bentuk struktur data yang paling sempurna untuk mencatat uang masuk ternyata bentuk yang paling buruk dan lambat untuk menampilkan riwayat mutasi di aplikasi pengguna?

Sesi 21 / 343 menit baca

1. Bayangkan Jika

Sebuah kantor akuntan menyimpan jutaan transaksi keuangan dalam bentuk kuitansi baris tunggal di buku kas fisik: - Kuitansi #1: Budi setor Rp 50.000 - Kuitansi #2: Ani tarik Rp 20.000 - Kuitansi #3: Budi bayar kopi Rp 10.000

Mencatat kuitansi baru hanya butuh 1 detik (sangat cepat). Namun ketika seorang bos bertanya "Berapa total kekayaan Budi, daftar 5 belanjaan terakhirnya, dan ringkasan pengeluaran bulanan?", akuntan harus membaca 10 juta lembar kuitansi dari halaman pertama, menghitung kalkulator selama 30 menit, dan kantor macet.


2. Apa yang Sebenarnya Terjadi

Inilah dilema fundamental basis data relasional (OLTP vs OLAP / Read vs Write Optimization).

Untuk menyelesaikan dilema ini, arsitek menerapkan pola CQRS (Command Query Responsibility Segregation):

  1. Pemisahan Peran Total: - Command (Write Model): Khusus menerima perintah aksi bisnis (CreateOrder, TransferMoney). Disimpan dalam skema yang dinormalisasi ketat (3NF / Third Normal Form) untuk menjamin integritas referensial dan mencegah anomali data. - Query (Read Model): Khusus melayani tampilan antarmuka pengguna (GetOrderHistory, SearchProductCatalog). Disimpan dalam skema Denormalized Flat Document (misal: JSON di MongoDB, ElasticSearch untuk full-text search, atau Materialized Read Table di PostgreSQL) yang siap saji tanpa perlu join 10 tabel!

  2. Asynchronous Projection (Event-Driven Sync): - Setiap kali Write Model berhasil menyimpan transaksi (OrderPlaced), sebuah event dipublikasikan ke message bus. - Pekerja latar belakang (Projector / Worker) mengambil event tersebut, membentuk dokumen agregat siap saji, lalu menyimpannya ke Read Model. - Query dashboard sekarang hanya membaca 1 dokumen datar dalam tempo 1 milidetik tanpa melakukan JOIN sama sekali!

  3. Materialized Views: - Bentuk CQRS paling sederhana di dalam satu database engine: tabel virtual yang menyimpan hasil pre-computed query berat dan diperbarui secara berkala (REFRESH MATERIALIZED VIEW CONCURRENTLY).


3. Harga Mahal yang Harus Dibayar

Mengapa CQRS tidak boleh dipakai sembarangan? - Eventual Consistency: Ada jeda waktu (projection lag) antara saat transaksi ditulis di Write Model hingga muncul di Read Model. Pengguna mungkin baru melihat invoice pesanannya 500 ms setelah menekan tombol bayar. - Kompleksitas Operasional Ganda: Tim harus memelihara dua database berbeda, logika sinkronisasi event projector, skenario replay data saat projector crash, dan penanganan bug duplikasi event.


4. Nama Resminya

  • CQRS: Command Query Responsibility Segregation.
  • Command: Mutasi state yang tidak mengembalikan data bisnis (hanya status sukses/gagal).
  • Query: Pengambilan data murni tanpa efek samping (side-effect free).
  • Projection: Proses transformasi event menjadi representasi Read Model siap baca.
  • Event Sourcing: Menyimpan histori perubahan sebagai urutan event tak terhapuskan (append-only event log).

5. Di Dunia Kita

  • E-Commerce Product Search: Produk diedit di MySQL (Write Model), tapi kolom pencarian, filter kategori, dan harga di halaman depan diambil dari ElasticSearch / OpenSearch (Read Model).
  • Social Media Timeline: Postingan disimpan di relational DB, tapi feed timeline beranda di-generate dan disimpan di Redis Sorted Set siap baca.

6. Lensa Performance Tester

Metrik dan pengujian krusial: 1. Projection Lag Under Load: Berapa milidetik waktu yang dibutuhkan projector untuk mengupdate Read Model saat beban 3.000 Write QPS? 2. Query Latency Improvement: Bandingkan latensi query JOIN 8 TABLES (tanpa CQRS: 450 ms) vs DIRECT READ MODEL FETCH (dengan CQRS: 3 ms). 3. Projector Recovery Time: Matikan worker projector selama 5 menit saat jam sibuk, lalu nyalakan kembali. Berapa menit backlog antrean event tuntas diproses?


7. Pertanyaan untuk Ronde Berikutnya

Level 2 (Database) telah selesai! Kita sudah menaklukkan connection pool, read replica, cache, sharding, dan CQRS.

Sekarang kita melangkah masuk ke LEVEL 3: SISTEM YANG SALING BERGANTUNG. Ketika aplikasi dipecah menjadi ratusan microservices independen, apa yang terjadi ketika satu request pengguna diam-diam memicu 30 panggilan jaringan internal yang saling menunggu?

Jawabannya ada di Sesi 22: Ketika Layanan Dipecah (Monolith to Microservices & API Gateway).