Run a Server Manually
Menjalankan FastAPI di production: pilihan worker, konfigurasi, dan hal yang sering salah.
Intisari
- Development:
--reload. Production: beberapa worker, tanpa reload. - Titik awal jumlah worker:
(2 ร jumlah core) + 1โ lalu ukur dan sesuaikan. - Di dalam container, jalankan satu worker per container dan biarkan orkestrator yang menskalakan.
- Bind ke
0.0.0.0di container;127.0.0.1tidak bisa dijangkau dari luar. - Taruh reverse proxy (nginx/Caddy) di depan untuk TLS, dan matikan buffering-nya untuk SSE.
Development
uv run uvicorn myapp.main:app --reload --port 8000
--reload memantau perubahan file dan me-restart otomatis. Jangan dipakai di production โ ia memakan CPU dan tidak dirancang untuk itu.
Production
uv run uvicorn myapp.main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--log-level info \
--proxy-headers \
--forwarded-allow-ips='*'
| Flag | Kenapa |
|---|---|
--host 0.0.0.0 | Wajib di container โ 127.0.0.1 tidak bisa dijangkau dari luar |
--workers N | Proses paralel, memanfaatkan banyak core |
--proxy-headers | Percaya X-Forwarded-* dari reverse proxy |
--forwarded-allow-ips | Proxy mana yang boleh dipercaya |
--timeout-keep-alive | Sesuaikan dengan keep-alive proxy di depannya |
Berapa worker
(2 ร jumlah core CPU) + 1
Itu titik awal, bukan jawaban akhir. Untuk aplikasi LLM, pertimbangkan:
- Bebannya I/O-bound โ worker banyak menganggur menunggu API
- Tiap worker adalah proses terpisah dengan memorinya sendiri
- Kalau kodemu async penuh, satu worker sudah bisa menangani ratusan koneksi
Untuk aplikasi LLM yang async penuh, sering kali 2โ4 worker sudah cukup โ bahkan di mesin berinti banyak. Kalau kamu menaikkan worker dan throughput tidak naik, hambatannya bukan di CPU: kemungkinan besar rate limit penyedia API atau ada kode blocking yang tersembunyi.
Di dalam container
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8000"]
Satu worker per container. Biarkan orkestrator (Docker Compose, ECS, Kubernetes)
yang menjalankan beberapa container. Alasannya: health check jadi akurat per instance,
restart lebih bersih, dan penggunaan memori bisa diukur per proses. Menjalankan
--workers 4 di dalam container membuat orkestrator hanya melihat satu proses
induk โ dan tidak tahu kalau satu worker di dalamnya mati.
Gunicorn dengan uvicorn worker
uv add gunicorn
uv run gunicorn myapp.main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 300 \
--graceful-timeout 30
Gunicorn memberi manajemen proses yang lebih matang: restart worker yang macet, graceful reload, dan penanganan sinyal yang lebih baik. Kalau kamu tidak memakai orkestrator container, ini pilihan yang bagus.
Naikkan --timeout untuk aplikasi LLM. Default gunicorn adalah 30 detik โ
ia akan membunuh worker yang sedang menunggu respons LLM panjang. Setel ke 300 detik atau lebih.
Reverse proxy
server {
listen 443 ssl;
server_name api.contoh.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /chat/stream {
proxy_pass http://127.0.0.1:8000;
proxy_buffering off; # โ WAJIB untuk SSE
proxy_read_timeout 300s;
proxy_http_version 1.1;
proxy_set_header Connection '';
}
}
Graceful shutdown
Uvicorn menangani SIGTERM dengan berhenti menerima koneksi baru, menyelesaikan
request yang sedang berjalan, lalu menjalankan bagian shutdown dari lifespan.
services:
api:
stop_grace_period: 30s # beri waktu request panjang untuk selesai
Untuk aplikasi LLM, beri jeda yang cukup โ respons streaming bisa berjalan puluhan detik.
Logging
import logging, json, sys
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
return json.dumps({
"waktu": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"pesan": record.getMessage(),
})
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
Tulis log ke stdout, bukan ke file. Di dunia container, itu kontraknya โ orkestrator yang mengumpulkan, merotasi, dan mengirimkannya ke sistem log terpusat. Aplikasi yang menulis ke file sendiri membuat log-nya hilang saat container diganti.
Daftar periksa production
--host 0.0.0.0, tanpa--reload- Konfigurasi lewat environment variable (
pydantic-settings) - Endpoint
/healthdan/ready - Log terstruktur ke stdout
- Reverse proxy untuk TLS; buffering dimatikan untuk rute SSE
- Timeout worker yang cukup panjang untuk respons LLM
- CORS dibatasi ke origin yang benar โ jangan
["*"] - Rate limit per pengguna
- Batas ukuran request body
- Exception handler global supaya detail internal tidak bocor
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.