Yazılım Geliştirme

Rust’ta Actor Modeli: Tokio ve Kanallarla Pratik Kurulum

Bunu yaşayan biri olarak söyleyeyim, Geçen kış, Şubat 2025’te İstanbul’daki bir fintech ekibiyle konuşurken aynı cümleyi üç ayrı kişiden duydum: “Mutex yüzünden saç baş yoluyoruz.” Haklıydılar. İşin aslı şu ki, eşzamanlı sistemlerde mesele çoğu zaman hız değil; kimin neyi, ne zaman değiştirdiği. Rust burada bayağı iyi bir kapı açıyor. Actor modeli de tam bu noktada devreye giriyor: durumu paylaşmak yerine mesajla konuşuyorsun. Daha az kavga, daha az kilit, daha az sürpriz.

Ben bu yaklaşımı ilk kez 2023’te kendi log işleme denememde test etmiştim. Küçük bir servis düşünün; gelen olaylar bazen fena hâlde artıyor, bazen sakinleşiyor. Başta klasik yöntemle gittim: ortak state, birkaç mutex, biraz da “idare eder” diye düşündüğüm optimizasyonlar… Sonuç? Gayet tanıdık. Gecikme sıçradı, hata ayıklama uzadı, ekip birbirine baktı. Sonra actor modeline geçince tablo değişti; her parça kendi alanında çalıştı, veri akışı da daha temiz oldu.

💡 Bilgi: Actor modelinde temel fikir basit: durum tek elde kalır, dış dünya o duruma doğrudan dokunmaz; bunun yerine mesaj yollar. Böylelikle race condition riski ciddi biçimde azalır.

Neden Actor Modeli Hâlâ Bu Kadar Işe Yarıyor?

İnanın, Bakın şimdi, actor modelini teorik bir akademik oyuncak gibi görmek kolay. Ama pratikte çok net bir derdi çözüyor: paylaşılan mutable state’i ortadan kaldırıyor. Yani iki iş parçacığı aynı veriye aynı anda abanıp sistemi dağıtmıyor. Rust’ın sahiplik kurallarıyla birleşince bu yapı bayağı sağlam duruyor.

Bir de şu var: actor yaklaşımı sadece güvenlik değil, mimarı temizlik de veriyor. Her actor kendi küçük dünyasında yaşıyor; log işliyor olabilir, ödeme kuyruğu yönetiyor olabilir ya da IoT sensörlerinden veri topluyor olabilir. Hani mutfakta herkes aynı tencereye kaşık sokmasın diye ayrı kaplar kullanırsınız ya — mantık biraz öyle.

Şöyle söyleyeyim, Küçük startup tarafında bu yaklaşım hızlı deneme yapmayı kolaylaştırıyor. Enterprise seviyede işe asıl avantaj gözle görünür hâle geliyor: servisler arası sınırlar netleşiyor, izleme kolaylaşıyor ve tek bir hatalı işlem bütün sistemi sürüklemiyor. E tabi her şey güllük gülistanlık değil; mesaj tasarımını kötü yaparsanız sisteminiz “esnek” değil “karışık” ölür.

Paylaşılmış state yerine mesaj geçmek

Aslında — dür bir saniye, önce şunu söyleyeyim — paylaşılmış state tamamen kötü değil. Ama büyüdükçe maliyeti artıyor. Mesajlaşma işe işi biraz yavaşlatabilir ama karşılığında kontrol veriyor. Hele bir de yüksek trafikli backend’lerde bu takas çoğu zaman değerli oluyor.

Açıkçası, Ben bunu 2024 sonbaharında Ankara’da bir SaaS projesinde tekrar gördüm. Takımda iki kişi aynı cache katmanına dokununca debug süresi uzuyordu; actor tabanlı küçük bir kuyruk katmanı ekledikten sonra sorunların yarısı kendiliğinden kayboldu desem abartmış olmam.

Mesajları Net Tanımlamak Neden Önemli?

Actor modelinin bel kemiği mesaj tipleri. Mesaj ne kadar açık olursa sistem o kadar okunur oluyor. Rust burada zaten doğal olarak yardım ediyor çünkü enum ve struct ile kontratı sert biçimde tanımlıyorsunuz.

#[derive(Debug, Clone)]
pub enum LogEvent {
Warning { message: String },
Error { code: u32, message: String },
}
pub struct ProcessAck {
pub id: u64,
pub status: String,
}

Bu örnekte güzel olan şey şu: gönderen taraf ne yolladığını biliyor, alan taraf da ne beklediğini biliyor. “Acaba burada string mi geliyordu integer mı?” sorusu ortadan kalkıyor veya en azından derleme aşamasında yakalanıyor.

