Modern backend sistemlerde “hızlı” olmak artık tek başına yeterli değil; ilk isteğe ne kadar hızlı
cevap verdiğiniz, ne kadar az bellekle ayağa kalktığınız ve bunu ne kadar öngörülebilir yaptığınız
da en az throughput kadar önemli. Özellikle serverless, kısa ömürlü container, edge worker ve
bursty internal servis senaryolarında problem genellikle steady-state throughput değil; cold start,
startup allocation ve operasyonel sadelik oluyor.
Anka bu probleme alışılmış ASP.NET Core yaklaşımının tersinden yaklaşıyor: middleware pipeline yok,
built-in routing yok, TLS yok, HTTP/2 yok. Bunun yerine .NET 8+ üzerinde, Native AOT dostu,
HTTP/1.x odaklı, allocation-disiplinli bir HTTP sunucu çekirdeği sunuyor. Amaç açık: süreç ayağa
kalktıktan sonra soketi milisaniyeler içinde dinlemeye almak ve connection/request yaşam döngüsünü
mümkün olduğunca deterministik tutmak.
Problem Tanımı
Klasik .NET web stack’i çok güçlü; ama bu gücün bir maliyeti var. Middleware zinciri,
reflection-heavy yüzey alanı, dinamik extensibility, geniş protokol desteği ve JIT warmup, cold
start tarafında ciddi bir vergi yaratıyor. Uygulamanızın gerçekten ihtiyacı sadece “socket aç,
HTTP/1.1 parse et, handler’ı çalıştır, cevabı yaz” ise bu genel amaçlı model gereğinden pahalı hale
gelebiliyor.
Anka’nın odaklandığı soru şu
Tam teşekküllü web framework’ü değil, sadece hızlı açılan, düşük bellekli, Native AOT ile uyumlu
bir HTTP listener istiyorsak minimum mimari ne olmalı?
Bu soruya verilen cevap performans sayılarında da kendini gösteriyor. Ölçülen bir ortamda Anka’nın
time-to-ready süresi yaklaşık 2.3 ms, startup allocation’ı 124.5 KB, steady-state RSS’i ise
yaklaşık 15 MB seviyesinde. Buna karşılık Kestrel benzer senaryoda daha geniş feature set’iyle
geliyor ama cold start ve bellek tarafında daha ağır bir profil çiziyor. İlginç olan nokta şu:
throughput tarafında fark dramatik değil; yani Anka’nın asıl avantajı “ham req/s” değil, başlangıç
maliyeti ve footprint.
Mimari: Minimum Katman, Maksimum Kontrol
Anka’nın mimarisi bilinçli şekilde dar tutuldu:
Server.StartAsync()
-> paralel accept loop’lar
-> Connection.RunAsync() (her TCP bağlantısı için bir task)
-> SocketReceiver.ReceiveAsync()
-> HttpParser
-> RequestHandler(request, response, ct)
-> HttpResponseWriter.WriteAsync()
Bu akışın önemli tarafı şu: sistemde “fazla soyutlama” yok. İstek işleme zinciri doğrudan socket
seviyesinden başlıyor ve doğrudan socket seviyesinde bitiyor.
Server: Kabul katmanı
Server, listening socket’i açıyor, backlog’u ayarlıyor ve tek accept loop yerine birden fazla
paralel accept loop çalıştırıyor. Bu karar küçük görünebilir ama burst traffic altında tek accept
döngüsünün bağlantı kabulünü serialize etmesini engelliyor.
Ek olarak thread pool minimumu yukarı çekilerek ilk yüklenme anındaki thread injection gecikmesi
azaltılıyor. Bu, benchmark tuning gibi görünebilir; ama aslında cold path davranışını deterministik
hale getiren operasyonel bir karar.
Connection: Gerçek çalışma birimi
Her TCP bağlantısı için bir Connection task’i oluşturuluyor. Bu nesne:
- pooled receive buffer kiralıyor,
- pooled HttpRequest alıyor,
- connection-scoped HttpResponseWriter yaratıyor,
- socket’ten veri okuyup parse ediyor,
- handler’ı çağırıyor,
- keep-alive ise aynı kaynaklarla bir sonraki isteğe geçiyor.
Buradaki kritik fikir şu: request bazlı değil, connection bazlı kaynak sahipliği. Böylece aynı
keep-alive bağlantısında tekrar eden istekler için yeniden buffer kiralama/iadeleri minimize
ediliyor.
SocketReceiver: Async I/O ama allocation disipliniyle
Anka, Socket.ReceiveAsync(Memory) gibi daha rahat yüzeyler yerine SocketAsyncEventArgs +
IValueTaskSource kullanıyor. Bunun sebebi mikro-optimizasyon merakı değil; senkron tamamlanan
socket okumalarında state machine ve ek allocation’lardan kaçınmak.
Özellikle loopback/LAN senaryolarında veri çoğu zaman kernel buffer’da zaten hazır oluyor. Bu
durumda receive çağrısı senkron tamamlanıyor ve sistem neredeyse düz fonksiyon çağrısı maliyetinde
ilerliyor. Veri hazır değilse OS I/O completion thread işi tamamlıyor; continuation ise
RunContinuationsAsynchronously = true ile thread pool’a post ediliyor. Sonuç: I/O thread’i request
işleme yükü altında bloke olmuyor.
HttpParser: Tek geçişte parse
Parser’ın asıl değeri “HTTP parse ediyor olması” değil; bunu tek geçişte ve pooled buffer
mantığıyla yapması.
- request line parse ediliyor,
- path/query ayrıştırılıyor,
- header isimleri ingest sırasında lowercase normalize ediliyor,
- Content-Length inline takip ediliyor,
- gerekiyorsa body okunuyor,
- chunked request body varsa decode edilip req.Body içine reassemble ediliyor,
- keep-alive kararı parse aşamasında hesaplanıyor.
Bu yaklaşım iki önemli kazanım getiriyor:
- İkinci bir header taraması gerekmiyor.
- Handler’a geçen HttpRequest, zaten kullanıma hazır ve mümkün olduğunca kopyasız bir model
oluyor. - HttpResponseWriter: framework değil, doğrudan wire format
Response katmanı da aynı felsefeyi izliyor. HttpResponseWriter, HTTP response header block’unu
pooled buffer üzerinde kuruyor, küçük body’leri mümkünse aynı buffer içine inline ediyor ve
doğrudan Socket.SendAsync() ile yazıyor.
Burada da önemli bir karar var: chunked response encoding yok. Tüm response’lar Content-Length ile
gidiyor. Bu kısıt, response pipeline’ını sadeleştiriyor; özellikle AOT ve düşük allocation hedefi
için kod yolunu daraltıyor.
Temel Tasarım Kararları
Tek RequestHandler delegesi
Anka’da middleware zinciri yok. Tek bir RequestHandler var. Bu bilinçli bir radikallik.
Kazancı
- çağrı zinciri kısalıyor,
- soyutlama katmanları azalıyor,
- handler dispatch maliyeti düşüyor,
- Native AOT için daha sade bir yüzey oluşuyor.
Bedeli
- çapraz kesen concern’leri sizin kurmanız gerekiyor,
- routing, auth, logging, metrics gibi konular framework tarafından organize edilmiyor.
Bu karar “eksik framework” değil; çekirdek primitive sağlama yaklaşımı.
Built-in routing yok
Path dispatch kullanıcı koduna bırakılıyor. Basit bir switch çoğu senaryo için yeterli kabul
ediliyor. Bu ilk bakışta ilkel gelebilir; ama routing’nin gerçek maliyeti sadece path eşleştirme
değildir. Yanında metadata sistemi, endpoint graph’i, convention set’i ve geniş bir API yüzeyi
gelir. Anka bu maliyeti baştan reddediyor.
Header isimlerini ingest sırasında lowercase’e çekmek
Case-insensitive header lookup’ı her sorguda yapmak yerine, isimler parse sırasında normalize
ediliyor. Böylece lookup tarafında düz SequenceEqual yeterli oluyor. Bu küçük bir tasarım tercihi
gibi görünür ama hot path’te branch ve iş yükünü azaltır.
HttpRequest ve buffer’ları connection boyunca tekrar kullanmak
HttpRequest.ResetForReuse() yaklaşımı, request nesnesini her istekten sonra çöpe atmıyor. Aynı
bağlantıda yeniden kullanıyor. Bu model özellikle keep-alive altında etkili; çünkü steady-state
allocation’ı düşüren şey çoğu zaman büyük optimizasyonlar değil, küçük ama sistematik yeniden
kullanım kararlarıdır.
CAS tabanlı küçük request pool
HttpRequestPool lock-free ve küçük tutulmuş. Amaç teorik “sınırsız pooling” değil; düşük
overhead’li, cache-friendly ve pratik bir reuse mekanizması. Tüm slotlar doluysa nesne discard
ediliyor. Bu da önemli bir mühendislik kararı: bazen her şeyi pool’lamaya çalışmak, hiç
pool’lamamaktan daha pahalıdır.
Trade-off’lar: Bu Mimari Neyi Kazandırıyor, Neyi Bilerek Vermiyor?
Anka’nın iyi yanı, ne olmadığını açıkça söylemesi.
| Karar | Kazanç | Bedel |
| Middleware yok | Daha kısa hot path, daha az soyutlama | Cross-cutting concern’leri developer kurar |
| Built-in routing yok | Daha düşük startup ve daha basit model | Büyük API’lerde ergonomi düşer |
| HTTP/1.x only | Kod yolu daralır, AOT yüzeyi sadeleşir | Modern protocol features yok |
| TLS built-in değil | Çekirdek sade kalır | Reverse proxy zorunlu hale gelir |
| Chunked response yok | Response writer basitleşir | Streaming ergonomisi sınırlanır |
| Raw socket yaklaşımı | Allocation ve davranışı üzerinde tam kontrol | Bakım maliyeti framework kullanımından yüksektir |
En kritik trade-off şu: Anka, Kestrel’in yerini almak için değil, farklı bir optimizasyon noktasını
sahiplenmek için var. Eğer ihtiyacınız HTTP/2, TLS termination, middleware ekosistemi, gelişmiş
hosting entegrasyonu, observability plug-in yüzeyi ise cevap zaten büyük ihtimalle Kestrel. Ama
ihtiyacınız düşük footprint, hızlı readiness ve son derece kontrollü bir request path ise Anka
mantıklı hale geliyor.
Bu Tasarımın En Güçlü Tarafı: Performans Değil, Öngörülebilirlik
Burada bence asıl değer yalnızca “0 B allocation” ya da “2.3 ms ready” değil. Asıl değer, sistemin
davranışını zihinde kolayca modelleyebilmek:
- her bağlantı için bir task,
- connection-scoped buffer’lar,
- parse → handle → write hattı,
- az sayıda hata durumu,
- sınırlı protocol yüzeyi.
Mimari seviyede baktığımızda bu tür sistemlerin değeri benchmark sunumundan çok
operasyonel açıklık tarafında ortaya çıkar. Sistem ne yapıyor, ne yapmıyor, yük altında nerede
davranış değiştiriyor, hangi noktalarda maliyet ödüyor bunlar nettir.
Örnek Kullanım Senaryoları
Serverless veya kısa ömürlü container API’leri
Cold start kritikse ve endpoint set’i küçükse, Anka’nın minimal açılış maliyeti ciddi avantaj
sağlar.
Sidecar / internal control-plane endpoint’leri
Health, readiness, metrics gateway veya basit control API’leri için tam framework çoğu zaman
gereksizdir.
Edge compute ve appliance tarzı dağıtımlar
Düşük RSS ve Native AOT uyumu, sınırlı kaynaklı ortamlarda önemli fark yaratır.
Benchmarking ve protokol araştırması
HTTP pipeline davranışını framework gürültüsü olmadan gözlemek isteyen ekipler için iyi bir
araştırma yüzeyi sunar.
Çok dar kapsamlı yüksek kontrol gerektiren servisler
Tek amaçlı webhook receiver, internal callback endpoint’i veya lightweight ingestion servisleri
için uygundur.
Ne Zaman Kullanmazdım?
Ben bu mimariyi şu durumlarda seçmezdim:
- geniş ekiplerin üzerinde geliştirdiği genel amaçlı product API’si,
- zengin middleware ekosistemi gereken uygulamalar,
- streaming/TLS/HTTP/2’nin birinci sınıf ihtiyaç olduğu sistemler,
- framework entegrasyonlarının geliştirici verimliliğinden daha değerli olduğu organizasyonlar.
Çünkü burada kazandığınız şey “özgürlük” değil; kontrol. Kontrol ise her zaman daha fazla
mühendislik sorumluluğu demektir.
Sonuç
Anka’nın teknik olarak en doğru özeti şu olur: özellik çıkararak performans kazanan değil, kapsamı
daraltarak davranışı sadeleştiren bir HTTP sunucu çekirdeği. Native AOT, düşük startup allocation,
connection-scoped kaynak yönetimi, tek geçişli parser ve doğrudan socket yazımı aynı hedefe hizmet
ediyor: küçük, hızlı açılan, anlaşılır bir request path.
Bu yaklaşım herkese uygun değil. Zaten olmaya da çalışmıyor. Ama deneyimli backend mühendisliği
perspektifinden bakınca, bence en olgun tarafı tam olarak bu: problemi dar tanımlıyor, çözümü o
problem için optimize ediyor ve bunun dışındaki alanları bilinçli şekilde dışarıda bırakıyor. Bu da
çoğu zaman “her şeyi yapan” sistemlerden daha değerli bir mühendislik kararıdır.
Farkındayım Anka için herhangi bir kod örneği paylaşmadım. Projenin teknik detaylarına GitHub sayfasından ulaşabilirsiniz.