Pengembangan Web

Buffering Respons Nginx - Memisahkan Client Lambat Tanpa Menunda Stream

Buffering Respons Nginx - Memisahkan Client Lambat Tanpa Menunda Stream

Sebuah aplikasi dapat selesai membuat respons dengan cepat, sementara orang yang mengunduhnya memakai koneksi lambat. Dalam situasi itu, siapa yang sebaiknya menunggu: aplikasi atau reverse proxy di depannya? Jawabannya berubah ketika respons tersebut bukan halaman HTML atau dokumen JSON biasa, melainkan stream yang nilainya bergantung pada setiap pesan agar segera tiba.

Response buffering Nginx berada di tengah persoalan waktu tersebut. Untuk upstream HTTP yang dicapai dengan proxy_pass, buffering aktif secara default. Default ini berguna untuk banyak respons konvensional, tetapi tidak netral bagi Server-Sent Events, progress bertahap, atau output lain yang sensitif terhadap waktu. Tujuannya bukan mematikan buffering di mana-mana. Kita perlu menentukan janji setiap endpoint, lalu mempertahankan janji itu secara sengaja.

Ada tiga jam, bukan satu

Respons yang melewati reverse proxy melibatkan setidaknya tiga pihak: client, Nginx, dan aplikasi upstream. Ketiganya dapat bergerak dengan kecepatan berbeda. Aplikasi mungkin menghasilkan byte dengan cepat, Nginx membacanya dengan cepat, sedangkan client menerimanya dengan lambat. Dalam kasus lain, aplikasi mungkin menghasilkan pesan kecil setiap beberapa detik saat client siap menerima tiap pesan seketika.

Menurut referensi resmi modul proxy Nginx, ketika response buffering aktif, Nginx membaca dari upstream secepat mungkin ke buffer memory yang dikonfigurasi. Jika respons tidak muat, sebagian data dapat ditulis ke temporary file. Nginx kemudian dapat terus mengirimkan respons sesuai kecepatan client.

Mekanisme ini memisahkan dua hubungan. Upstream berbicara dengan Nginx; client juga berbicara dengan Nginx. Client lambat tidak selalu harus menahan upstream selama seluruh proses download. Panduan resmi reverse proxy menyebut isolasi dari client lambat sebagai salah satu alasan buffering dapat membantu.

Ketika buffering dimatikan, hubungan tersebut menjadi lebih rapat. Nginx meneruskan data respons ke client secara sinkron saat menerimanya dari upstream. Ini berguna ketika waktu pengiriman menjadi bagian dari perilaku endpoint, tetapi sisi upstream juga menjadi kurang terlindung dari client yang membaca dengan lambat.

Buffering bukan caching

Kata “buffer” mudah tertukar dengan beberapa fitur di sekitarnya. Response buffering adalah penanganan sementara untuk satu respons yang sedang berjalan. Mekanisme ini tidak dengan sendirinya membuat respons dapat digunakan kembali untuk request berikutnya. Penggunaan ulang lintas request merupakan wilayah response caching, lengkap dengan directive, key, aturan freshness, dan keputusan keamanannya sendiri.

Request buffering juga berbeda. proxy_request_buffering mengatur apakah Nginx membaca body request dari client sebelum mengirimkannya ke upstream. Hal itu memengaruhi upload dan perilaku retry request. proxy_buffering mengatur respons yang berjalan ke arah sebaliknya. Mengubah salah satunya tidak otomatis mengubah yang lain.

Perbedaan ini penting saat melakukan diagnosis. “Upload terlambat mencapai aplikasi” dan “browser menerima streamed events sekaligus dalam satu kelompok” mungkin sama-sama disebut buffering dalam percakapan sehari-hari, tetapi keduanya menunjuk jalur data dan directive yang berbeda.

Apa yang sebenarnya diatur oleh response buffer

Untuk respons HTTP yang diproxy, proxy_buffer_size menentukan buffer untuk bagian pertama respons upstream, yang biasanya berisi response header. proxy_buffers menentukan jumlah dan ukuran buffer yang tersedia bagi satu koneksi. Default yang didokumentasikan bergantung pada ukuran memory page platform, sehingga angka hasil salin-tempel tidak otomatis menjadi peningkatan.

