Kembali ke Blog
Database#postgresql#database#performa#SQL#backend

10 Tips PostgreSQL yang Bikin Query Kamu 10x Lebih Cepat

PostgreSQL adalah database paling powerful untuk web developer. Tapi banyak yang tidak pakai fiturnya dengan optimal. Tips ini langsung bisa diterapkan di project kamu sekarang.

Muhamad Putra Aulia Hidayat

Muhamad Putra Aulia Hidayat

27 Maret 20265 menit baca

10 Tips PostgreSQL untuk Performa Optimal

PostgreSQL adalah pilihan database terbaik untuk sebagian besar web application. Tapi banyak developer yang hanya pakai sebatas INSERT, SELECT, UPDATE, DELETE — padahal ada banyak fitur powerful yang bisa dramatisasi performa query.

1. Index yang Tepat

Index adalah hal pertama yang harus dicek kalau query lambat.

-- Cek query yang lambat
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

-- Lihat index yang ada
SELECT tablename, indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public';

-- Buat index untuk kolom yang sering di-WHERE
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);
CREATE INDEX CONCURRENTLY idx_orders_created_at ON orders(created_at DESC);

-- Composite index kalau sering filter 2 kolom sekaligus
CREATE INDEX CONCURRENTLY idx_orders_user_status 
ON orders(user_id, status);

2. EXPLAIN ANALYZE — Teman Terbaik Kamu

-- Selalu pakai ini untuk debug query lambat
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders 
WHERE user_id = 123 AND status = 'pending'
ORDER BY created_at DESC;

-- Yang perlu diperhatikan:
-- Seq Scan = tidak pakai index (biasanya masalah)
-- Index Scan = pakai index (bagus)
-- Hash Join vs Nested Loop (tergantung data size)

3. Partial Index untuk Data Terfilter

-- Kalau sering query order yang pending saja
-- Jangan index semua status, buat partial index
CREATE INDEX idx_pending_orders 
ON orders(created_at) 
WHERE status = 'pending';

-- Index ini jauh lebih kecil dan lebih cepat
-- karena hanya meng-index rows yang relevant

4. Gunakan CTE untuk Query Kompleks

-- Tanpa CTE - susah dibaca dan di-maintain
SELECT u.name, COUNT(o.id), SUM(oi.price)
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed'
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE u.created_at > NOW() - INTERVAL '90 days'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 0;

-- Dengan CTE - bersih dan mudah debug
WITH new_users AS (
  SELECT id, name FROM users
  WHERE created_at > NOW() - INTERVAL '90 days'
),
completed_orders AS (
  SELECT user_id, id FROM orders
  WHERE status = 'completed'
),
order_totals AS (
  SELECT o.user_id, COUNT(o.id) as order_count,
         SUM(oi.price) as total_spent
  FROM completed_orders o
  JOIN order_items oi ON o.id = oi.order_id
  GROUP BY o.user_id
)
SELECT u.name, ot.order_count, ot.total_spent
FROM new_users u
JOIN order_totals ot ON u.id = ot.user_id;

5. JSONB untuk Data Semi-Terstruktur

-- Kolom JSONB dengan index GIN
CREATE TABLE products (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  metadata jsonb DEFAULT '{}'::jsonb
);

CREATE INDEX idx_products_metadata ON products USING GIN(metadata);

-- Insert data
INSERT INTO products (name, metadata) VALUES
('Laptop', '{"brand": "Asus", "specs": {"ram": "16GB", "storage": "512GB"}}');

-- Query dengan JSONB operators
SELECT name, metadata->'brand' as brand
FROM products
WHERE metadata @> '{"specs": {"ram": "16GB"}}';

6. Connection Pooling dengan PgBouncer

Setiap koneksi PostgreSQL butuh ~5MB RAM. Kalau aplikasimu punya 100 concurrent users, itu sudah 500MB hanya untuk connections.

Solusinya: connection pooler seperti PgBouncer atau Supabase Pooler.

# Dengan Supabase, ganti connection string dari:
postgresql://user:pass@db.supabase.co:5432/postgres

# Ke pooler:
postgresql://user:pass@db.supabase.co:6543/postgres?pgbouncer=true

7. Materialized View untuk Laporan Berat

-- Dashboard laporan yang berat, jangan query real-time
CREATE MATERIALIZED VIEW monthly_revenue AS
SELECT 
  DATE_TRUNC('month', created_at) as month,
  COUNT(*) as total_orders,
  SUM(total) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY 1
ORDER BY 1;

-- Refresh setiap jam (pakai pg_cron atau cron job)
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_revenue;

-- Query dashboard sekarang instant
SELECT * FROM monthly_revenue WHERE month > NOW() - INTERVAL '12 months';

8. Proper Data Types

-- JANGAN pakai varchar untuk semua hal
-- PAKAI tipe yang tepat:

CREATE TABLE users (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- bukan integer
  email       text NOT NULL,                               -- bukan varchar(255)
  age         smallint,                                    -- bukan integer untuk angka kecil
  balance     numeric(12,2),                              -- bukan float untuk uang!
  is_active   boolean DEFAULT true,                        -- bukan integer 0/1
  metadata    jsonb DEFAULT '{}'::jsonb,                 -- bukan text untuk JSON
  created_at  timestamptz DEFAULT now()                    -- bukan timestamp (pakai timezone!)
);

9. Soft Delete yang Benar

-- Soft delete dengan index yang efisien
ALTER TABLE posts ADD COLUMN deleted_at timestamptz;

-- Partial index hanya untuk data yang belum dihapus
CREATE INDEX idx_posts_active ON posts(user_id, created_at)
WHERE deleted_at IS NULL;

-- Query otomatis exclude deleted rows
-- Atau bisa pakai RLS policy di Supabase:
CREATE POLICY 'hide_deleted' ON posts
  FOR SELECT USING (deleted_at IS NULL);

10. VACUUM dan Maintenance

-- Cek table bloat (tanda perlu VACUUM)
SELECT 
  schemaname, tablename,
  n_dead_tup as dead_rows,
  n_live_tup as live_rows,
  ROUND(n_dead_tup::numeric/NULLIF(n_live_tup+n_dead_tup,0)*100, 2) as dead_ratio
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;

-- Manual VACUUM untuk tabel yang bloated
VACUUM ANALYZE orders;

-- Autovacuum biasanya cukup, tapi kalau ada bulk delete/update
-- jalankan manual VACUUM setelahnya

Implementasikan tips-tips ini secara bertahap di project kamu, mulai dari yang paling mudah: pastikan semua kolom yang di-filter punya index yang sesuai.

postgresqldatabaseperformaSQLbackend

Newsletter Digital Uptime

Tips teknologi & bisnis mingguan

Bergabung dengan 2,500+ subscriber yang mendapatkan insight teknologi, tutorial development, dan tips bisnis digital langsung ke inbox mereka setiap minggu.

Tidak ada spam. Unsubscribe kapan saja.

Kami menggunakan cookies untuk meningkatkan pengalaman Anda di website ini. Dengan melanjutkan, Anda menyetujui penggunaan cookies sesuai Kebijakan Privasi kami.