Temmuz ortasında, saha operasyon sorumlumuz odama elinde telefonla girdi: "Manisa'daki pompa istasyonundaki üç cihaz gece yarısından beri sessiz. Teknisyen gitti, güç ledi yanıyor ama cihaz açılmıyor." Bir gece önce 2 bin 100 cihaza firmware 3.4.0 göndermiştik. Üçü Manisa'da, on yedisi başka şehirlerde olmak üzere toplam 20 cihaz güncellemeden çıkamamıştı. Yüzde 99'luk başarı oranı sunumlarda güzel durur; sahada ise 20 cihaz demek, 20 araç, 20 teknisyen ziyareti, cihaz başına ortalama 1.400 TL yol ve iş gücü maliyeti, artı müşteriye "verileriniz üç gün eksik" telefonu demek. O sabah OTA'nın istatistik değil, kabus yönetimi işi olduğunu bir kez daha hatırladım.
Tuğlanın Anatomisi
Ölen cihazları depoya getirtip JTAG ile açtığımızda tablo netleşti: hepsinde yeni imaj flash'a eksik yazılmıştı. İndirme sırasında değil — indirme SHA-256 ile doğrulanıyordu — yazma sırasında. Bu 20 cihazın ortak noktası, sahadaki en eski donanım revizyonu olmaları ve o revizyonda güç regülatörünün marjının dar olmasıydı. Flash yazma akımı tepe yaptığı anda GSM modemi de veri gönderiyorsa besleme geriliminde 150 milivoltluk bir çukur oluşuyor, mikrodenetleyici brown-out resetine giriyordu. Yazma yarıda kalıyor, cihaz açılışta geçersiz imajı bulup duruyordu.
İlk hipotezimiz elbette bu değildi. İki gün boyunca "bozuk imaj dağıtıldı" ihtimalini kovaladık; CDN'deki dosyayı bayt bayt karşılaştırdık, aynıydı. Sonra "flash ömrü doldu" dedik; wear seviyelerine baktık, normaldi. Gerçek sebebi bulmamızı sağlayan şey, ölen cihazların hepsinde son log satırının modem TX olayıyla çakışmasıydı. Firmware güncellenirken telemetri göndermeye devam ediyorduk — çünkü kimse "güncelleme sırasında sus" demeyi akıl etmemişti. Çözümün bir satırı buydu: yazma fazında modemi uykuya al.
A/B Şeması Kağıtta Vardı, Zihniyette Yoktu
Asıl utanç verici kısım şu: cihazlarımızda A/B partition şeması zaten vardı. İki imaj yuvası, bootloader'da yuva seçme mantığı — hepsi yerindeydi. Peki 20 cihaz nasıl tuğlalaştı? Çünkü A/B'yi disk alanı pahalı diye "kısmi" uygulamıştık: uygulama imajı çift kopyalıydı ama bootloader'ın okuduğu boot flags bölgesi tekti ve yeni imaj doğrulanmadan önce güncelleniyordu. Brown-out tam o pencerede gelince bootloader, yarım yazılmış B yuvasını "geçerli" sanıp ona atlıyor, açılamayınca da A'ya dönmeyi bilmiyordu. A/B şemasının bütün değeri, geçiş anının atomikliğindedir; biz atomikliği delmiştik ve bunu bize ancak saha söyleyebildi.
Yeniden tasarımda üç kural koyduk. Bir: boot flag yazımı tek sektörlük, checksum'lı, çift kopyalı ve en son yapılan işlem. İki: yeni imaja geçen cihaz, kendini "deneme modunda" işaretler; açılıştan sonra 10 dakika içinde buluta bağlanıp sağlık raporu veremezse donanım watchdog'u cihazı resetler ve bootloader otomatik olarak eski yuvaya döner. Üç: hiçbir koşulda bootloader'ın kendisi OTA ile güncellenmez; onun için ayrı, imzalı ve fabrikada test edilen bir prosedür var. Bu üçlüye ekipte "pişman olmama protokolü" diyoruz.
Yüzde 1'i Sahada Değil, Dağıtım Planında Yakalamak
Teknik rollback mekanizması işin yarısı. Diğer yarısı, hatalı bir imajın 2 bin cihaza birden gitmesini baştan engellemek. 3.4.0 vakasından önce dağıtımlarımız iki aşamalıydı: 50 cihazlık pilot, sonra herkes. Pilotta o eski donanım revizyonundan tek cihaz yoktu — pilot grubunu "kolay ulaşılır" cihazlardan seçmiştik, İzmir merkezdeki güçlü şebekeli, yeni revizyon cihazlardan. Yani pilotumuz, filoyu temsil etmiyordu; sadece en iyi senaryoyu test ediyordu.
Artık dağıtım halkaları donanım revizyonu, şebeke operatörü ve sinyal kalitesi kırılımlarını zorunlu olarak kapsıyor. İlk halka 25 cihaz ve içinde her revizyondan, her operatörden ve "kötü şebeke" etiketli bölgelerden en az iki cihaz var. İkinci halka yüzde 5, üçüncüsü yüzde 25, sonra kalan herkes. Halkalar arası geçiş otomatik ama şartlı: güncellenen grupta açılış başarısı yüzde 99,5'in altına düşerse veya telemetri sessizliği 30 dakikayı aşan cihaz oranı binde 5'i geçerse dağıtım kendini durduruyor ve Slack'e alarm düşüyor. Bu eşikleri Grafana'da izleyen panele ekip içinde "kalp monitörü" deniyor; dağıtım günlerinde herkesin ikinci ekranında o açık durur.
Bir de kimsenin konuşmayı sevmediği konu var: delta güncellemeler. Tam imajımız 1,9 MB; 2G'deki bir cihaz bunu ortalama 11 dakikada, kesintili hatlarda bazen 40 dakikada indiriyordu ve her kesinti baştan başlamak demekti. HTTP range ile kaldığı yerden devam etmeyi zaten yapıyorduk ama asıl kazanım binary diff'e geçmek oldu: sürümler arası tipik delta 140-300 KB'a indi, indirme süresi ortalama 90 saniyeye düştü, aylık veri maliyetimizden de hatırı sayılır bir kalem silindi. Bedeli, delta üretim zincirinin kendisinin de test edilmesi gereken bir yazılım olması — yanlış taban sürümüne uygulanan delta, bozuk imajın en sinsi kaynağıdır. Bu yüzden delta paketinin başlığında hem taban hem hedef imajın hash'i taşınır ve cihaz ikisini de doğrulamadan tek bayt yazmaz.
İmza konusunu da es geçmeyeyim, çünkü rollback mekanizması kadar kritik: cihazlar yalnızca bizim özel anahtarımızla imzalanmış imajları kabul ediyor ve doğrulama, indirme bittiğinde değil yazmadan önce ve boot sırasında iki kez yapılıyor. İmza anahtarı HSM'de, imzalama işlemi CI pipeline'ının insan onayı gerektiren son adımında. Bir keresinde stajyer bir arkadaş test imajını yanlışlıkla üretim dağıtım kanalına yükledi; imaj test anahtarıyla imzalı olduğu için sahadaki tek bir cihaz bile onu yutmadı ve olay, felaket yerine bir onboarding hikayesine dönüştü. Güvenlik zinciri, en yorgun olduğunuz gün sizi kendi elinizden korumak için vardır.
Korkunun Doğru Dozu
3.4.0'dan sonraki 18 ayda 31 OTA dağıtımı yaptık; toplam 60 binin üzerinde güncelleme işlemi, sıfır tuğla. Ama asıl değişen rakam şu: dağıtım öncesi ekipteki gerginlik. Eskiden OTA günü kimse izin almazdı, telefonlar açık uyunurdu. Şimdi 3.7.2'yi dağıttığımız gün ben toplantıdaydım ve haberim akşam özet mailinden oldu. Korku yok olmadı — olmamalı da. Sadece yerini değiştirdi: artık dağıtım gecesi değil, tasarım toplantısında yaşanıyor. "Bu değişiklik brown-out anında ne yapar?" sorusu her firmware PR şablonunda yazılı bir madde.
OTA konusunda öğrendiğim en kalıcı ders şu oldu: güncelleme sistemi, mutlu gün senaryosuna göre değil, elektriğin en kötü anda gitmesine göre tasarlanır. Cihaz sahada güç kaybedecek, şebeke kesilecek, kullanıcı tam yazma sırasında fişi çekecek — bunlar istisna değil, istatistiksel kesinlik. 10 bin cihazda binde birlik bir ihtimal, her dağıtımda 10 ziyaret demektir. Kendi filonuzda OTA sürecini kuruyorsanız ya da "bir kere kötü tecrübe yaşadık, artık güncelleme yapmaya korkuyoruz" noktasındaysanız bana ulaşın; o korkuyla yaşamanın da, onu protokole dönüştürmenin de nasıl bir şey olduğunu yerinden anlatabilirim.
Manisa'daki üç cihazı bugün hâlâ depoda, masamın karşısındaki rafta tutuyorum. Yeni işe başlayan her gömülü yazılımcıya onları gösterip aynı cümleyi kuruyorum: "Bunlar bug değil, ders. Ve dersin saha bedeli 28 bin TL'ydi."