Yaklaşım Artısı Eksiği
Müşterek mutable state + mutex Daha direkt görünür Kilit karmaşası ve deadlock riski var
Actor + message passing Daha izole ve güvenli Tasarlaması ilk başta biraz disiplin istiyor
Kanal tabanlı hibrit yapı Trafik kontrolü iyi ölür Aşırı kuyrukta gecikme yaratabilir

Neyse uzatmayalım; mesele sadece tıp güvenliği değil. Mesajları düzgün kurarsanız ileride yeni event türleri eklemek de kolaylaşıyor. Kötü kurarsanız her yeni alan için kodun yarısını elden geçirmeniz gerekir… işte o kısmı pek eğlenceli değil. Bu konuyla ilgili C++ Neden Hâlâ Ayakta? Stroustrup’tan Gelen Dersler yazımıza da göz atmanızı tavsiye ederim.

Tokio Kanallariyla Akışı Kurmak

Tokio’nün mpsc kanalı bu iş için gayet uygun bir omurga sağlıyor çünkü üretici ile tüketiciyi ayırıyorsunuz. Gönderen taraf mesaj basıyor, alıcı taraf kendi ritminde işliyor. Bounded channel kullanınca da backpressure devreye giriyor; yani sistem “dür bakalım biraz yavaşla” diyebiliyor.

Çok konuştum, örnekle göstereyim. Bu konuyla ilgili MCP Ekosistemi Nereye Gidiyor: Hız, Açıklar, Güvenlik yazımıza da göz atmanızı tavsiye ederim.

let (sender, mut receiver) = mpsc::channel(256);
let actor = LogActor {
id: 1,
buffer: Vec::new(),
};
let worker_handle = tokio::spawn(async move {
while let Some(msg) = receiver.recv().await {
let _ = actor.handle(msg).await;
}
});

Bunu ilk kez canlıya yakın ortamda denediğimde fark ettiğim şey şu oldu: kanal dolunca uygulama çökmedi; sadece baskıyı geri itti. Bu küçük detay bile operasyonel tarafta büyük rahatlık veriyor. Kısacası queue taşarsa sistem size bağırmadan önce fren yapabiliyorsunuz. dissectml: EDA ile AutoML Arasındaki Eksik Parça yazımızda bu konuya da değinmiştik.

Nerede iyi çalışır?

Araya gireyim: Küçük projelerde log toplama, e-posta kuyruğu ya da arka plan job’ları için çok uygun.

Orta ölçekte işe ödeme öncesi doğrulama adımları veya telemetri toplama gibi parçalarda iyi sonuç veriyor.

Enterprise tarafta da servis içi orkestrasyon için kullanılabiliyor. Orada observability şart; yoksa aktörler konuşuyor ama siz duymuyorsunuz.
İşte o zaman sıkıntı başlıyor. OpenAI’dan Güvenlik İçin Yeni Hamle: Fellowship Programı Ne Anlatıyor? yazımızda da bu konuya değinmiştik. AI Ajanlar Neden Yalan Söyler: Asıl Ders Ne? yazımızda da bu konuya değinmiştik.

Aktorun Ic Dünyası Nasıl Görünüyor?

Aktör dediğimiz şey aslında küçük bir kapsül gibi düşünebilirsiniz.

Kendi buffer’ı var mı? Var.
Kendi kimliği var mı? Var.
Dışarıdan gelip kafasına göre state değiştiren biri var mı? Yok.
Tam olarak güzelliği burada yatıyor zaten.

İyi tasarlanmış bir actor sistemi size hızdan önce düzen verir; düzen geldikten sonra hız zaten çoğu zaman arkadan geliyor!

Aslında, handle fonksiyonunda gelen event’e göre davranış değiştiriyorsunuz.

Warning gelirse yazdırıyorsunuz.
Error gelirse başka yol izliyorsunuz.
Bazı event’leri de direkt ignore edebiliyorsunuz.
Basit görünüyor ama ölçeklenebilirlik hissini veren tam da bu sadelik oluyor.
Biraz LEGO gibi (eh, fena değil). parçalar net olunca kombinasyon artıyor.

Kilit yoksa sorun biter mi?

Açık konuşayım:
Hayır. Her problem sihirli biçimde çözülmüyor — Mesela yanlış boyutlandırılmış kanal kapasitesi gecikme yaratabilir. Ya da fazla sayıda actor açarsanız scheduler üstünde gereksiz yük oluşabilir. Yani özgürlük geliyor ama bedava değil. Bence en can alıcı nokta burası:

  • Mesaj formatını erken sabitleyin ama aşırı katı olmayın.
  • Kanal kapasitesini trafik profiline göre ayarlayın.
  • Error path’i baştan düşünün; yoksa sonradan yamamak zor ölür.
  • Loglama ve metrikleri unutmayın çünkü async dünyada sessizlik bazen kandırıcıdır. (bu kritik)

Küçük Startup ile Büyük Kurum Aynı Şeyi Mi Yapar?

