Belajar FastAPI - Performance & Benchmarking
Episode 25 of 28

Belajar FastAPI - Performance & Benchmarking

Mengukur dan mengoptimalkan performa FastAPI: profiling endpoint dengan cProfile dan middleware timing, tuning connection pooling database, caching dengan Redis, serta load testing dengan k6 untuk menemukan bottleneck.

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

Pendahuluan

Setelah di episode 24 aplikasi kalian dideploy ke produksi, pertanyaan berikutnya selalu muncul: seberapa cepat, dan seberapa siap? Episode ini membahas cara mengukur performa dengan benar — bukan menebak — lalu mengoptimalkan hal yang paling sering jadi bottleneck: database, koneksi, dan cache.

Mengapa episode ini penting? "Optimasi" tanpa pengukuran hanyalah intuisi. Episode ini mengajarkan urutan yang benar: ukur → temukan bottleneck → perbaiki → ukur lagi. Dan kita akan membuktikan bahwa perbaikan paling berdampak di FastAPI biasanya bukan di kode Python, melainkan di database dan cache.

Ukur Dulu: Middleware Timing

Sejak episode 11 kita punya dasar timing middleware. Perluas menjadi logging per endpoint untuk menemukan endpoint yang lambat:

PythonTiming middleware
import time
import logging
 
from fastapi import FastAPI, Request
 
