Penggunaan Berkelanjutan Opsi TCP_NODELAY
(brooker.co.za)- Masalah latensi pada sistem terdistribusi berulang kali teratasi hanya dengan mengaktifkan
TCP_NODELAY, yang menunjukkan bahwa perilaku default TCP bisa tidak selaras dengan workload modern - Algoritma Nagle dibuat dalam RFC896 pada 1984 untuk mengurangi biaya header paket TCP kecil, dengan cara menahan pengiriman segmen baru sampai ACK diterima
- Jika digunakan bersama delayed ACK, satu sisi menunggu ACK sementara sisi lain menunggu data respons atau timer, sehingga merugikan aplikasi pipeline yang sensitif terhadap latensi
- Meski RTT di dalam data center sekitar 500μs, server modern dapat menyelesaikan banyak pekerjaan dalam waktu itu, sehingga manfaat menunda transmisi selama satu RTT menjadi tidak jelas
- Sistem terdistribusi modern, karena TLS, encoding, serialisasi, dan ukuran pesan aplikasi, semakin jarang menghadapi masalah paket satu byte, sehingga dalam lingkungan sensitif latensi lebih alami untuk menonaktifkan algoritma Nagle
Konfigurasi yang pertama diperiksa saat debugging latensi
- Saat muncul masalah latensi di sistem terdistribusi, hal pertama yang biasanya diperiksa adalah apakah
TCP_NODELAYdiaktifkan - Banyak pengembang sistem terdistribusi telah membuang waktu pada masalah yang ternyata bisa diselesaikan hanya dengan satu opsi socket sederhana ini
- Pengulangan ini menunjukkan bahwa perilaku default TCP mungkin tidak cocok untuk sistem terdistribusi masa kini, atau bahwa algoritma Nagle sendiri mungkin sudah usang
Masalah yang ingin diselesaikan oleh algoritma Nagle
- RFC896 adalah dokumen tahun 1984 yang membahas masalah paket kecil
- Pada masa itu, saat mengirim data seperti input keyboard yang masuk satu karakter demi satu karakter melalui TCP, muncul inefisiensi karena 1 byte data membawa 40 byte header
- Untuk setiap 1 byte data berguna, ada 40 byte header, sehingga terjadi overhead 4000%
- Ini masih bisa ditoleransi pada beban ringan, tetapi buruk untuk throughput jaringan
- Tujuan algoritma Nagle adalah meningkatkan throughput dengan mengamortisasi biaya header TCP dengan lebih baik
- Paket kecil terutama muncul pada aplikasi interaksi manusia seperti shell, atau implementasi yang menyerahkan data sedikit demi sedikit ke kernel lewat beberapa pemanggilan
write
- Paket kecil terutama muncul pada aplikasi interaksi manusia seperti shell, atau implementasi yang menyerahkan data sedikit demi sedikit ke kernel lewat beberapa pemanggilan
- Perilaku intinya adalah tidak langsung mengirim data baru sebagai segmen TCP terpisah jika data yang sebelumnya dikirim belum menerima ACK
- Algoritma Nagle sering dijelaskan bersama timer, tetapi RFC896 sendiri tidak menggunakan timer terpisah selain round-trip time (RTT) jaringan
Latensi yang muncul saat digabung dengan delayed ACK
- Delayed ACK adalah pendekatan yang tidak langsung mengirim konfirmasi penerimaan paket, melainkan menunggu sampai ada data untuk dikirim balik atau sampai timer habis
- RFC813 adalah dokumen awal dari 1982 yang mengusulkan penundaan ACK, dan membahas bahwa dalam situasi tertentu penerima dapat menunda pengiriman ACK lalu menetapkan timer untuk mengirimnya nanti
- RFC1122 memformalkan delayed ACK dengan lebih resmi
- Kedua fitur ini masuk akal secara terpisah, tetapi bila dipakai bersama dapat menimbulkan latensi
- Algoritma Nagle menunggu penerimaan ACK sebelum mengirim lebih banyak data
- Delayed ACK menunda pengiriman ACK sampai data respons siap atau timer habis
- Ini membantu memenuhi paket, tetapi tidak cocok untuk aplikasi pipeline yang sensitif terhadap latensi
- Komentar John Nagle di Hacker News juga melihat masalahnya bukan pada pencegahan tinygram, melainkan pada gabungan penundaan ACK dan timer tetap
- Ini adalah contoh dua fitur protokol yang masuk akal tetapi ketika digabung menghasilkan perilaku yang tidak diinginkan, dan interaksi seperti inilah yang membuat perancangan protokol menjadi sulit
Titik ketidakcocokan dengan sistem terdistribusi modern
- Bahkan tanpa delayed ACK, perilaku algoritma Nagle bisa berbeda dari yang diinginkan sistem terdistribusi modern
- Dalam lingkungan sekarang, RTT sendiri adalah biaya yang tidak bisa diabaikan
- RTT tunggal di dalam data center biasanya sekitar 500μs
- RTT antar data center dalam region yang sama berada pada kisaran beberapa ms
- Jalur global bisa mencapai ratusan ms
- Server modern mampu memproses banyak pekerjaan bahkan dalam ratusan μs, sehingga sulit mengatakan bahwa menunda pengiriman data selama satu RTT memberi manfaat yang jelas
- Justifikasi asli algoritma Nagle adalah mengurangi overhead header 40x yang muncul pada paket satu byte
- Database terdistribusi dan sistem terdistribusi modern umumnya tidak mengirim paket satu byte
- Data yang dikirim aplikasi sendiri sudah lebih besar
- Ada tambahan overhead protokol seperti TLS
- Ada juga overhead encoding dan serialisasi
- Masalah perlunya menghindari pesan kecil masih penting, tetapi tanggung jawabnya secara efektif telah berpindah ke lapisan aplikasi
- Mengirim data yang dibungkus JSON satu byte demi satu byte tetap tidak efisien, terlepas dari algoritma Nagle
Mengapa TCP_NODELAY dipandang sebagai pilihan default
- Jika membangun sistem terdistribusi yang sensitif terhadap latensi di atas hardware kelas data center modern, Anda bisa mengaktifkan
TCP_NODELAYuntuk menonaktifkan algoritma Nagle - Dengan mempertimbangkan trafik, komposisi aplikasi, dan performa hardware sistem modern, algoritma Nagle mungkin tidak lagi dibutuhkan
- Ada juga pandangan bahwa
TCP_NODELAYseharusnya menjadi nilai default - Kode yang memanggil
writeuntuk setiap byte bisa menjadi lebih lambat jikaTCP_NODELAYdijadikan default - Jika efisiensi penting, kode seperti itu seharusnya diperbaiki pada implementasi aplikasi, bukan mengandalkan algoritma Nagle
TCP_QUICKACK lebih dekat ke opsi pelengkap
TCP_QUICKACKkadang disebut sebagai alternatif, tetapi karena kurang portabel dan memiliki makna yang agak khusus, sulit dijadikan pilihan utama- Arti tepatnya perlu dicek langsung di Linux tcp man page
- Masalah yang lebih besar adalah bahwa
TCP_QUICKACKtidak menyelesaikan masalah mendasar ketika kernel menahan data lebih lama daripada yang dimaksudkan program - Jika program memanggil
write(), maka wajar mengharapkanwrite()benar-benar dijalankan
1 komentar
Komentar Hacker News
Selama karier saya, saya sudah beberapa kali memperbaiki masalah latensi yang disebabkan oleh algoritma Nagle, dan sekarang itu menjadi hal pertama yang saya curigai.
Logikanya sendiri masuk akal, tetapi tidak cocok untuk sebagian beban kerja, dan menurut saya engineer harus memilihnya secara eksplisit saat membuat socket, bukan menyerahkannya pada nilai bawaan sistem operasi.
Masalahnya bukan apakah ini opsi yang baik/buruk, melainkan adanya pengaturan yang mengubah cara pengiriman data dengan cukup agresif, tetapi banyak orang tidak tahu keberadaannya.
Sejauh ini saya selalu menemukan bug.
Contoh: https://cloud-haskell.atlassian.net/browse/DP-108 atau https://github.com/agentm/curryer/issues/3
Namun saya tidak setuju dengan “ini bukan opsi baik/buruk”.
Ini adalah heuristik di sisi kernel untuk “secara ajaib memperbaiki” aplikasi yang ditulis dengan buruk, dan seperti kata artikel, aplikasi yang normal tidak melakukan system call
write()jaringan 1 byte.Perangkat lunak seperti itu harus diperbaiki.
Menurut saya kasus ketika fitur ini masuk akal hanyalah situasi langka saat Anda adalah administrator sistem kernel, tetapi karena alasan seperti politik tim tidak bisa memperbaiki perangkat lunak yang berjalan di mesin tersebut.
Selain itu, ini membuat perangkat lunak yang normal menjadi lebih rumit.
Artinya kita harus secara eksplisit mematikan sihir aneh yang dimasukkan untuk sedikit menaikkan throughput perangkat lunak yang ditulis buruk, dan pada perangkat lunak yang ditulis dengan benar sihir itu menciptakan latensi besar yang mengejutkan.
John Nagle mengatakan di thread yang ditautkan dari sini bahwa delayed ACK lebih buruk, dan saya setuju.
Namun pola Send/Send/Receive yang diperburuk oleh algoritma Nagle adalah use case yang sepenuhnya valid dan umum, dan berlaku untuk semua yang melakukan RPC ber-pipeline di atas TCP.
Menurut saya delayed ACK dan algoritma Nagle keduanya seharusnya mati secara default.
Namanya juga seharusnya kira-kira TCP_DELAY, dan hanya dinyalakan ketika Anda tidak ingin mengimplementasikan buffering dasar di user space.
Orang-orang seharusnya tidak perlu mengetahui hal-hal seperti ini, dan perilaku default seharusnya tidak mengejutkan.
writeburuk, opsi untuk menyalakan TCP_DELAY menjadi cukup aneh.Itu berarti dibutuhkan software engineer yang cukup pintar untuk mengetahui opsi ini, tetapi tidak cukup pintar untuk membagi pemanggilan
writedengan baik atau membuat sendiri buffering ala Nagle yang lebih baik dan sesuai dengan aplikasinya.Jika tidak ada sesuatu seperti
io_uringyang mengimbangi biaya system call, sisi user space bekerja lebih baik.Seingat saya, itulah motivasi utamanya.
Kesimpulannya agak aneh. Algoritma Nagle jelas merupakan upaya batching write, dan terlepas dari hardware, jaringan, aplikasi, maupun use case, dalam beberapa kasus batching write memang lebih baik.
Bahkan hari ini banyak komputasi menggunakan batching write, dan aplikasi jaringan juga mendapat manfaat.
Protokol tingkat lebih tinggi yang lebih baru seperti QUIC melakukan batching write, dan pada dasarnya memindahkan koneksi independen serta penanganan error TCP ke user space, sehingga protokol mendorong data ke aplikasi secepat mungkin, sementara koneksi dan penanganan error untuk masing-masing stream ditangani oleh aplikasi, bukan oleh stack TCP/IP host atau router.
Jika jaringan kembali jenuh seperti dulu, algoritma Nagle akan kembali dalam bentuk modifikasi QUIC, kemungkinan dengan cara menunggu pengiriman paket QUIC sampai kriteria tertentu tercapai di bagian yang lebih dalam dari kode aplikasi.
Segala hal dalam teknologi akan diciptakan ulang ketika hardware atau software mencapai bottleneck. Karena performa keduanya tidak tumbuh dengan kecepatan yang sama, pada akhirnya selalu begitu.
Selain bandwidth, algoritma Nagle juga berguna ketika jumlah paket per detik menjadi jenuh karena paket-paket kecil.
Berkat itu, Anda memang bisa mengakses layanan dengan teletypewriter fisik, tetapi TCP menjadi tidak tahu batas pesan, dan meski sekarang sebagian pengetahuan itu bisa disisipkan, perangkat lunak awal tidak bisa melakukannya.
Sebaliknya, banyak protokol non-TCP seperti QUIC, SCTP, dan TP4 menyediakan batas pesan secara eksplisit.
Antarmukanya dengan sistem berbasis pesan yang paling-paling direkonstruksi, bukan port serial yang diemulasikan.
Protokol tidak memiliki konteks yang cukup untuk melakukan batching dengan benar.
Sebaliknya, bagaimana kalau mematikan delayed ACK?
Masalahnya adalah perilaku patologis yang muncul saat pencegahan paket kecil dan delayed ACK saling berinteraksi
Opsi yang terekspos untuk mematikan pencegahan paket kecil adalah
TCP_NODELAY, lalu bagaimana cara mematikan delayed ACK?Maksudnya saat ingin membenchmark keempat kombinasi untuk melihat mana yang paling cocok
Setelah sedikit mencari, di Linux ada opsi socket
TCP_QUICKACK, tetapi harus disetel setiap kali menerima dataAda juga
/proc/sys/net/ipv4/tcp_delack_mindan/proc/sys/net/ipv4/tcp_ato_minDi FreeBSD ada
net.inet.tcp.delayed_ackdannet.inet.tcp.delacktimeTCP_QUICKACKmemperbaiki bentuk terburuknya, tetapi tidak menyelesaikan seluruh masalahAlgoritma Nagle masih bisa menunggu hingga satu waktu round-trip maksimum sebelum mengirim data, dan jika mengikuti RFC, itu hanya menambah latensi dengan nyaris tanpa manfaat
TCP_QUICKACKharus disetel setiap kali menerima data—apa yang mereka pikirkan?Kenapa orang hanya ingin mematikannya pada sebagian waktu saja?
quickack 1di akhir route untuk mematikan delayed ACK pada jalur tersebutDi dunia dengan bandwidth terbatas, ukuran minimum paket 64 byte, dan masih perlu jeda antar-frame, mengirim satu paket TCP untuk setiap byte adalah pemborosan bandwidth yang sangat besar
Di sebagian besar jaringan Ethernet, ukuran minimumnya masih seperti itu sampai sekarang, dan mengirim ACK kosong juga sama
Namun posisi dasar saya adalah ini: bukan
TCP_NODELAY, melainkan cukup TCPSaya kurang bisa menerima argumen bahwa Nagle sudah tidak diperlukan lagi
Telnet memang tidak penting hari ini, tetapi rasanya masih banyak aplikasi dengan pola seperti ini
write(fd, "Host: "),write(fd, hostname),write(fd, "\r\n"),write(fd, "Content-type: "), dan seterusnyaIni mungkin bukan overhead 40 kali lipat, tetapi bisa sekitar 5 kali lipat
Saat menulis ke file, kita tidak melakukan seperti ini lalu berharap performa ajaib, meskipun sistem operasi juga punya buffer sendiri
Tidak ada alasan untuk berharap berbeda hanya saat menulis ke socket, dan sejak awal Nagle juga tidak menyelamatkan Anda dari overhead system call
Saya mengonfirmasinya baik dengan membaca kode maupun mengamati perilaku
stracewrite(2)kecil, jadi menurut saya pola seperti ini sekarang tidak begitu umumDi server, I/O asinkron biasanya diperlukan agar bisa diskalakan dengan baik, dan di klien pun memblokir karena panggilan jaringan memberi pengalaman yang buruk
Apalagi di lingkungan saat ini yang sering mengalami perubahan jaringan dan sering keluar dari jangkauan
Bahkan mengabaikan aspek jaringan, system call cukup mahal sehingga buruk untuk performa
Saya penasaran apakah ada yang tahu cara bagus untuk mengaktifkan TCP_NODELAY pada socket ketika tidak bisa mengakses source aplikasi
Saya tidak menemukan pengaturan kernel yang berlaku permanen, atau perintah untuk mengubahnya setelah berjalan
Saya bisa mematikan delayed ACK dengan memasukkan
quickack 1ke tabel routing, tetapi mengaktifkan TCP_NODELAY dari luar aplikasi tampaknya sangat sulitBelakangan ini saya mengalami persis masalah yang dijelaskan di sini antara aplikasi milik saya dan aplikasi closed-source yang berinteraksi dengannya
socket(2)bisa berfungsiCaranya memanggil fungsi asli, lalu melakukan sesuatu seperti
setsockopt, dan mengembalikan socket yang sudah dimodifikasiJika semula
your_app —> server, jadikanyour_app -> localhost_socat -> serversocat punya opsi command line untuk menyetel
tcp_nodelayNamun Anda harus meyakinkan aplikasi closed-source itu agar terhubung ke localhost
Jika ia melakukan DNS lookup, mungkin bisa membuatnya terhubung ke localhost lewat entri
/etc/hostsAplikasi berkomunikasi dengan socat lewat socket lokal, jadi
tcp_nodelaydi sisi aplikasi tidak berpengaruhsetsockoptdenganptrace/proc//fd/lalu menyetel opsi socket mungkin bisa berfungsi. Saya belum mengujinyaLD_PRELOADSekitar 15 tahun lalu saya memainkan MMO yang sangat real-time, dan semua komunikasinya memakai TCP
Saat mengklik tombol, aksi saya bahkan tidak muncul di layar sampai paket respons kembali
Akhirnya anak-anak yang memainkan game ini, termasuk saya, menemukan bahwa jika mengaktifkan TCP_NODELAY, game menjadi sangat mulus
Efeknya terutama besar bagi pemain di California yang dekat dengan server game
Efek samping yang menarik: sebelum perubahan, jika TCP stream macet, game akan berhenti sebentar lalu memutar ulang event yang terlewat dengan sangat cepat
Biasanya event itu adalah adegan saya mati
Setelah perubahan, sebagai gantinya koneksi langsung terputus
Episode podcast Oxide and Friends terkait: https://www.youtube.com/watch?v=mqvVmYhclAg
Jika memakai bahasa modern seperti Go yang mengaktifkan TCP_NODELAY secara default, ini tidak berlaku :-)
https://github.com/golang/go/issues/57530
Baru tahu
Bukankah cukup memakai library networking yang “modern” saja?
Tidak selalu begitu. Kadang-kadang penyebabnya DNS
Di router dekat lokasi konstruksi, debu mengendap di celah antara laser dan serat optik sehingga sinyal cukup teredam, dan terlihat packet loss 40–50%
Setelah menemukan titik loss, NOC mengirim email ke operator transmisi terkait, dan teknisi yang dikirim sehari kemudian membalas dengan cerita itu
Tapi biasanya masih bisa diatasi dengan patch workaround, jadi bukan masalah besar
TCP_NODELAYatau stream bufferingWeb, sebagai sistem yang benar-benar kompleks, juga bisa gagal karena cache