Masalahnya Selalu TCP_NODELAY
(brooker.co.za)- Saat men-debug masalah latensi pada sistem terdistribusi, hal pertama yang harus diperiksa adalah pengaturan TCP_NODELAY
- Algoritme Nagle adalah pendekatan yang diusulkan dalam RFC896 pada 1984, dirancang untuk mengurangi overhead header TCP saat mengirim paket-paket kecil
- Namun, ketika digabungkan dengan mekanisme delayed ACK, pengiriman data tertunda hingga ACK diterima, sehingga memperburuk kinerja aplikasi yang sensitif terhadap latensi
- Di lingkungan data center modern, RTT sangat pendek, dan sebagian besar sistem sudah mengirim pesan besar, sehingga manfaat algoritme Nagle hampir hilang sepenuhnya
- Karena itu, pada sistem terdistribusi modern TCP_NODELAY seharusnya diaktifkan secara default, dan algoritme Nagle tidak lagi diperlukan
Latar belakang algoritme Nagle
- RFC896 karya John Nagle pada 1984 diusulkan untuk menyelesaikan masalah overhead 4000%: 1 byte data dibanding 40 byte header yang muncul saat mengirim data kecil seperti input keyboard
- Masalah saat itu adalah paket-paket kecil dikirim setiap kali pengguna mengetik satu karakter, sehingga efisiensi jaringan menurun
- Solusinya adalah membatasi agar segmen baru tidak dikirim selama data sebelumnya belum di-ACK
- Pendekatan ini efektif pada lingkungan jaringan saat itu, tetapi tidak cocok untuk sistem modern yang mengutamakan latensi
Interaksi algoritme Nagle dan delayed ACK
- Delayed ACK (RFC813, RFC1122) adalah mekanisme di mana pihak penerima tidak langsung mengirim ACK, melainkan menundanya sampai ada data balasan atau timer habis
- Algoritme Nagle berhenti mengirim sambil menunggu ACK, dan delayed ACK menunda ACK, sehingga terjadi deadlock karena kedua sisi saling menunggu
- John Nagle sendiri menyebut kombinasi ini sebagai “kombinasi yang mengerikan”, dan menunjukkan bahwa kedua fitur ini diperkenalkan secara terpisah tetapi menimbulkan latensi saat dipakai bersama
Masalah di lingkungan modern
- RTT di dalam data center sekitar 500μs, dan bahkan dalam region yang sama pun hanya beberapa milidetik
- Dalam lingkungan seperti ini, menunda transmisi selama satu RTT saja sudah menyebabkan kehilangan performa
- Selain itu, sistem terdistribusi modern sudah mengirim pesan yang cukup besar karena adanya TLS, serialisasi, dan overhead protokol, sehingga masalah paket satu byte hampir tidak lagi ada
- Optimasi untuk pesan kecil kini ditangani di lapisan aplikasi
Mengapa TCP_NODELAY diperlukan
- Pada sistem terdistribusi yang sensitif terhadap latensi, disarankan mengaktifkan TCP_NODELAY untuk menonaktifkan algoritme Nagle
- Ini bukan pilihan yang “tidak efisien” atau “salah konfigurasi”, melainkan keputusan yang sesuai dengan karakteristik hardware dan lalu lintas modern
- Penulis berpendapat bahwa TCP_NODELAY seharusnya menjadi nilai default
- Sebagian kode yang “mengirim pada setiap pemanggilan
write()” mungkin menjadi lambat, tetapi kode seperti itu memang harus diperbaiki secara mendasar
- Sebagian kode yang “mengirim pada setiap pemanggilan
Opsi terkait lainnya
- Opsi TCP_QUICKACK memang mengurangi penundaan ACK, tetapi bukan solusi mendasar karena masalah portabilitas dan perilaku yang tidak konsisten
- Masalah intinya adalah kernel menahan data lebih lama daripada waktu yang dimaksudkan aplikasi, padahal data seharusnya dikirim segera saat
write()dipanggil
Kesimpulan
- Algoritme Nagle adalah penemuan hebat untuk meningkatkan efisiensi jaringan di masa lalu, tetapi
dalam lingkungan jaringan berkecepatan tinggi dan sistem terdistribusi modern, ia justru menjadi fitur usang yang menimbulkan latensi - Karena itu, selalu mengaktifkan TCP_NODELAY diajukan sebagai prinsip dasar dalam perancangan sistem modern
1 komentar
Komentar Hacker News
Saat itu banyak host berbagi satu kanal Ethernet, sehingga CSMA/CD digunakan untuk menghindari tabrakan
Namun saat ini sebagian besar Ethernet memakai struktur point-to-point, dengan lingkungan full-duplex yang memungkinkan kirim dan terima secara bersamaan
Karena itu CSMA tidak lagi diperlukan, dan menurutnya menonaktifkan algoritma Nagle dengan mengatur TCP_NODELAY adalah pilihan yang masuk akal dalam kebanyakan kasus
Fakta bahwa ini dijadikan default menurut saya adalah salah satu kesalahan besar dalam sejarah jaringan
Sekitar tahun 2014, saat mengganti switch data center, saya pernah harus mempertahankan beberapa perangkat lama karena tidak mendukung 10Mbit half-duplex
Ini membantu mencegah terciptanya paket yang terlalu kecil
Nagle adalah optimasi di lapisan TCP, berfungsi menggabungkan paket kecil agar lebih efisien
CSMA adalah masalah lapisan fisik/data link, jadi terpisah dari Nagle
Backend yang ditulis dengan Go secara default sudah mengaktifkan TCP_NODELAY, jadi itu bukan penyebabnya, tetapi bagian tentang bagaimana orang memandang masalah Nagle terasa menarik
Ada diskusi lama juga, lihat thread ini
Pada komunikasi bergaya chatty seperti protokol DICOM, mengatur TCP_NODELAY=1 bisa sangat meningkatkan throughput
Lihat tautan terkait
Saya rasa delayed ACK tidak lagi memberi banyak keuntungan pada workload modern
Dalam lingkungan modern yang berpusat pada HTTP, menurut saya lebih baik mematikan Nagle dan delayed ACK sekaligus
Karena RTT antar-data center hanya ratusan mikrodetik, menunda bahkan satu RTT bisa malah merugikan
Tautan wiki
Aplikasi seharusnya yang memutuskan kapan mengirim dan kapan melakukan buffering
Di Linux, ini adalah hint ke kernel bahwa data tambahan akan segera dikirim, berguna saat header dan data dikirim terpisah
Bila dipakai bersama io_uring, hasilnya bisa lebih efisien
Akan bagus jika ada cara untuk mengosongkan buffer dan mengirim setelah pesan yang memerlukan respons segera
Kanal TCP modern sering mencampur pesan sinkron dan asinkron, jadi ini makin rumit
Saya berharap protokol seperti SCTP dipakai lebih luas
Bahkan pada wrapping seperti TLS, mencari batas pesan itu merepotkan
Secara ideal, harus ada bit “buffering diizinkan” untuk memecah transfer besar, lalu “kirim langsung” pada akhirnya
TCP_CORK setidaknya adalah alternatif yang agak mirip, meski terasa kasar
File I/O juga mengalami masalah serupa
Isinya cukup menarik
Aplikasi seharusnya bisa langsung mengatur sendiri keseimbangan antara latensi dan throughput
Namun bila diimplementasikan di level aplikasi, perlu mengetahui unacked data, sehingga jadi tidak efisien
Bahkan sekadar timer flush 20ms pun akan jauh lebih baik