Jika respons yang dibuffer melampaui memory buffer yang tersedia, Nginx dapat memakai temporary file. proxy_max_temp_file_size membatasi temporary file tersebut untuk satu respons, sedangkan nilai nol mematikan buffering respons ke temporary file. Opsi ini tidak membuat respons menghilang dan tidak membuktikan penggunaan memory akan aman di bawah concurrency. Opsi tersebut mengubah tempat penampungan kelebihan data yang dibuffer.

Tidak ada jawaban universal yang dapat dipercaya seperti “pakai enam belas buffer” tanpa mengetahui distribusi ukuran respons, concurrency, anggaran memory, perilaku storage, dan populasi client pada layanan yang sebenarnya. Memory buffer yang lebih besar dapat mengurangi penggunaan temporary file sekaligus meningkatkan potensi memory yang tertahan pada banyak request bersamaan. Buffer yang lebih kecil dapat memindahkan tekanan ke storage atau memunculkan masalah response header yang terlalu besar. Pertanyaan yang berguna bukan angka mana yang terlihat cepat, tetapi batas mana yang saat ini benar-benar terlihat.

Streaming menjadikan waktu bagian dari correctness

Halaman biasa tetap berguna jika tiba dalam beberapa potongan besar, bukan banyak potongan kecil. Progress stream berbeda. Jika aplikasi mengirim “10%”, “20%”, dan “30%” dari waktu ke waktu tetapi sebuah intermediary mengumpulkan pesan-pesan itu, byte yang tiba masih dapat benar sementara perilakunya salah bagi pengguna.

Server-Sent Events memberikan contoh konkret. WHATWG HTML Living Standard mendefinisikan event stream dengan media type text/event-stream dan memprosesnya baris demi baris. Catatan pemrosesannya memperingatkan bahwa block buffering dapat menunda event dispatch. Ini bukan berarti setiap endpoint yang disebut “stream” membutuhkan setting Nginx yang sama, tetapi membuktikan bahwa buffering dapat memengaruhi perilaku SSE yang terlihat.

Sebuah location yang spesifik dapat mematikan proxy response buffering tanpa mengubah route biasa:

location /events/ {
    proxy_pass http://app_backend;
    proxy_buffering off;
}

Contoh ini hanya menangani satu mekanisme. Upstream tetap perlu mengirim event record yang lengkap dan melakukan flush melalui runtime-nya sendiri. Compression, proxy lain, CDN, client library, dan kondisi jaringan juga dapat memengaruhi pengiriman. Mematikan buffering Nginx tidak dapat menggantikan aplikasi yang masih menyimpan output di buffer tingkat bahasa pemrograman.

Biarkan aplikasi menandai respons yang menjadi pengecualian

Terkadang satu route mengembalikan respons konvensional sekaligus stream. Modul proxy juga mengenali response header upstream X-Accel-Buffering. Aplikasi dapat mengembalikan X-Accel-Buffering: no untuk respons yang memerlukan perilaku pass-through sambil membiarkan buffering aktif bagi respons biasa. Nginx memproses header ini kecuali penanganannya dimatikan melalui proxy_ignore_headers.

Header ini merupakan mekanisme kontrol Nginx, bukan janji HTTP umum yang dipahami setiap intermediary. Jika reverse proxy atau CDN lain berada di depan Nginx, perilaku buffering-nya harus diperiksa secara terpisah. Pendekatan yang dikendalikan aplikasi berguna ketika aplikasi lebih memahami semantik respons daripada pola URL yang luas, tetapi pendekatan ini juga menciptakan kontrak lintas layer yang perlu didokumentasikan dan diuji.

Apa yang hilang ketika buffering dimatikan

Delay pengiriman yang lebih rendah bukan setting tanpa biaya. Tanpa response buffering, produksi upstream dan konsumsi client menjadi lebih erat terhubung. Client yang lambat atau berhenti sementara dapat membuat jalur respons aktif lebih lama. Konsekuensi terhadap kapasitas bergantung pada server upstream, model concurrency, batas koneksi, laju respons, dan jumlah stream bersamaan; semuanya tidak dapat disimpulkan hanya dari satu directive.

Pemulihan error juga memiliki batas tegas. Dokumentasi proxy_next_upstream menjelaskan bahwa Nginx hanya dapat meneruskan request ke upstream lain jika belum ada data yang dikirim ke client. Setelah respons parsial terlihat, server kedua tidak dapat diam-diam menggantikan bagian awalnya. Batas ini penting bagi streaming, ketika output memang sengaja dimulai sebelum operasi selesai.

