Bali, Indonesia
Insights

Menangani MQTT Connection Storm: Analisis Socket Exhaustion dan Epoll Starvation pada Edge Gateway

Invalid Date
0:00
Implementasi komputasi edge pada ekosistem IoT—seperti yang dipaparkan dalam riset Analisis Implementasi Edge Computing dalam Mengurangi Latensi pada Sistem Internet of Things oleh Telaumbanua dan Giawa—secara empiris memang terbukti memangkas latensi transmisi secara drastis dibanding membuang seluruh telemetri langsung ke cloud broker. Dengan menempatkan papan komputasi ringkas seperti single-board computer (SBC) ARM atau gateway industri tepat di jaringan lokal mikrokontroler (ESP32), responsivitas aktuator dan agregasi metrik menjadi sangat instan. Namun, ada paradoks keandalan yang kerap luput saat arsitektur ini dibawa dari meja lab ke lantai produksi: ketika gateway lokal mengalami reboot mendadak atau terjadi partisi jaringan sementara, seluruh armada edge nodes akan mencoba menyambung kembali secara bersamaan. Fenomena thundering herd ini dikenal sebagai MQTT Connection Storm. Ribuan mikrokontroler yang kehilangan koneksi TCP akan langsung masuk ke loop rekoneksi agresif. Bagi edge gateway berspesifikasi terbatas, gelombang rekoneksi ini bukan sekadar lonjakan lalu lintas data biasa, melainkan badai konkurensi di lapisan kernel yang berujung pada socket exhaustion, luapan antrean TCP SYN, dan epoll starvation. Akibatnya, broker lokal macet total bukan karena beban transfer data MQTT (PUBLISH), melainkan karena kegagalan menangani proses inisiasi jabat tangan jaringan (HANDSHAKE).
Ketika 3.000 hingga 10.000 mikrokontroler mengirim paket TCP SYN dalam jendela waktu sub-detik, beban pertama ditanggung oleh subsistem jaringan Linux kernel sebelum aplikasi broker (seperti Mosquitto atau VerneMQ) sempat membaca data apa pun. Kernel memiliki dua antrean terpisah untuk proses jabat tangan tiga arah (three-way handshake): SYN queue (antrean koneksi semi-terbuka / SYN_RECV) dan Accept queue (antrean koneksi yang sudah menerima ACK final dari klien dan menunggu dijemput syscall accept4()). Pada sebagian besar distribusi Linux untuk edge gateway, nilai default net.core.somaxconn dan parameter backlog broker sering kali masih berada di angka konservatif (misalnya 128 atau 512). Begitu laju koneksi datang melampaui kemampuan event loop broker untuk memanggil accept(), Accept queue akan penuh seketika. Secara default, Linux tidak merespons dengan paket TCP RST ketika antrean ini meluap; kernel cukup membuang (drop) paket ACK ketiga yang masuk. Mikrokontroler mengira koneksi sudah terbangun dan mengirim paket MQTT CONNECT, namun gateway tidak mengakuinya. Perangkat IoT kemudian mengalami timeout, menutup soket secara sepihak, dan memulai ulang siklus SYN baru. Siklus ganas ini melipatgandakan beban jabat tangan secara eksponensial di tingkat antarmuka jaringan.
BASH
# Verifikasi drop pada TCP accept queue di gateway
netstat -s | grep -i "listen drops"

# Tingkatkan batas antrean soket di tingkat kernel (/etc/sysctl.d/99-edge.conf)
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_abort_on_overflow = 0
Mengatur tcp_abort_on_overflow = 0 memastikan kernel tetap bersikap defensif dengan membiarkan klien mencoba retransmisi ACK, alih-alih melempar RST yang memicu reset logika instan pada firmware mikrokontroler yang belum tentu menangani error RST dengan anggun.
Jika paket koneksi berhasil melewati antrean kernel dan masuk ke ruang pengguna (user-space), ancaman berikutnya bergeser ke keterbatasan deskriptor berkas (file descriptors). Setiap soket TCP yang diterima broker merepresentasikan satu file descriptor terbuka. Masalahnya, ketika rekoneksi massal gagal di tengah jalan—misalnya karena timeout otentikasi TLS atau parsing identitas MQTT client ID yang bertabrakan—soket ditutup secara cepat dan berpindah ke status TIME_WAIT selama durasi 2MSL (Maximum Segment Lifetime, default Linux 60 detik). Ketika ribuan perangkat mencoba menyambung ulang setiap 2 detik, soket dalam status TIME_WAIT menumpuk jauh lebih cepat daripada laju daur ulangnya oleh kernel. Kondisi ini memicu error klasik EMFILE (Too many open files) pada proses broker. Lebih parah lagi, jika gateway juga bertindak sebagai jembatan (MQTT bridge) yang meneruskan data lokal ke cloud melalui koneksi balik yang fluktuatif, kehabisan ephemeral port lokal (net.ipv4.ip_local_port_range) akan melumpuhkan komunikasi keluar gateway seutuhnya.
BASH
# Optimasi pemanfaatan kembali soket TIME_WAIT untuk koneksi keluar
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535

