<

MQTT QoS Seçimi: Sahadaki 10 Bin Cihaz Ne Zaman Yalan Söyler?

Saat sabahın 04.17'siydi, telefonum enerji izleme müşterimizin operasyon müdüründen gelen mesajla titredi: "Gece boyunca aynı sayaçtan 340 kez aynı tüketim verisi düşmüş, faturalama ekibi çıldırdı." Sahada 10 bin 400 cihazımız vardı ve günde ortalama 4,2 milyon MQTT mesajı işliyorduk. Duplicate oranımız normalde binde 7 civarındaydı; o gece yüzde 11'e fırlamıştı. Aynı payload, aynı cihaz, aynı zaman damgası — ama 340 kopya. İlk refleksim herkesinki gibiydi: "Broker'da bir şey patlamış olmalı."

Patlamamıştı. Broker tam olarak kendisinden isteneni yapıyordu. Sorun, iki yıl önce bir toplantı odasında verilmiş ve kimsenin sorgulamadığı bir karardı: "Veri kaybetmeyelim, her şeyi QoS 1 yapalım."

QoS 1'in Küçük Harflerle Yazılmış Sözleşmesi

MQTT'nin QoS seviyeleri kağıt üzerinde basit görünür. QoS 0 "at ve unut", QoS 1 "en az bir kez", QoS 2 "tam olarak bir kez". Mühendislerin çoğu bu üçlüyü bir kalite skalası gibi okur: 0 kötü, 1 idare eder, 2 mükemmel. Oysa QoS 1'deki o "en az" ifadesi bir garanti değil, bir tehdittir. Protokol size açıkça diyor ki: ağ kötüleştiğinde ben bu mesajı iki kez, beş kez, gerekirse 340 kez teslim edeceğim ve bu benim hatam sayılmayacak.

O gece olan şuydu: Ege kırsalındaki bir GSM baz istasyonu bakıma alınmış, bölgedeki 600 küsur cihazımız 2G'ye düşmüştü. Cihaz PUBLISH gönderiyor, mesaj broker'a ulaşıyor, broker veriyi işleyip PUBACK dönüyor — ama PUBACK zayıf bağlantıda kayboluyordu. Cihazın firmware'i PUBACK görmeyince mesajı "teslim edilmedi" sayıp yeniden gönderiyordu. Retry aralığımız sabit 8 saniyeydi ve deneme üst sınırı yoktu. Bağlantı 45 dakika boyunca yarı canlı kaldığında matematik kendini yazıyordu: veri her seferinde ulaşıyor, onay hiç dönmüyor, sayaç 340'a tırmanıyordu.

Yanlış Hipotezle Kaybedilen İki Gün

İtiraf edeyim, ilk iki günü broker'ı suçlayarak harcadık. EMQX loglarını didik didik ettik, inflight window ayarlarıyla oynadık, hatta broker sürümünü yükseltmeyi bile konuştuk. Ekipten bir arkadaş "belki load balancer TCP bağlantısını sessizce kesiyor, broker PUBACK'i ölü sokete yazıyordur" hipotezini ortaya attı; bu kulağa o kadar makul geliyordu ki bir gün de onu araştırdık. LB idle timeout'u 300 saniyeydi, keepalive'ımız 60 — orada sorun yoktu.

Gerçeği bize gösteren şey, tek bir cihazın modem loglarını saat saat MQTT paket dökümüyle yan yana koymak oldu. Cihaz tarafında tcpdump alamıyorduk ama firmware'e geçici bir "gönderim sayacı" telemetrisi ekledik. Sonuç netti: cihaz 340 kez göndermiş, broker 340 kez almış, cihaz 0 kez PUBACK görmüştü. Asimetrik link kalitesi — uplink çalışıyor, downlink çöp. 2G'de bu sandığınızdan çok daha sık yaşanır. Cihaz yalan söylemiyordu; sadece kendisine söylenmeyen bir gerçek vardı.

Persistent Session'ın İkinci Bıçağı

Duplicate hikayesi bununla bitmedi. Cihazlarımız persistent session kullanıyordu (Clean Session = false), çünkü komut kanalında cihaz offline'ken gönderilen konfigürasyon mesajlarının kaybolmamasını istiyorduk. Mantıklı. Ama baz istasyonu bakımı bittiğinde 600 cihaz 4 dakikalık bir pencerede yeniden bağlandı ve broker her birinin session'ında bekleyen inflight mesajları DUP bayrağıyla yeniden akıtmaya başladı. Reconnect fırtınası ile redelivery fırtınası üst üste bindi; broker'ın çıkış kuyruğu saniyede 9 bin mesaja vurdu, backend consumer'larımız 11 dakika lag yedi.

