Ücretsiz3 dakikalık yazılım sağlık testi: önce neyi düzeltmeniz gerektiğini görün
Blog

Neden Rust? 1 GB RAM'li bir Orange Pi'nin bana öğrettikleri

3 dk okuma
  • Rust
  • IoT
  • Node.js
Seri: Giriş · 1/13 bölüm yayındaRust’ı öğreniyorumTüm bölümler
İçindekiler

Rust, yıllardır başlamak isteyip yoğun çalışma temposu yüzünden bir türlü vakit ayıramadığım bir dildi. Sahada bir Orange Pi PC Plus üzerinde yaşadığım bellek sorunu, bu isteği artık ertelenemez hale getirdi.

Problem

Sahadaki cihazlardan gelen sensör verisini MQTT üzerinden toplayan servislerimiz vardı. Bu servisler Orange Pi PC Plus üzerinde Node.js ile çalışıyordu. Kartın 1 GB belleği var ve bu bellek işletim sistemi, MQTT broker’ı ve kendi servislerimiz arasında paylaşılıyor.

İlk günlerde her şey yolundaydı. Sonra bir desen fark ettim: servislerin bellek kullanımı saatler içinde yavaş yavaş artıyor, hiç geri düşmüyordu. Node bellek sızdırıyordu. 1 GB’lık bir kartta bunun sonu belli: bellek dolar, sistem yavaşlar, en sonunda servis ya da cihazın kendisi yanıt vermez hale gelir.

İlk tepkim: yeniden başlat

İlk çözümüm, birçok kişinin yapacağı şeydi. Servisi systemd’ye bağladım ve Restart=always ile çökünce kendini yeniden başlatmasını sağladım. Bunu daha önce systemd yazımda da anlatmıştım.

Bu bir çözüm değil, bir yara bandıydı. Servis her yeniden başladığında birkaç saniyelik bir boşluk oluşuyor, o arada gelen veri kayboluyordu. Sorunu çözmemiştim, sadece saklamıştım.

Asıl mesele

Bir noktada şunu kabul ettim: sorun tek bir hatalı satır değildi. Çöp toplayıcıya (garbage collector) ve onun ne zaman çalışacağına güvenen bir çalışma ortamını, belleğin bu kadar kıt olduğu bir cihazda koşturuyordum. Sunucuda fark edilmeyecek bir sızıntı, 1 GB’lık kartta günler içinde sistemi düşürüyordu.

Benim ihtiyacım olan şuydu:

  • Bellek kullanımı öngörülebilir olmalıydı.
  • Sızıntılar çalışma anında değil, mümkünse daha kod yazarken yakalanmalıydı.
  • Yanında koca bir çalışma ortamı taşımayan, küçük ve tek bir çalıştırılabilir dosya üretmeliydi.

Neden Rust?

C ya da C++ ile de bu kontrolü elde edebilirdim. Ama o zaman bellek güvenliğinin yükü tamamen bana kalırdı. Sızıntıdan kaçarken bu sefer de taşmalar ve geçersiz bellek erişimleriyle uğraşacaktım.

Rust ise iki dünyanın arasında duruyor. Çöp toplayıcı yok; belleğin ne zaman ayrılıp ne zaman bırakılacağı belli. Sahiplik (ownership) kuralları sayesinde bu belirlilik derleyici tarafından denetleniyor. Yani hatalarımın büyük kısmını sahada, gece yarısı çöken bir cihazda değil, daha derleme aşamasında görüyorum.

Go’yu da düşündüm

Rust’a karar vermeden önce Go’yu da ciddi ciddi merak ettim. Go da tek bir çalıştırılabilir dosyaya derleniyor, öğrenmesi kolay ve sunucu tarafında çok yaygın. Ama kararımı merakla değil, verilere bakarak verdim. Değerlendirdiğim diller arasında (Node.js, Go ve Rust) en performanslısı açık ara Rust’tı:

  • Hız ve enerji: Minho Üniversitesi’nden Pereira ve arkadaşlarının 27 programlama dilini aynı 10 problem üzerinde karşılaştırdığı çalışmada, en hızlı dil olan C’ye göre oranlanmış çalışma süresi Rust’ta 1,04, Go’da 2,83, JavaScript’te 6,52. Enerji tüketiminde de sıra aynı: Rust 1,03, Go 3,23, JavaScript 4,45. Yani Rust, C ile neredeyse aynı seviyede ve bu ölçümde Go’dan yaklaşık 2,7 kat hızlı.
  • Güncel ölçümler: The Computer Language Benchmarks Game’in Rust 1.84 ile Go 1.23’ü karşılaştıran sonuçlarında, listelenen 10 problemin hepsinde en hızlı Rust programı en hızlı Go programından önce bitiyor. Örneğin mandelbrot’ta 0,95 saniyeye karşı 3,77 saniye, spectral-norm’da 0,72 saniyeye karşı 1,43 saniye.
  • Çöp toplayıcı: Go’nun bir çöp toplayıcısı var, Rust’ın yok. Discord, 2020’de “Read States” servisini tam da bu yüzden Go’dan Rust’a taşıdı: Go’nun çöp toplayıcısı iki dakikada bir zorunlu olarak çalışıyor ve gecikmede düzenli sıçramalara yol açıyordu. Rust ile yeniden yazılan serviste bu sıçramalar ortadan kalktı.

Hakkını vermek gerekirse Go her ölçüde geride değil. Aynı çalışmada bellek kullanımında Go (1,05), Rust’tan (1,54) daha iyi sonuç veriyor. Ama benim cihazımdaki sorun toplam bellek miktarından çok, belleğin ne zaman serbest bırakılacağının belirsizliğiydi. Çöp toplayıcısı olmayan ve hızda C ile aynı ligde olan Rust, bu yüzden benim ihtiyacıma daha uygundu.

Sonuç

Bu deneyim bana şunu öğretti: doğru araç, sorunun yaşandığı ortama göre seçilir. Node.js hâlâ birçok iş için harika bir araç. Ama belleğin her megabaytının önemli olduğu bir uç cihazda, belirsizliği değil kontrolü seçmem gerekiyordu. Rust yıllardır listemdeydi; sahada yaşadığım bu sorun, ona nihayet başlamamı sağladı.

Kaynaklar

İlgili yazılar