Ilustrasi bottleneck pada connection pool database saat menunggu respon API eksternal

Bagi seorang developer backend, mimpi buruk terbesar adalah menerima laporan “Sistem timeout” saat jam sibuk. Biasanya, masalah ini muncul ketika aplikasi kita berinteraksi dengan API pihak ketiga. Kita menulis kode yang mengambil data dari API, lalu menyimpannya ke database dalam satu alur kerja. Namun, ada jebakan fatal yang sering tidak disadari: membungkus kedua proses tersebut dalam satu transaksi database yang sama. Di ruangproses.id, saya sering menulis catatan tentang bagaimana efisiensi kode bukan hanya soal algoritma, melainkan tentang bagaimana kita memperlakukan resource sistem secara bijak.

Analogi Teller Bank: Mengapa Transaksi Terlalu Panjang itu Berbahaya?

Untuk memahami mengapa kita harus menghindari transaksi panjang, mari gunakan analogi sederhana. Bayangkan database Anda adalah sebuah bank dengan jumlah Teller yang terbatas (ini adalah Connection Pool).

Ketika Anda memulai transaksi database (BeginTransactionAsync), Anda sebenarnya sedang maju ke loket dan meminta Teller untuk “mengunci” posisi Anda. Anda mengatakan, “Jangan layani orang lain dulu sampai urusan saya selesai.”

Namun, apa yang terjadi jika di tengah transaksi tersebut, Anda harus keluar dari gedung bank untuk pergi ke toko lain (API Call) demi mengambil dokumen persyaratan? Anda meninggalkan loket, tetapi Anda tetap “menyandera” Teller tersebut. Selama Anda pergi (menunggu API merespon), Teller tersebut diam mematung, tidak bisa melayani nasabah lain. Jika ada 100 orang melakukan hal yang sama secara bersamaan, Teller (koneksi database) akan habis. Inilah yang menyebabkan Connection Pool Exhaustion.

Kode yang Bermasalah (The Anti-Pattern)

Mari kita lihat contoh kode yang salah, di mana API Call berada di dalam transaksi:

// ANTI-PATTERN: Menunggu API di dalam transaksi
using (var trans = await _context.Database.BeginTransactionAsync())
{
    // API Call memakan waktu 5 detik
    var data = await _httpClient.GetAsync("https://marketplace-api.com/products");
    
    // Database tertahan menunggu API Call selesai
    await _context.Products.AddRangeAsync(data);
    await _context.SaveChangesAsync();
    
    await trans.CommitAsync();
}

Dalam skenario di atas, database benar-benar tersandera. Jika API pihak ketiga sedang down atau lambat, aplikasi Anda akan menumpuk antrean koneksi, yang akhirnya berujung pada system timeout.

Melakukan Refactoring: Strategi Database Query Optimization

Solusinya adalah memisahkan dunia API (I/O eksternal) dengan dunia Database (I/O internal). Kita harus menerapkan teknik Database Query Optimization dengan cara mempersempit scope transaksi. Kita hanya membuka transaksi saat data sudah siap untuk ditulis, bukan saat kita sedang menunggu respon API.

Berikut adalah pendekatan yang lebih baik yang sudah saya terapkan:

// REFACTORED: API Call di luar transaksi
// 1. Baca data dari API (Proses eksternal yang lambat)
var data = await _httpClient.GetAsync("https://marketplace-api.com/products");

// 2. Transaksi Database (Sangat Cepat & Sempit)
// Transaksi hanya dibuka sesaat sebelum melakukan penulisan
await using (var trans = await _context.Database.BeginTransactionAsync())
{
    await _context.Products.AddRangeAsync(data);
    await _context.SaveChangesAsync();
    
    // Commit transaksi dengan cepat, bebaskan Teller!
    await trans.CommitAsync();
}

Dengan memindahkan GetAsync ke luar transaksi, Teller bank (koneksi database) Anda hanya sibuk selama milidetik saat proses Insert berlangsung. Sisanya, koneksi tersebut tetap bebas melayani permintaan pengguna lain.

Mengapa Ini Lebih Efisien?

Dengan memisahkan proses ini, kita mendapatkan tiga keuntungan utama:

  1. Resiliensi: Jika API pihak ketiga lambat, aplikasi Anda tidak akan membuat database macet.
  2. Skalabilitas: Karena setiap transaksi berlangsung singkat, sistem Anda dapat menangani lebih banyak request secara bersamaan (konkurensi tinggi).
  3. Penghematan Resource: Connection pool Anda akan jauh lebih sehat karena setiap koneksi bekerja lebih efisien.

Sebagai referensi tambahan bagi teman-teman developer, Anda bisa mempelajari lebih lanjut mengenai mekanisme performa dalam EF Core melalui dokumentasi resmi Microsoft.

Kesimpulan

Optimasi backend adalah perjalanan berkelanjutan. Di ruangproses.id, saya belajar bahwa setiap kali kita berurusan dengan interaksi eksternal, kita harus bertanya: “Apakah saya sedang menyandera resource penting?” Jika jawabannya ya, lakukan refactoring. Jangan takut untuk memecah proses yang panjang menjadi beberapa langkah yang lebih kecil.

Ingat, database yang responsif adalah fondasi dari pengalaman pengguna yang mulus. Teruslah bereksperimen, dokumentasikan temuan Anda, dan jangan ragu untuk berbagi pengalaman refactoring Anda. Memperbaiki satu baris kode hari ini bisa menyelamatkan server Anda dari crash di masa depan. Selamat berkode!

Leave a Reply

Your email address will not be published. Required fields are marked *