Bu noktada QoS 2'yi ciddi ciddi konuştuk. Kağıt üzerinde çözümdü: dört yollu el sıkışma, garanti tekillik. Sahada ise felaketti. Testlerde QoS 2, 2G bağlantıda mesaj başına ortalama 2,8 saniye ek gecikme ve yüzde 60 daha fazla radyo uyanıklığı getirdi — pil ömründen yıllık yüzde 8-9 çalıyordu. Üstelik PUBREL kaybolduğunda mesaj iki taraf için de belirsizlik bölgesinde asılı kalıyordu. 10 bin cihazlık filoda QoS 2, çözdüğü sorundan büyük bir sorun yaratıyordu.

Doğru Soru: Bu Mesaj İki Kez Gelirse Ne Bozulur?

Sonunda masaya doğru soruyu koyduk. "Hangi QoS daha iyi?" değil; "bu mesaj iki kez işlenirse ne bozulur, hiç gelmezse ne bozulur?" Topic'lerimizi bu iki soruya göre üç sınıfa ayırdık ve her sınıfa ayrı sözleşme yazdık.

Anlık durum verisi — sıcaklık, sinyal gücü, batarya — QoS 0'a indi. Bu veriler zaten 30 saniyede bir yenileniyor; bir tanesinin kaybolması hiçbir şeyi değiştirmiyor, iki kez gelmesi de öyle. Bu tek değişiklik toplam trafiğin yüzde 55'ini retry mekanizmasının dışına çıkardı ve broker CPU'sunu yüzde 30 rahatlattı.

Sayaç okumaları ve alarmlar QoS 1'de kaldı, ama tek bir şartla: uçtan uca idempotency. Her mesaja cihaz tarafında üretilen monoton bir sequence numarası ve cihaz kimliğiyle birleşen bir tekillik anahtarı ekledik. Backend'de ingestion katmanı bu anahtarı Redis'te 24 saatlik pencerede tutuyor; mükerrer geleni sayacı artırıp sessizce düşürüyor. Duplicate oranı hâlâ binde 7 — ama artık kimsenin faturasına yansımıyor, sadece bir Grafana panelinde ağ sağlığı göstergesi olarak yaşıyor. Duplicate'i yok etmeye çalışmadık; onu ölçülebilir ve zararsız hale getirdik.

Komut kanalı ise QoS 1 + persistent session'da kaldı, fakat MQTT 5'in Session Expiry Interval özelliğiyle session ömrünü sonsuzdan 4 saate indirdik ve komutlara uygulama seviyesinde son geçerlilik zamanı koyduk. Üç gün offline kalmış bir cihazın açılır açılmaz bayat "röleyi kapat" komutu yemesi, bir enerji sisteminde şaka değil.

Retry Politikası da Bir Mimari Karardır

Firmware tarafında retry stratejisini de elden geçirdik: sabit 8 saniye yerine 8-16-32-64 saniyelik exponential backoff, üzerine artı eksi yüzde 25 jitter, toplamda en fazla 6 deneme. Denemeler tükenirse mesaj cihazın yerel kuyruğuna düşüyor ve bağlantı sağlıklı bir PINGRESP gördükten sonra toplu senkronizasyonla gidiyor. Reconnect fırtınalarını yumuşatmak için yeniden bağlanma gecikmesine cihaz ID'sinden türetilen deterministik bir jitter ekledik; 600 cihaz artık 4 dakikada değil, 20 dakikaya yayılarak dönüyor. Broker'daki reconnect tepe değeri saniyede 41 bağlantıdan 9'a indi.

Bu değişikliklerin tamamı yayına alındıktan üç ay sonra aynı bölgede yine bir baz istasyonu bakımı yaşandı. Bu kez telefonum çalmadı; sabah dashboard'a baktığımda sadece duplicate göstergesinin birkaç saatliğine sarıya döndüğünü ve kendi kendine yeşile indiğini gördüm. Olay, olay olmaktan çıkmıştı.

Bugün birine MQTT mimarisi kurarken tek bir tavsiye verecek olsam şu olurdu: QoS'u bir kalite ayarı gibi değil, her topic için ayrı ayrı imzalanan bir sözleşme gibi düşünün ve o sözleşmenin bedelini — pil, bant genişliği, duplicate, gecikme — masaya açıkça yazın. Sahada binlerce cihazla benzer bir dertle boğuşuyorsanız ve "her şeyi QoS 1 yaptık, garip şeyler oluyor" cümlesi size tanıdık geliyorsa bana yazın; yaşadığınız şeyin log dosyasındaki karşılığını muhtemelen dakikalar içinde söyleyebilirim, çünkü aynı satırlara ben de bakakalmıştım.

Cihazlar yalan söylemez. Protokolün küçük harflerini okumayan bizlere sadece imzaladığımız sözleşmeyi hatırlatırlar — genellikle sabaha karşı ve 340 kopya halinde.

📅 Yayınlanma:  ·  Yakup Zengin