logger = logging.getLogger("perf")
 
 
@app.middleware("http")
async def timing_middleware(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    duration_ms = (time.perf_counter() - start) * 1000
    if duration_ms > 500:
        logger.warning(
            "slow request path=%s method=%s duration_ms=%.1f",
            request.url.path, request.method, duration_ms,
        )
    response.headers["X-Process-Time"] = f"{duration_ms:.2f}"
    return response

Ambang batas (500ms) membuat log hanya berisik saat ada masalah — teknik observability dari episode 17 yang dipakai praktis.

Profiling dengan cProfile

Untuk menemukan fungsi mana yang menghabiskan waktu, pakai cProfile — profiler bawaan Python:

Profile request via cProfile
python -m cProfile -s cumtime -o profile.out \
  -c "from fastapi.testclient import TestClient; from app.main import app; c=TestClient(app); c.get('/items/')"
PythonLihat hasil profiling
import pstats
 
p = pstats.Stats("profile.out")
p.sort_stats("cumtime").print_stats(20)

Output menampilkan 20 fungsi teratas berdasarkan cumulative time — di sinilah kalian melihat apakah waktu habis di query SQL, serialisasi Pydantic, atau kode bisnis. Jangan mengoptimasi tanpa bukti dari output ini.

Tip

Aturan 90/10 performa FastAPI: 90% masalah ada di database (query N+1, index hilang, pooling kecil) dan I/O blocking — bukan di "Python yang lambat". Mulai profil dari lapisan query, bukan dari micro-optimasi sintaks.

Tuning Connection Pooling

Bottleneck paling umum kedua setelah query lambat: koneksi database. Pooling yang kita konfigurasi di episode 14 punya angka yang bisa disetel:

PythonTuning pool
engine = create_async_engine(
    DATABASE_URL,
    pool_size=20,
    max_overflow=10,
    pool_pre_ping=True,
    pool_recycle=1800,
)

Tanda-tanda pooling bermasalah:

  • TimeoutError: QueuePool limit of size ... overflow ... reached → pool terlalu kecil untuk concurrency; naikkan pool_size/max_overflow (atau periksa query yang terlalu lama).
  • Koneksi hang tanpa sebab → kemungkinan koneksi mati; pool_pre_ping=True menyelamatkan.
  • Koneksi melonjak tak terkendali → query lambat menahan koneksi; perbaiki query dulu, jangan sekadar menaikkan angka.

Important

Jangan menaikkan pool tanpa ukur — koneksi database adalah resource terbatas (PostgreSQL default 100). Pool 100 pada 10 worker = database kebanjiran. Sesuaikan pool dengan batas database dan beban aktual yang diukur lewat benchmark.

Caching dengan Redis

Bottleneck ketiga yang paling berdampak besar: query berulang untuk data yang jarang berubah. Redis menjawabnya dengan cache di memory:

Install redis client
pip install redis
PythonCache endpoint dengan Redis
import json
 
import redis.asyncio as aioredis
from fastapi import FastAPI
 
app = FastAPI()
cache = aioredis.from_url(
    "redis://localhost:6379/0", decode_responses=True
)
 
CACHE_TTL = 60  # detik
 
 
@app.get("/items/")
async def list_items() -> dict:
    cached = await cache.get("items:list")
    if cached is not None:
        return json.loads(cached)
 
    items = await fetch_items_from_db()  # query mahal
 
    await cache.set("items:list", json.dumps(items), ex=CACHE_TTL)
    return {"items": items}

Pola cache-aside: cek cache → jika ada, langsung kembalikan; jika tidak, hitung dari database → simpan ke cache → kembalikan. Request berikutnya dalam 60 detik tidak menyentuh database sama sekali.

Warning

Cache menambahkan masalah stale data (data basi). Tetapkan TTL realistis dan invalidate cache saat data diubah (hapus key di endpoint write). Untuk data yang berubah cepat, jangan di-cache — atau terima keterlambatan konsistensi dengan sadar.

Load Testing dengan k6

Benchmark dimulai dengan k6 — tool load testing yang modern dan bisa dijalankan di CI:

Install k6
# Linux/macOS (Homebrew)
brew install k6
JSk6 script
import http from "k6/http";
import { check } from "k6";
 
export const options = {
  stages: [
    { duration: "30s", target: 20 },   // rampa ke 20 VU
    { duration: "1m", target: 50 },    // tahan 50 VU
    { duration: "30s", target: 0 },    // turun
  ],
  thresholds: {
    http_req_duration: ["p(95)<500"],
    http_req_failed: ["rate<0.01"],
  },
};
 
export default function () {
  const res = http.get("http://localhost:8000/items/");
  check(res, { "status 200": (r) => r.status === 200 });
}

Jalankan:

Run load test
k6 run script.js

Output k6 menampilkan metrik kunci: http_req_duration (latensi), http_reqs (throughput), dan http_req_failed (error rate). thresholds mengubah benchmark menjadi gerbang otomatis: jika p(95) > 500ms, test gagal — sempurna untuk CI.

Tip

Aturan pembacaan hasil: perhatikan latensi p95 vs p99 (bukan rata-rata — rata-rata menyembunyikan outlier), dan error rate. Benchmark sebelum vs sesudah perubahan (misal menambah cache Redis) adalah cara membuktikan bahwa optimasi benar-benar bekerja.

Hierarki Optimasi FastAPI

Urutan perbaikan yang paling berdampak:

  1. Database: index yang tepat, hindari N+1, batasi kolom.
  2. Koneksi: pool sizing yang cocok, pool_pre_ping.
  3. Cache: Redis untuk data yang jarang berubah.
  4. Serialisasi: batasi field via response_model (makin sedikit field, makin cepat).
  5. Async: pastikan tidak ada I/O blocking di event loop.
  6. Kode Python: terakhir — biasanya bukan masalah utama.

Common Pitfalls

PitfallSolusi
Optimasi tanpa pengukuranProfil dulu dengan cProfile / timing
Pool dinaikkan tanpa ukurSesuaikan dengan batas DB + benchmark
Cache tanpa invalidateHapus key saat write; TTL realistis
Hanya melihat latensi rata-rataLihat p95/p99 dan error rate
Query N+1 di ORMselectinload/joinedload untuk relasi

Penutup

Inti yang harus dibawa pulang:

  • Ukur dulu: middleware timing + cProfile menemukan bottleneck sejati.
  • Pooling: gejala QueuePool penuh, pool_pre_ping, sesuaikan dengan batas DB.
  • Redis cache-aside untuk data jarang berubah; jangan lupa invalidate.
  • k6 dengan stages + thresholds mengubah load test jadi gerbang CI.
  • Hierarki optimasi: DB → koneksi → cache → serialisasi → kode.

Di episode 26 selanjutnya kita akan membahas ekosistem & tren modern 2026 — posisi FastAPI 0.141, Pydantic 2.x, Starlette 0.4x, perbandingan matang dengan Flask/Django/Express, dan tren AI backends yang membentuk arah framework ini!

Belajar FastAPI - Performance & Benchmarking | Belajar FastAPI