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**.