Belajar Backend Developer - Performance Tuning Lanjutan
Episode 25 of 28

Belajar Backend Developer - Performance Tuning Lanjutan

Mengoptimasi performa sampai produksi: profiling untuk menemukan bottleneck nyata, connection pooling untuk database, isolasi level database, dan proses tuning yang berbasis pengukuran bukan tebakan

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Di episode 9-21 kalian membangun cache, queue, dan scaling. Episode 25 membawa performa ke tingkat lanjutan: saat cache sudah dipasang dan instance sudah diskala, masih ada lapisan optimasi yang lebih dalam — profiling, connection pooling, dan analisis bottleneck. Inilah pekerjaan sehari-hari senior backend engineer.

Mengapa penting? Karena di produksi, performa adalah uang dan pengalaman: API yang lambat kehilangan user, query yang boros menaikkan biaya database, dan connection pool yang salah membuat database kehabisan koneksi justru saat traffic naik. Episode ini mengajarkan cara menemukan bottleneck dengan data — bukan tebakan.

Prinsip Pertama: Ukur, Jangan Menebak

Aturan emas tuning: semua keputusan berbasis pengukuran. Identifikasi bottleneck dengan cara yang sama seperti diagnosa medis — ukur dulu, baru tindak.

Ukur latency dan status
curl -s -o /dev/null -w "total: %{time_total}s\n" http://localhost:3000/v1/products
curl -s -o /dev/null -w "connect: %{time_connect}s\n" http://localhost:3000/v1/products

Perbedaan time_connect (jaringan) vs time_total (server + jaringan) memberi petunjuk pertama: lambat di jaringan atau di aplikasi?

Bagian dari Latency

100%

Jika time_total jauh lebih besar dari time_connect, bottleneck ada di aplikasi/database — bukan jaringan.

Profiling Aplikasi

Profiling menemukan di mana CPU dan waktu dihabiskan. Tiga level:

1. Profiling Kode (CPU)

Node.js punya profiler bawaan:

CPU profile Node.js
node --cpu-prof --cpu-prof-dir=/tmp/prof dist/server.js
# hasil .cpuprofile dibuka di Chrome DevTools / speedscope

2. Profiling Database (Query)

Database sering jadi bottleneck terbesar. PostgreSQL menyediakan cara melihat query paling lambat dan paling sering:

Query paling lambat di PostgreSQL
SELECT
  calls,
  round(total_exec_time::numeric, 2) AS total_ms,
  round(mean_exec_time::numeric, 2) AS mean_ms,
  query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Aktifkan pg_stat_statements di postgresql.conf:

postgresql.conf
shared_preload_libraries = 'pg_stat_statements'

3. Profiling CPU Proses (Flamegraph)

Alat seperti async-profiler (Java), pprof (Go), dan perf menghasilkan flamegraph — peta visual di mana waktu menumpuk. Flamegraph dibaca: lebar = waktu; fungsi paling lebar adalah target optimasi.

Note

Jangan optimize berdasarkan perasaan — optimize berdasarkan profil. Lebih dari setengah "optimasi" tanpa pengukuran sia-sia atau bahkan merugikan. Workflow yang benar: ukur → temukan hotspot → perbaiki → ukur lagi. Simpan baseline sebelum perubahan, bandingkan setelahnya.

Connection Pooling

Setiap query butuh koneksi ke database — dan membuat koneksi baru sangat mahal (handshake TCP + auth + proses). Connection pool menjaga sekumpulan koneksi siap pakai.

Pool koneksi dengan node-postgres
import { Pool } from "pg"
 
export const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,              // koneksi maksimal
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 2_000,
})
 
router.get("/v1/products", async (_req, res) => {
  const { rows } = await pool.query(
    "SELECT * FROM products WHERE stock > 0 LIMIT 50",
  )
  res.json({ data: rows })
})

Ukuran Pool: Seberapa Besar?

Kesalahpahaman umum: "semakin banyak koneksi semakin cepat". Salah — koneksi melampaui batas justru memperlambat: tiap koneksi memakan memori, dan database menyentuh disk dalam satu waktu (episode 5). Aturan praktis yang terkenal:

text
ukuran pool ≈ (jumlah core CPU) × 2 + (spare untuk I/O disk)

Contoh: database 8 core → pool sekitar 16-24. Tambahkan pemantauan: jika idle in transaction dan waiting naik, pool terlalu kecil; jika CPU database tidak terpakai dan koneksi menumpuk, pool terlalu besar.

Koneksi Bocor

Kesalahan paling mahal: koneksi diambil tapi tidak pernah dikembalikan → pool habis → aplikasi timeout semua request. Selalu try/finally:

Selalu kembalikan koneksi
import { pool } from "./db.js"
 
async function getProduct(id: number) {
  const client = await pool.connect()
  try {
    return await client.query(
      "SELECT * FROM products WHERE id = $1", [id])
  } finally {
    client.release()
  }
}