Küçük startup tarafında hedef genelde hızlı çıkmak oluyor. Burada actor modeli çok şatafatlı tasarlanmaz; sade tutulur. Örneğin tek bir ingest servisi içinde birkaç worker aktörü yeterli. Hatta bazen fazla soyutlama yapmak zarar bile verebilir çünkü ekip küçüktür ve herkesin zihinsel yükü sınırlıdır. Bir startup’ta ben olsam önce kritik dar boğazları actor’a taşırım…

Büyük kurumda işe tablo değişir.

Orada asıl mesele ekiplerin birbirine çarpmaması ölür.

Actor sınırları servis sınırlarına benzer şekilde çizilir. Bağımlılıklar kontrollü gider.

Kurumsal projede geçen yıl gördüğüm örnekte mesela 14 mikroservis arasındaki kuyruk akışı tek merkezden yönetiliyordu; yarısı zaten gereksiz karmaşıktı.

Actor yaklaşımı o karmaşayı azaltmak için fena değildi ama tasarım disiplini istiyordu.

Gel gelelim her şeyi actor’a çevirmek de doğru değil; bazı işler düz fonksiyonlarla daha temiz kalıyor.”

Sahada Karşılığı Olan Artılar Ve Eksiler

” />

Sıkça Sorulan Sorular

Tokio ile Rust’ta actor modeli nasıl kurulur?

Tokio’da actor modelini genelde “actor başına bir task + actorların arası için kanal (mpsc)” şeklinde kurarsın. Her actor mesaj kuyruğundan gelen istekleri sırayla işler; böylece paylaşılan mutable state ihtiyacı azalır. Ben ilk denemelerimde hâlâ “biraz mutex eklerim” demiştim, ama sonra asıl faydanın mesajlaşmayı disiplinli tasarlayınca ortaya çıktığını gördüm.

Actor modelinde Mutex kullanmak yine de gerekli mi?

Çoğu senaryoda hayır; actor içinde durum tek bir yerde tutulduğu için race condition riski belirgin şekilde düşer. Yine de bazı bileşenlerde (ör. paylaşılan cache, global registry, dış kütüphaneler) sınırlı mutex kullanımı kaçınılmaz olabiliyor. Benim pratikte “actor sınırlarını net çizeyim” yaklaşımı, mutex ihtiyacını en çok azaltan şey oldu.

Mesaj tasarımı neden bu kadar kritik?

Actor modelinin bel kemiği mesaj tipleridir; mesajlar net değilse sistem hızlıca “karışık” hale gelir. İyi mesaj tasarımı, aktörler arası sorumlulukları ve akışın hangi garantilerle ilerlediğini görünür kılar. Kötü tasarımda ise kuyruk şişmesi, beklenmedik gecikmeler ve zor debug gibi sorunlar artar.

Yüksek trafikte actor modeli performans sorunları çıkarır mı?

Mesajlaşma her zaman sıfır maliyet getirmez; bazı durumlarda throughput düşebilir. Ama paylaşılan kilitler ve karmaşık senkronizasyon yüzünden yaşanan gecikmelerle kıyaslayınca actor yaklaşımı çoğu sistemde daha öngörülebilir olur. Özellikle yüksek trafikte, doğru kanal boyutu (buffer), backpressure ve actor başına iş döngüsünü iyi ayarlamak fark yaratır.

Backpressure (geri basınç) actor modelinde nasıl yönetilir?

Kanalın buffer boyutu ve gönderme stratejisi backpressure’ın temelidir. Örneğin buffer dolduğunda gönderimi bekletmek mi, hata döndürmek mi yoksa düşürmek mi istediğine karar vermen gerekir. Ben genelde “önce sistemin dayaniklılığını ölç, sonra stratejiyi netleştir” şeklinde ilerliyorum; aksi halde kuyruk taşması geç fark ediliyor.

Kaynaklar ve İleri Okuma

Microsoft: Actor Model (mimari desen)

Tokio Tutorial (task, kanallar ve asenkron temel kavramlar)

Tokio mpsc Kanalları (resmi API dokümantasyonu)

tokio-rs/mini-redis (Tokio + kanal/task pratik örnek)

Aşkın KILIÇ

20+ yıl deneyimli Azure Solutions Architect. Microsoft sertifikalı bulut mimari ve DevOps danışmanı. Azure, yapay zekâ ve bulut teknolojileri üzerine Türkçe teknik içerikler üretiyor.

AZ-305AZ-104AZ-500AZ-400DP-203AI-102

Bu içerik işinize yaradı mı?

Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.

← Onceki Yazi
iPhone Air 2 Eylül Sürprizi mi? Apple’ın Planı Karışıyor
Sonraki Yazi →
SaaS İçin API Anahtarları: Güvenli Kurulum Rehberi

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

İçindekiler
← iPhone Air 2 Eylül Sürprizi mi...
SaaS İçin API Anahtarları: Güv... →