Mematikan buffering juga tidak mendefinisikan liveness. proxy_read_timeout mengukur jeda antara dua operasi read berturut-turut dari upstream, bukan durasi seluruh respons. Karena itu, stream berumur panjang membutuhkan keputusan tingkat aplikasi mengenai masa idle, heartbeat, reconnection, dan arti pesan yang tidak kunjung datang. Menaikkan timeout tanpa model tersebut mungkin hanya menunda deteksi kegagalan.

Amati kedua sisi Nginx

Stopwatch di browser saja tidak dapat menjelaskan waktu dihabiskan di mana. Nginx menyediakan variabel waktu yang menghadap client dan upstream agar batas tersebut lebih terlihat. Referensi modul log mendefinisikan $request_time sebagai waktu sejak byte pertama client dibaca hingga pencatatan setelah byte terakhir respons dikirim. Modul upstream secara terpisah menyediakan $upstream_header_time dan $upstream_response_time.

Format access log sementara atau yang dipertahankan dengan hati-hati dapat mencantumkan batas-batas tersebut:

log_format upstream_timing
    '$request status=$status bytes=$body_bytes_sent '
    'request_time=$request_time '
    'upstream_header_time=$upstream_header_time '
    'upstream_response_time=$upstream_response_time';

Nilai-nilai ini perlu ditafsirkan. $request_time yang besar di samping upstream time yang lebih kecil dapat selaras dengan pengiriman client yang lambat, tetapi itu bukan bukti bahwa satu setting buffer salah. Retry dapat menghasilkan beberapa nilai upstream, streaming response membuat “selesai” memang sengaja terlambat, dan traffic agregat lebih berarti daripada satu request. Bandingkan endpoint sejenis dalam kondisi representatif dan periksa error log Nginx untuk peringatan temporary file, bukan melakukan tuning dari satu baris terpisah.

Ubah satu location, lalu verifikasi janjinya

Proses yang hati-hati dimulai dari perilaku, bukan directive:

  1. Klasifikasikan endpoint sebagai respons terbatas, transfer besar, atau stream yang sensitif terhadap waktu.
  2. Catat gejalanya: event pertama terlambat, upstream tertahan client lambat, peringatan temporary file, tekanan memory, atau hal lain.
  3. Ukur waktu yang terlihat oleh client dan waktu upstream sebelum mengubah konfigurasi.
  4. Terapkan perubahan sekecil mungkin pada location tertentu untuk menguji hipotesis.
  5. Validasi client normal dan client yang sengaja diperlambat, lalu amati concurrency serta penggunaan resource.

Sebelum reload, gunakan pengujian konfigurasi yang didokumentasikan:

sudo nginx -t

Referensi command-line Nginx menyatakan bahwa -t memeriksa sintaks konfigurasi dan mencoba membuka file yang dirujuk. Hanya setelah pengujian berhasil, konfigurasi yang sudah diuji boleh di-reload melalui prosedur service management normal pada server. Keberhasilan syntax test tidak membuktikan event tiba tepat waktu, penggunaan memory dapat diterima, atau upstream melakukan flush dengan benar. Semua itu merupakan properti runtime dan membutuhkan pemeriksaan runtime.

Pertahankan default untuk hal biasa, buat pengecualian secara eksplisit

Response buffering bukan optimasi lama yang otomatis harus dihapus, dan streaming bukan alasan untuk mematikannya bagi seluruh situs. Buffering dapat membiarkan upstream selesai sementara Nginx menangani client yang lebih lambat. Perilaku pass-through dapat mempertahankan timing ketika pengiriman bertahap menjadi bagian dari kontrak endpoint. Masing-masing menyelesaikan masalah berbeda.

Titik awal yang dapat dipertanggungjawabkan cukup sederhana: pertahankan buffering untuk respons biasa yang terbatas, buat pengecualian streaming tetap sempit dan terlihat, serta hindari mitos angka buffer. Keputusan akhir harus mengikuti bukti dari jalur respons yang sebenarnya. Jika aplikasi, proxy, dan client masing-masing memiliki jam berbeda, konfigurasi sebaiknya dimulai dengan menanyakan jam mana yang sedang ditunggu pengguna.

References