Bottleneck Analysis: Lapisan demi Lapisan

Saat API lambat, periksa berurutan:

Lapisan 1: Client/Network

CDN belum dipakai? Konten statis lambat? Episode 9.

Lapisan 2: App Logic

Profiling kode: ada loop O(n²), serialisasi besar, atau blocking sync? Episode 25 bagian profiling.

Lapisan 3: Database

Query lambat di pg_stat_statements. Optimasi: index (episode 5), pagination (episode 4), cache (episode 9).

Lapisan 4: Infrastructure

CPU tinggi? Scaling (episode 21). Koneksi habis? Pool. Latency jaringan? Region/CDN (episode 16).

Contoh alur diagnosa nyata:

text
Gejala: GET /v1/orders lambat 2 detik, CPU app normal.
1. curl -w → time_total 2s, time_connect 20ms → bottleneck di app/db
2. pg_stat_statements → query orders tanpa index, 1.8s eksekusi
3. EXPLAIN ANALYZE → sequential scan (episode 5)
4. CREATE INDEX idx_orders_user → latency turun ke 80ms

Isolasi Level dan Transaksi Panjang

Satu transaksi panjang bisa memblokir yang lain. Pahami isolation level (episode 5) dan hindari transaksi yang membungkus I/O lambat:

text
PENYEBAB LAMBAT:
BEGIN;
SELECT ...;          -- baca cepat
await callExternalApi();  -- 2 detik menahan transaksi!
UPDATE ...;
COMMIT;

Transaksi menahan lock dan memblokir baris lain (episode 5). Aturan: transaksi sependek mungkin — I/O eksternal di luar transaksi; lock dipecah; isolasi disesuaikan kebutuhan (biasanya READ COMMITTED sudah cukup).

Warning

Transaksi yang membungkus panggilan eksternal (HTTP/queue) adalah salah satu penyebab "database menggantung" paling umum: aplikasi menahan transaksi sambil menunggu layanan lain, dan layanan lain menunggu database. Pecah transaksi: tulis dulu, proses eksternal setelah commit, lalu update status.

Load Testing

Tuning tanpa load test tidak terverifikasi. k6 adalah alat load testing modern:

JSk6 - load test sederhana
import http from "k6/http"
import { check, sleep } from "k6"
 
export const options = {
  stages: [
    { duration: "1m", target: 50 },   // naik ke 50 user
    { duration: "2m", target: 50 },   // tahan
    { duration: "1m", target: 0 },    // turun
  ],
  thresholds: {
    http_req_duration: ["p(95)<500"], // 95% request < 500ms
  },
}
 
export default function () {
  const res = http.get("http://localhost:3000/v1/products")
  check(res, { "status 200": (r) => r.status === 200 })
  sleep(0.1)
}
Jalankan load test
bunx k6 run load-test.js

Hasilnya memberi jawaban objektif: berapa kapasitas sebenarnya, dan di mana titik patah — sebelum event besar (episode 21), bukan setelahnya.

Tip

Load test yang baik mengukur response time percentiles (p50, p95, p99), bukan rata-rata — rata-rata menyembunyikan lonjakan. p95 di bawah ambang berarti mayoritas user nyaman. Simpan hasil setiap load test sebagai baseline: begitulah kalian tahu apakah perubahan benar-benar memperbaiki atau merusak.

Common Pitfalls

Optimasi Tanpa Pengukuran

Menebak bottleneck = buang waktu. Profil dulu.

Pool Terlalu Besar

Koneksi berlebih memperlambat database. Ikuti aturan core × 2 + spare.

Transaksi Membungkus I/O Eksternal

Menahan lock sambil menunggu HTTP = menggantung. Pecah transaksi.

Melihat Rata-rata, Bukan Percentile

p50 yang baik bisa menyembunyikan p99 yang buruk. Ukur percentile.

Optimasi DB Tanpa Melihat Plan

Index yang salah tidak dipakai. EXPLAIN ANALYZE selalu.

Penutup

Episode 25 menuntaskan performa: prinsip ukur-jangan-menebak, profiling kode dan database, connection pooling yang tepat, analisis bottleneck berlapis, isolasi level, dan load testing berbasis percentile.

Inti yang harus dibawa pulang:

  • Ukur → temukan → perbaiki → ukur lagi; baseline adalah syarat.
  • Profiling: pg_stat_statements untuk query, CPU profiler untuk kode.
  • Connection pool ≈ core × 2 + spare; koneksi wajib dikembalikan.
  • Analisis berlapis: network → app → database → infrastructure.
  • Transaksi sependek mungkin; jangan bungkus I/O eksternal.
  • Load test dengan percentile (p95/p99), bukan rata-rata.

Di episode 26 selanjutnya kita akan melihat gambaran besar industri: ekosistem & tren modern 2026 — stack yang dominan dan arah backend ke depan. Sampai jumpa di episode 26!

Belajar Backend Developer - Performance Tuning Lanjutan | Belajar Backend