2 poin oleh GN⁺ 2024-05-10 | 1 komentar | Bagikan ke WhatsApp
  • 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_NODELAY diaktifkan
  • 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
  • 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_NODELAY untuk menonaktifkan algoritma Nagle
  • Dengan mempertimbangkan trafik, komposisi aplikasi, dan performa hardware sistem modern, algoritma Nagle mungkin tidak lagi dibutuhkan
  • Ada juga pandangan bahwa TCP_NODELAY seharusnya menjadi nilai default
  • Kode yang memanggil write untuk setiap byte bisa menjadi lebih lambat jika TCP_NODELAY dijadikan default
  • Jika efisiensi penting, kode seperti itu seharusnya diperbaiki pada implementasi aplikasi, bukan mengandalkan algoritma Nagle

TCP_QUICKACK lebih dekat ke opsi pelengkap

  • TCP_QUICKACK kadang 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_QUICKACK tidak menyelesaikan masalah mendasar ketika kernel menahan data lebih lama daripada yang dimaksudkan program
  • Jika program memanggil write(), maka wajar mengharapkan write() benar-benar dijalankan

1 komentar

 
GN⁺ 2024-05-10
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.

    • Saya juga mirip; setiap kali melihat framework RPC baru, saya punya hobi membuka issue GitHub dengan pertanyaan “apakah sudah mempertimbangkan TCP_NODELAY, atau framework ini hanya bisa 20 panggilan per detik?”
      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.
    • Jika tujuannya terutama memperbaiki aplikasi yang memiliki perilaku write buruk, 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 write dengan baik atau membuat sendiri buffering ala Nagle yang lebih baik dan sesuai dengan aplikasinya.
    • Setuju. Di bidang trading frekuensi tinggi/latensi rendah, mematikan algoritma Nagle sudah cukup lama dikenal, mungkin lebih dari 15 tahun, dan itu juga salah satu hal pertama yang saya cek.
    • Yang benar-benar diinginkan adalah menetapkan penundaan sebesar n mikrodetik, tetapi tidak ada cara yang baik selain menaruh buffering user space sendiri di depan system call.
      Jika tidak ada sesuatu seperti io_uring yang mengimbangi biaya system call, sisi user space bekerja lebih baik.
    • Logika ini awalnya ditujukan untuk hal-hal seperti sesi Telnet.
      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.

    • Perbedaan antara QUIC dan TCP terletak pada dosa asal TCP dan pendahulunya: meniru koneksi port serial asinkron yang tidak terlihat lapisan pesannya.
      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.
    • Benar, tetapi implementasi khusus ini bergantung pada heuristik untuk menentukan cara batching, dan tampaknya asumsi itu tidak cocok.
    • Batching harus dikendalikan oleh aplikasi, bukan protokol.
      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 data
    Ada juga /proc/sys/net/ipv4/tcp_delack_min dan /proc/sys/net/ipv4/tcp_ato_min
    Di FreeBSD ada net.inet.tcp.delayed_ack dan net.inet.tcp.delacktime

    • TCP_QUICKACK memperbaiki bentuk terburuknya, tetapi tidak menyelesaikan seluruh masalah
      Algoritma 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
    • Betul. TCP_QUICKACK harus disetel setiap kali menerima data—apa yang mereka pikirkan?
      Kenapa orang hanya ingin mematikannya pada sebagian waktu saja?
    • Di CentOS/RedHat, Anda bisa menambahkan quickack 1 di akhir route untuk mematikan delayed ACK pada jalur tersebut
  • Di 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 TCP

    • Saya ingin ada protokol yang punya mekanisme bawaan untuk menyadari bahwa pipe di sisi lawan terputus karena alasan apa pun
    • Bukankah QUIC(https://en.wikipedia.org/wiki/QUIC) seharusnya menyelesaikan masalah TCP seperti latensi?
  • Saya 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 seterusnya
    Ini mungkin bukan overhead 40 kali lipat, tetapi bisa sekitar 5 kali lipat

    • Perbaiki saja aplikasinya
      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
    • Melihat pembahasan Telnet, saya jadi penasaran apa yang dilakukan OpenSSH; ternyata OpenSSH menyetel TCP_NODELAY pada semua koneksi, termasuk sesi interaktif
      Saya mengonfirmasinya baik dengan membaca kode maupun mengamati perilaku strace
    • Jika diasumsikan memakai I/O asinkron, satu-satunya cara yang masuk akal adalah memasukkan data ke buffer alih-alih memblokir pada setiap write(2) kecil, jadi menurut saya pola seperti ini sekarang tidak begitu umum
      Di 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
    • Seluruh internet tidak seharusnya dihukum hanya karena sebagian developer menulis kode buruk
    • Sejak awal memang jangan melakukan ini
      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 1 ke tabel routing, tetapi mengaktifkan TCP_NODELAY dari luar aplikasi tampaknya sangat sulit
    Belakangan ini saya mengalami persis masalah yang dijelaskan di sini antara aplikasi milik saya dan aplikasi closed-source yang berinteraksi dengannya

    • Mungkin semacam intersepsi LD_PRELOAD terhadap socket(2) bisa berfungsi
      Caranya memanggil fungsi asli, lalu melakukan sesuatu seperti setsockopt, dan mengembalikan socket yang sudah dimodifikasi
    • Bergantung pada situasi konkretnya, mungkin bisa menaruh socat di tengah
      Jika semula your_app —> server, jadikan your_app -> localhost_socat -> server
      socat punya opsi command line untuk menyetel tcp_nodelay
      Namun 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/hosts
      Aplikasi berkomunikasi dengan socat lewat socket lokal, jadi tcp_nodelay di sisi aplikasi tidak berpengaruh
    • Mungkin bisa menempelkan debugger lalu memanggil setsockopt dengan ptrace
    • Membuka /proc//fd/ lalu menyetel opsi socket mungkin bisa berfungsi. Saya belum mengujinya
    • LD_PRELOAD
  • Sekitar 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

    • Saya tidak tahu apakah yang dimaksud WoW, tetapi sekitar masa itu update game melakukan perubahan persis seperti ini, dan mungkin juga mengubah hal lain
      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

    • Itu episode yang luar biasa, dan benar-benar menunjukkan dengan kuat pentingnya visualisasi
  • Jika memakai bahasa modern seperti Go yang mengaktifkan TCP_NODELAY secara default, ini tidak berlaku :-)

  • Tidak selalu begitu. Kadang-kadang penyebabnya DNS

    • Pernah ada line card router yang rusak membuat bit terakhir alamat IPv4 menjadi 0, sehingga muncul tiket “hanya alamat IPv4 genap yang bisa diakses”
    • Dalam kasus saya, pernah penyebabnya kaca yang kotor
      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
    • Sekali dalam 50 tahun, di tempat berjarak 2 miliar km, penyebabnya bisa jadi chip memori yang rusak
      Tapi biasanya masih bisa diatasi dengan patch workaround, jadi bukan masalah besar
    • Jangan lupa juga BGP, atau disk yang penuh tanpa notifikasi
    • Kalau gagal, itu DNS; kalau gerakannya berhenti begitu saja, itu TCP_NODELAY atau stream buffering
      Web, sebagai sistem yang benar-benar kompleks, juga bisa gagal karena cache