# Pastikan limit file descriptor systemd service broker diperbesar
# /etc/systemd/system/mosquitto.service.d/limits.conf
[Service]
LimitNOFILE=65536
Sebagian besar broker MQTT kelas ringan beroperasi dengan pola single-threaded event loop berbasis epoll. Dalam satu putaran event loop, broker memanggil epoll_wait(), memproses aktivitas deskriptor yang terdaftar, lalu kembali menunggu event baru. Ketika connection storm melanda, listen socket broker akan terus-menerus memancarkan event EPOLLIN yang menandakan tersedianya koneksi baru untuk diambil. Di sinilah anomali event loop starvation terjadi. Jika kode internal broker dirancang untuk menguras seluruh koneksi baru secara serakah (greedy accept) sebelum melayani antrean soket klien yang sudah aktif, CPU core gateway akan sepenuhnya terpakai untuk menangani alokasi buffer koneksi baru dan jabat tangan TLS/SSL yang memakan banyak siklus komputasi. Klien yang sebelumnya sudah terhubung stabil tidak mendapatkan giliran untuk memproses paket MQTT PINGREQ (keep-alive) tepat waktu. Broker menduga klien lama tersebut telah hang atau mati karena durasi keep-alive terlewati, lalu memutus soket mereka secara paksa. Klien yang baru saja diputus ini seketika berbalik memicu SYN baru, mengunci gateway dalam siklus degradasi kaskade (cascading failure) yang tidak bisa pulih dengan sendirinya tanpa intervensi pemutusan daya fisik. Broker modern mengatasinya dengan menerapkan batasan kuota koneksi per tick: hanya memproses sejumlah N pemanggilan accept() per putaran epoll_wait(), memastikan trafik I/O klien yang sudah hidup tetap mendapatkan porsi komputasi yang adil.
Memperkuat kernel dan konfigurasi broker gateway hanyalah separuh jalan dari resolusi sistemik. Sumber utama dari badai konkurensi ini berada pada logika loop rekoneksi di firmware perangkat IoT itu sendiri. Mayoritas pustaka klien MQTT mikrokontroler menggunakan loop rekoneksi deterministik: jeda waktu statis 5 detik atau exponential backoff konvensional tanpa keacakan. Jika ratusan ESP32 terputus pada detik yang sama (misalnya saat sakelar jaringan lokal menyala kembali), interval backoff eksponensial murni (1s, 2s, 4s, 8s) hanya akan memindahkan waktu kedatangan badai secara berulang-ulang tanpa memecah kepadatannya. Solusi mutlak pada tingkat mikrokontroler adalah mengimplementasikan algoritma Decorrelated Jitter. Alih-alih melipatgandakan interval secara matematis kaku, waktu tunggu acak disisipkan di setiap iterasi berdasarkan durasi sebelumnya, sehingga mendistribusikan kurva kedatangan koneksi secara merata sepanjang sumbu waktu:
C
// Logika rekoneksi dengan Decorrelated Jitter pada ESP32 / FreeRTOS
uint32_t calculate_backoff(uint32_t prev_sleep, uint32_t min_sleep, uint32_t max_sleep) {
    uint32_t upper_bound = prev_sleep * 3;
    if (upper_bound > max_sleep) upper_bound = max_sleep;
    uint32_t sleep_ms = min_sleep + (esp_random() % (upper_bound - min_sleep + 1));
    return sleep_ms;
}
Mengombinasikan mitigasi dua sisi—peningkatan toleransi antrean jaringan di tingkat kernel gateway Linux, isolasi prioritas event loop pada broker, serta penanaman jitter acak pada firmware mikrokontroler—mengubah arsitektur edge computing dari sekadar prototipe yang rapuh terhadap partisi jaringan menjadi platform terdistribusi yang tahan banting menghadapi gejolak konektivitas ekstrem.