Ya, **PostgreSQL memiliki fitur *Materialized View***. ### Apa itu Materialized View di PostgreSQL? **Materialized View** adalah objek database yang **menyimpan hasil query secara fisik** (seperti tabel), bukan menjalankan ulang query setiap kali dipanggil seperti *VIEW* biasa. ### Perbedaan VIEW vs MATERIALIZED VIEW | Aspek | VIEW | MATERIALIZED VIEW | | ---------------- | -------------------------- | ----------------- | | Penyimpanan data | Tidak disimpan | Disimpan fisik | | Kecepatan query | Lebih lambat (query ulang) | Lebih cepat | | Update otomatis | Selalu real-time | ❌ Tidak otomatis | | Perlu refresh | ❌ | ✅ Ya | --- ### Contoh penggunaan ```sql CREATE MATERIALIZED VIEW laporan_penjualan AS SELECT tanggal, SUM(total) AS total_penjualan FROM penjualan GROUP BY tanggal; ``` Mengambil data: ```sql SELECT * FROM laporan_penjualan; ``` --- ### Refresh data (WAJIB agar update) ```sql REFRESH MATERIALIZED VIEW laporan_penjualan; ``` Tanpa mengunci (PostgreSQL ≥ 9.4, wajib ada index UNIQUE): ```sql REFRESH MATERIALIZED VIEW CONCURRENTLY laporan_penjualan; ``` --- ### Kapan sebaiknya pakai Materialized View? ✅ Query berat (JOIN besar, agregasi) ✅ Data laporan / dashboard ✅ Data tidak harus real-time ❌ Data yang sering berubah dan harus langsung update --- ### Kekurangan * Data bisa **tidak up-to-date** * Perlu mekanisme **refresh manual / cron** * Memakai **storage tambahan** --- Kalau mau, saya bisa jelaskan: * 🔁 cara auto-refresh pakai **cron / pg_cron** * ⚡ strategi performa (index materialized view) * 🆚 kapan pilih materialized view vs table biasa Tinggal bilang mau fokus ke yang mana. Berikut **strategi performa Materialized View di PostgreSQL (fokus INDEX & refresh)** yang sudah terbukti efektif untuk kasus laporan & query berat. --- ## 1️⃣ **WAJIB: Buat INDEX di Materialized View** Materialized view **TIDAK otomatis punya index**. ### Contoh ```sql CREATE MATERIALIZED VIEW mv_laporan_penjualan AS SELECT tanggal, cabang_id, SUM(total) AS total_penjualan FROM penjualan GROUP BY tanggal, cabang_id; ``` ### Index untuk filter & join ```sql CREATE INDEX idx_mv_penjualan_tanggal ON mv_laporan_penjualan (tanggal); CREATE INDEX idx_mv_penjualan_cabang ON mv_laporan_penjualan (cabang_id); ``` 📌 **Rule utama** * `WHERE` → index kolom filter * `JOIN` → index kolom join * `ORDER BY` → index kolom sorting --- ## 2️⃣ **UNIQUE INDEX untuk REFRESH CONCURRENTLY** Tanpa ini, `CONCURRENTLY` **akan error**. ```sql CREATE UNIQUE INDEX ux_mv_penjualan ON mv_laporan_penjualan (tanggal, cabang_id); ``` Lalu refresh: ```sql REFRESH MATERIALIZED VIEW CONCURRENTLY mv_laporan_penjualan; ``` 🎯 **Benefit** * Query tetap bisa baca data * Tidak lock materialized view --- ## 3️⃣ **Gunakan Composite Index (bukan banyak index kecil)** ❌ Buruk: ```sql (tanggal) (cabang_id) ``` ✅ Lebih optimal: ```sql CREATE INDEX idx_mv_penjualan_tanggal_cabang ON mv_laporan_penjualan (tanggal, cabang_id); ``` Urutan kolom **HARUS mengikuti pola query**: ```sql WHERE tanggal = ? AND cabang_id = ? ``` --- ## 4️⃣ **Partial Index untuk Data Populer** Kalau query sering ambil data terbaru: ```sql CREATE INDEX idx_mv_penjualan_30hari ON mv_laporan_penjualan (tanggal) WHERE tanggal >= CURRENT_DATE - INTERVAL '30 days'; ``` ⚡ Sangat cepat untuk dashboard “bulan ini”. --- ## 5️⃣ **Covering Index (INCLUDE)** Agar **Index Only Scan** terjadi: ```sql CREATE INDEX idx_mv_cover ON mv_laporan_penjualan (tanggal, cabang_id) INCLUDE (total_penjualan); ``` 📈 Mengurangi akses ke heap → performa naik. --- ## 6️⃣ **VACUUM & ANALYZE setelah REFRESH** Materialized view **tidak auto-analyze**. ```sql ANALYZE mv_laporan_penjualan; ``` Untuk rutin: ```sql VACUUM ANALYZE mv_laporan_penjualan; ``` 📌 Sangat penting untuk planner memilih index yang benar. --- ## 7️⃣ **Pisahkan MV untuk Use Case Berbeda** ❌ Satu MV dipakai semua kebutuhan ✅ Beberapa MV kecil & spesifik Contoh: ```sql mv_penjualan_harian mv_penjualan_bulanan mv_penjualan_per_cabang ``` Lebih cepat refresh & index lebih fokus. --- ## 8️⃣ **Refresh Incremental (Strategi Lanjutan)** PostgreSQL **tidak native incremental refresh**. Solusi umum: * MV hanya data lama * Data hari ini query langsung tabel ```sql SELECT * FROM mv_penjualan_lama UNION ALL SELECT ... FROM penjualan WHERE tanggal = CURRENT_DATE; ``` ⚡ Refresh MV cukup seminggu sekali. --- ## 9️⃣ **Gunakan EXPLAIN ANALYZE** Pastikan index benar-benar dipakai. ```sql EXPLAIN ANALYZE SELECT * FROM mv_laporan_penjualan WHERE tanggal = '2025-12-01'; ``` Cari: * `Index Scan` * `Index Only Scan` --- ## 🔥 Checklist Cepat (Best Practice) ✅ Selalu buat index ✅ Buat **UNIQUE index** jika pakai CONCURRENTLY ✅ Composite index > banyak index kecil ✅ Partial index untuk data populer ✅ ANALYZE setelah refresh ✅ Jangan jadikan MV “tabel serba guna” --- Kalau mau, saya bisa: * ⚙️ Buatkan **template index berdasarkan query kamu** * 📊 Analisis `EXPLAIN ANALYZE` hasilmu * ⏱️ Rekomendasi jadwal refresh (harian/jam) Kirim saja **struktur MV + contoh query yang sering dipakai**.