Uzmanlık
Yazılım mimarı: teslim garantisinin karar garantisi olmadığı sistemler
Bir fatura akışında outbox mesajın kaybolmamasını sağladı ama aynı mesajın aynı sonucu üreteceğini garanti etmedi; mimariyi o ikinci boşluğu da hesaba katan kararların toplamı olarak yapıyorum.
- Başlangıç
- 2022 Başlangıç
- yıl
- 4 yıl
- yazı
- 5 yazı
- proje
- 10 proje
Bu ekosistemde ben
Mimariyi bir desen koleksiyonu olarak değil, belirli bir sistemde, belirli bir sınırla ve belirli bir bedelle verilmiş karar olarak yapıyorum. Bu sayfa üç konuyu kendi kapsamında tutuyor: Software Architecture, System Design ve Event-Driven Architecture.
Üçünün kanıtı sitede duruyor. Teslim ve idempotency tarafı outbox günlüğünde, yasal bir kısıtın tasarımı nasıl daralttığı numara çakışması günlüğünde, teslimden sonraki determinizm sorusu ise determinizm yazısında. Rollerin kendisi özgeçmişte, konunun kitap ölçeğindeki karşılığı güvenilir event-driven sistemler kitabında. Derinliğe inmek isterseniz sade.dev tarafındaki mimari kararların neden çürüdüğü yazısı ve outbox ile idempotent tüketim anlatısı doğru yer.
Bu sayfanın kapsadığı üç konu
Üçü de sitedeki bir yazıya, kayda ya da deneyime dayanıyor; derinlik değil kimlik ve kanıt düzeyinde duruyor.
-
Software Architecture
Mikroservis sınırlarını, kritik tasarım kararlarını ve bunların ekibe aktarılmasını kapsıyor. Senior Software Developer, ardından Staff Engineer rollerinde mimariyi domain-driven design ve test-odaklı geliştirmeyle birlikte yürüttüm; sınırları wiki'de bırakmayıp koda yazıp CI'da denetleyen yaklaşım da bu başlığın parçası.
-
System Design
Bir sistemin ölçeklenme, erişilebilirlik ve yük dağılımı kararlarını kapsıyor. Senkron bir fatura akışını ortak bir kuyruğa ve ayrı bir consumer servisine taşıdım. Bunun temeli, mimari sorumluluk üstlenmemden önce bir arama altyapısında atıldı; MySQL ile Redis'i birlikte kullanan bir önbellek katmanı ve böldüğüm veritabanı ile cache kümeleri.
-
Event-Driven Architecture
Teslim, tekrar ve determinizm sorularını kapsıyor. Transactional outbox, idempotent tüketici ve at-least-once teslim bu başlığın pratiği; teslim edilen bir mesajın aynı sonucu üreteceğinin ayrı bir garanti gerektirdiği de.
Bir kararın bedeli
E-fatura numarası üretiminde yasal bir kısıt tasarımın asıl kuvveti oldu. Zincir sırayla okunur.
- 01 Numarayı geç üretmek race doğurdu
- Gönderim anında MAX değerine bir ekleyen yöntem tek worker'la çalışıyordu; paralel worker'lar aynı numarayı üretip dış API'ye aynı numarayla gitti.
- 02 Numarayı erken üretmek boşluk doğurdu
- Numara kuyruğa atılırken üretilirse, düşen ya da iptal edilen fatura seride açıklanamayan bir boşluk bırakıyor. Yasal seride atlanan numara olamaz.
- 03 Yasal kısıt çözüm kümesini daralttı
- Çakışmayı tek başına çözen çok yöntem var; boşluksuzluk eklenince çözüm kümesi bir anda daraldı. Gereksinimi iş kuralı diye kenara almadan önce mimariyi nasıl kısıtladığına bakmak gerekiyor.
- 04 Numara, dış çağrıdan hemen önce rezerve edildi
- Rezerve edilen numara çağrı başarısız olsa da faturanın üstünde kalıyor ve aynı numarayla yeniden deneniyor. Dış çağrı transaction'ın dışında tutuldu; aksi hâlde sistem bir bottleneck'e dönüşürdü.
Teslim garantisi karar garantisi değildir
Her garanti bir maliyetle geliyor; bir deseni çözmek bir sonrakini açıyor.
- 01 Outbox mesajın kaybolmamasını sağlar
- Mesaj, kayıtla aynı transaction içinde bir outbox satırı olarak yazılıyor ve bir relay onu yayımlıyor. Dual-write problemi böylece kapanıyor.
- 02 At-least-once tekrar getirir
- Mesaj kaybolmuyor ama tekrar edebiliyor. Tüketicinin idempotent olması, tekrarı yan etkisiz hâle getiriyor.
- 03 Teslim edilen mesaj aynı kararı vermeyebilir
- Aynı önek listesi iki ayrı serviste tutulduğunda, birinin güncellenmesi, birkaç saat arayla gelen aynı biçimdeki iki mesajdan birini geçirip ötekini reddettirdi. Mesaj, kararın girdisinin tamamı değil.
- 04 Bağlamı üretim anında dondurmak gerekiyor
- Karar için gereken bilgiyi tüketici kendi çözmemeli; olay kendi bağlamını taşımalı. Retry, karar anını değiştirmemeli.
Mimariyi nasıl yürütüyorum
Üç alışkanlık, hepsi ekibin aynı kararı aynı biçimde okuyabilmesine dönük.
-
Kararın bedelini yazmak
Bir kararın neyi kapattığını ve yerine ne koyduğunu yazmadan onu mimari saymıyorum. Yukarıdaki iki zincir bu alışkanlığın çıktısı.
-
Sınırı koda yazmak
Katmanları ve izinli bağımlılıkları tek bir dosyada beyan edip her commit'te CI'da denetlemek, bir mimari kararın iki yıl sonra çürümesini önlüyor.
-
Bilgiyi ekibe aktarmak
Ekibe TDD ve DDD mentörlüğü verdim ve bir code-review rehberi hazırladım; mimarinin yaşadığı yer belge değil, ekibin her gün verdiği küçük kararlar.
Derinlik burada değil
Bu sayfa kimlik ve kanıt düzeyinde kalıyor. Desen anlatısını, mimari kararların neden çürüdüğünü ve outbox ile numara rezervasyonunun ayrıntılarını sade.dev'de yazıyorum.
Bu alandaki son yazılar
Aynı mesaj, farklı sonuç: event-driven mimaride determinizm
Event-driven mimaride aynı mesaj neden farklı sonuç üretir? Gerçek bir fatura akışından: determinizmi bozan gizli girdiler ve geri kazandıran dört hamle.
Bu alandaki ölçümler
Bir çekirdek Go'da 14.330, PHP-FPM'de 5.152 OAuth2 isteği taşıyor
Her istekte RS256 bearer token doğrulayıp PostgreSQL'e bir satır yazan ya da okuyan aynı API, bir, iki ve dört çekirdekte Go, PHP-FPM ve FrankenPHP worker ile saniyede kaç karma istek taşıyor?
Bulgu
Dört çekirdekte Go 57.321, FrankenPHP 25.659, PHP-FPM 20.606 karma istek taşıdı; çekirdek başına 14.330, 6.415 ve 5.152. Kapasite planına giren sayı bu değil, istek başına uygulama CPU'su: Go 66,8, FrankenPHP 110,7, PHP-FPM 187,7 mikrosaniye. FrankenPHP doyduğunda dört çekirdeğin ancak 2,84'ünü kullanabiliyor — Go 3,83, PHP-FPM 3,87. Darboğaz veritabanı değil: aynı dört çekirdekte PostgreSQL tek başına saniyede 68.212 satır yazıyor, yani en hızlı adayın karma tavanının üstünde.
21 gün önce ölçüldü
Partial index on beş dakikada üç yüz beş kat şişti — ve autovacuum bir kez bile gelmedi
Sürekli churn altındaki bir kuyruk tablosunda partial index'in küçüklüğü kalıcı mı, ve varsayılan autovacuum ayarları ona yetişiyor mu?
Bulgu
Canlı küme yaklaşık 845. saniyeye kadar beş bin satırda sabit dururken partial index 0,125 MB'dan 38,2 MB'a çıktı — üç yüz beş kat. Küçüklüğü canlı satır sayısından geliyor, şişme hızı iş hacminden, ve ikisi arasında hiçbir bağ yok. Composite index oransal olarak daha az şişti (%42) ama mutlak olarak daha çok (+126 MB) ve şişerken çöktü — büyük olasılıkla belleğe sığmadığı için, ki bu ölçümde nedeni ölçülmedi: gecikmesi 0,52 ms'den 61 saniyeye çıktı, kuyruğu 126 bine tırmandı. On beş dakikada 1,75 milyon ölü satır birikti ve autovacuum **bir kez bile koşmadı** — varsayılan eşik tablonun tamamına göre ölçekleniyor (50 + 0,2 × 10 milyon ≈ 2 milyon), churn ise küçük canlı kümede oluyor.
47 gün önce ölçüldü
Postgres partial index'i generic plana çevirmedi: kırk çalıştırma, kırk custom plan
Hazırlanmış bir deyimde Postgres partial index'li sorguyu kendiliğinden generic plana çeviriyor mu — yoksa bin altı yüz otuz beş katlık uçuruma ancak elle mi düşülüyor?
Bulgu
Postgres geçişi reddediyor. Partial index'te kırk çalıştırmanın kırkı da custom plan — sayaç 40/0. Reddetmesinin sebebi tam olarak felaketin kendisi: generic plan partial index'i kullanamaz, bu yüzden tahmini maliyeti yüksek çıkar ve planner onu seçmez. Composite index ise ders kitabındaki gibi altıncı çalıştırmada geçiyor (5/35) ve hiçbir şey kaybetmiyor. Yani bin altı yüz otuz beş katlık uçurum gerçek ama çitli: ona düşmek için `plan_cache_mode = force_generic_plan` yazmak gerekiyor.
47 gün önce ölçüldü
Bu alanda geliştirdiklerim
Üretim veritabanına giden ad-hoc SQL'i onaya, maskelemeye ve değiştirilemez bir ize bağlayan self-hosted portal; QueryProxy ürününe dönüştü.
Şu an ne yapıyor
Geliştiricinin yazdığı SQL'i üretim veritabanında doğrudan değil, bir onaydan geçirerek çalıştırıyor; sonuçlar diske yazılırken maskeleniyor ve her istek değiştirilemez bir kayda düşüyor. Prod erişimi tek kişide toplanmış ekipler kurabilir.
Çok hesaplı gelir-gider takibini tek modelde toplayan finans çekirdeği; web + iOS + Android'de çalışan Parantaj'a dönüştü.
Şu an ne yapıyor
Tek hesap/işlem modeli çoklu hesap yönetimini, bütçeyi, hedefleri ve raporlamayı aynı çekirdekte taşıyor; web, iOS ve Android istemcileri yayında. Kişisel ve kurumsal finansını tek yerden takip etmek isteyen kullanabilir.
Her projeye kendi MCP ucunu açan, dokümanı kendi sunucunda tutan çok kiracılı doküman sunucusu; embedding'ler CPU'da üretiliyor.
Şu an ne yapıyor
Bir projenin klasörünü, git deposunu, Obsidian kasasını ya da Notion çalışma alanını tek bir MCP ucunun arkasında aranabilir kılıyor; ajan dokümana ulaşırken veri üçüncü tarafa gitmiyor. Docker ile kurup panelden yöneten herkes kendi sunucusunda çalıştırabilir.
Bu alanda üretime çıkan işler
QueryProxy
Devam ediyorFounder & Developer
Seviye: Ana OdakQueryProxy, geliştiricilerin üretim veritabanına kimlik bilgisi almadan erişebilmesi için geliştirdiğim self-hosted bir sorgu onay portalıdır. Yazılan SQL çözümlenerek denetlenir, bir DBA onayından geçtikten sonra kuyrukta çalışır, sonuçlar diske yazılırken maskelenir ve her adım değiştirilemez bir denetim kaydına düşer.
Parantaj
Devam ediyorOwner
Seviye: Aktif GeliştirmeKişisel ve kurumsal finansal yönetim platformu. Gelir-gider takibi, bütçe planlama, detaylı raporlama ve çoklu hesap yönetimi sunar.
academia.sh
Devam ediyorFounder & Author
Seviye: Aktif Geliştirmeacademia.sh, bir dersin teorisini sahadan gelen gerçek bir problemden ayırmayan, dersleri kayıt olmadan ücretsiz okunan bir öğrenme platformudur. Müfredat-kurs-konu-ders hiyerarşisiyle sunulur, ilerleme ve quiz teoriyle problemi ayırmadan ders bazında ölçülür, tam metin arama Postgres üzerinde çalışır; kurs sonu sınavı, sertifika ve çalışma asistanı Pro katmanda. İçerik ayrı bir GitHub reposunda tutulur; ilk alan bilgisayar bilimleri, model baştan çok disiplinli tasarlandı.
Bu alanda sorulanlar
Bu eksende 13 soru yanıtlandı.
Gitflow'dan Trunk-Based'e geçerken feature flag mimarisini nasıl konumlandırırım?
Uzun ömürlü dalları kaldırıp değişiklikleri küçük parçalar hâlinde main'e alın, bitmemiş işi kapalı bir flag arkasında tutun ve biten flag'i temizleyin.
CQRS'i ne zaman uygulamalı ve okuma/yazma modellerini nasıl senkronlamalıyım?
Raporlamayı bir read-replica'ya taşıyın, tam CQRS'e ancak sorgu şekilleri tek şemaya sığmadığında geçin ve okuma modelini outbox event'leriyle besleyin.
Esnek ürün nitelikleri için NoSQL mu, PostgreSQL mi seçmeliyim?
İlişkisel çekirdeği (ürün, fiyat, sipariş) PostgreSQL'de tutup değişken nitelikleri GIN index'li tek bir `JSONB` kolonuna koyun; raporlama bölmeye karşı.
Birlikte kullandığım teknolojiler
-
RabbitMQ
Servisler arası asenkron teslim; outbox relay'inin çıkış kapısı.
-
Redis
Önbellek katmanında ilişkisel depoyla birlikte çalışan hızlı katman.
-
MySQL
Önbellekli okuma ve outbox tablosunun üzerinde durduğu depo.
-
PostgreSQL
Kuyruk tablosu ve index davranışını üzerinde ölçtüğüm veritabanı.
-
Docker
Geliştirme ortamı ve dağıtım paketi.
-
GitHub Actions
Mimari sınırların her commit'te denetlendiği CI hattı.
Sık sorulanlar
5 soru
-
Mimari alanında kaç yıldır çalışıyorsun?
2022'den bu yana, 4 yıldır. Senior Software Developer olarak başladım, Mart 2025'ten beri Staff Engineer olarak mikroservis ve event-driven mimariyi tasarlıyorum. Daha önceki arama altyapısı yıllarındaki sistem tasarımı temeli bu sürenin dayanağı; sürenin kendisine dahil değil. Bu sitede bu işlerle kesişen 5 yazı kayıtlı.
-
Software Architecture, System Design ve Event-Driven Architecture arasında nasıl ayırıyorsun?
Software Architecture sınırları ve kararları, System Design bir sistemin ölçeklenme ve erişilebilirlik davranışını, Event-Driven Architecture ise servisler arasındaki teslim, tekrar ve determinizm sorularını kapsıyor. Üçü aynı işin farklı ölçekleri; bu sayfa üçünü birlikte tutuyor.
-
Kuyruğa taşımak teslim garantisi verir mi?
Hayır. Outbox mesajın kaybolmamasını sağlar ama size at-least-once verir; tekrarı idempotent tüketici bastırır. Teslim edilen mesajın aynı kararı vereceği ise üçüncü bir garanti ve bir fatura akışında onu ayrıca kurmam gerekti.
-
Mimari kararı nasıl değerlendiriyorsun?
Neyi kapattığına ve yerine ne koyduğuna bakarak. E-fatura numarası örneğinde numarayı geç üretmek race, erken üretmek boşluk doğuruyordu; çözüm ikisini birden tutmaktı.
-
Derin mimari yazılarını nerede okuyabilirim?
sade.dev'de. Bu sayfa kimlik ve kanıt düzeyinde kalıyor; desen anlatısı ve uzun mimari yazılar orada.
Birlikte çalışalım
Bu ekosistemde bir işiniz varsa, ne yaptığınızı anlatın; nasıl kurulacağını konuşalım.