204 lines
3.5 KiB
Markdown
204 lines
3.5 KiB
Markdown
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**.
|