- İyi sistem tasarımı, karmaşık görünmeyen ve uzun süre boyunca kayda değer sorunlar çıkarmayan bir yapı demektir
- Sistem tasarımında en zor kısım durum (state) ile başa çıkmaktır; bu yüzden durum tutan bileşenlerin sayısını mümkün olduğunca azaltmak önemlidir
- Veritabanı, durumun çoğunlukla saklandığı yerdir; bu nedenle şema tasarımı ve indeksleme ile darboğazları gidermeye odaklanan bir yaklaşım gerekir
- Caching, event işleme ve arka plan işleri performans ve bakım kolaylığı için dikkatle devreye alınmalı, aşırı kullanımından kaçınılmalıdır
- Karmaşık tasarımlar yerine yeterince doğrulanmış basit bileşen ve yöntemleri uygun şekilde kullanmak, sürdürülebilir ve istikrarlı sistem kurmanın anahtarıdır
Sistem tasarımının tanımı ve genel yaklaşım
- Yazılım tasarımı kodun bir araya getirilmesiyse, sistem tasarımı da çeşitli servislerin bir araya getirilmesi sürecidir
- Sistem tasarımının başlıca bileşenleri uygulama sunucusu, veritabanı, cache, kuyruk, event bus, proxy gibi parçalardır
- İyi bir tasarım, "özel bir sorun yok", "beklediğimden daha kolay bitti", "bu kısmı dert etmeye gerek yok" gibi tepkiler doğurur
- Buna karşılık karmaşık ve göze çarpan tasarımlar, temel sorunları gizliyor ya da aşırı tasarımı işaret ediyor olabilir
- Karmaşık sistemleri en baştan doğrudan devreye almak yerine, çalışan en basit yapıdan başlayıp zamanla geliştirmek daha avantajlıdır
Durum (state) ve stateless ayrımı
- Yazılım tasarımındaki en çetin konu durum yönetimidir
- Bilgi saklamadan anında sonuç döndüren servisler (GitHub’ın PDF render etmesi gibi) stateless yapıdadır
- Buna karşılık veritabanına yazma yapan servisler durum yönetir
- Sistem içinde durum tutan bileşenleri mümkün olduğunca azaltmak gerekir. Bu, sistemin karmaşıklığını ve arıza olasılığını düşürür
- Durum yönetimini tek bir servisin üstlenmesi, diğer servislerin ise API çağrısı yapmak veya event üretmek gibi stateless rollere odaklanması önerilir
Veritabanı tasarımı ve darboğaz noktaları
Şema ve indeks tasarımı
- Veriyi saklamak için insanın kolay okuyabileceği bir şema tasarımı gerekir
- Fazla esnek şemalar (ör. her şeyi bir JSON kolonunda saklamak) uygulama kodu ve performans üzerinde yük oluşturabilir
- Sık sorgulanacak kolonları temel alarak uygun indeksler tanımlanmalıdır. Her şeye indeks koymak ise gereksiz ek yük doğurur
Darboğazları çözme yöntemleri
- Veritabanı erişimi çoğu zaman ağır bir darboğaz haline gelir
- Mümkün olduğunda karmaşık verileri uygulama katmanında değil, veritabanı içinde JOIN gibi işlemlerle ele almak performans açısından daha avantajlıdır
- ORM kullanırken döngü içinde sorgu çalıştırma hatasına dikkat edilmelidir
- Gerektiğinde sorguları bölerek veritabanının yükünü ya da sorgu karmaşıklığını dengelemek de bir yöntemdir
- Okuma sorgularını read-replica'lara dağıtmak, ana (write) düğüm üzerindeki yükü azaltmada etkilidir
- Çok sayıda sorgu aynı anda geldiğinde transaction ve yazma işlemleri veritabanını kolayca aşırı yükleyebilir; bu nedenle query throttling (sınırlama) düşünülmelidir
Yavaş işler ile hızlı işlerin ayrılması
- Kullanıcının etkileşimde bulunduğu işlemlerin yüzlerce milisaniye içinde yanıt vermesi gerekir
- Uzun süren işler (ör. büyük PDF dönüştürmeleri), önde kullanıcıya sadece en az gerekli kısmı hemen sunup geri kalanını arka planda çalıştırma yaklaşımıyla daha verimli yönetilir
- Arka plan işleri genelde bir kuyruk (ör. Redis) ve job runner ile birlikte çalışır
- Uzak zamana planlanmış işler için Redis yerine ayrı bir DB tablosu kullanıp scheduler ile çalıştırmak daha pratiktir
Caching
- Caching, aynı ya da maliyetli işlemler tekrarlandığında maliyeti düşürür ve performansı artırır
- Genelde cache’i yeni öğrenen junior mühendisler her şeyi cache’lemek ister; deneyimli mühendisler ise cache kullanımında daha temkinlidir
- Cache yeni bir durum katmanı eklediği için senkronizasyon sorunları, hatalar ve stale data riski taşır
- Önce sorguya indeks eklemek gibi performans iyileştirmeleri denenmeli, cache daha sonra uygulanmalıdır
- Büyük ölçekli cache için Redis/Memcached yerine S3/Azure Blob Storage gibi doküman depolarına periyodik olarak yazma yöntemi de kullanılabilir
Event işleme
- Çoğu şirketin bir event hub'ı (ör. Kafka) vardır ve farklı servisler event tabanlı olarak dağıtık biçimde çalışır
- Event’leri gereğinden fazla kullanmak yerine, basit istek–yanıt API tasarımı logging ve sorun çözme açısından daha faydalıdır
- Event tabanlı işleme, gönderenin alıcının davranışını önemsemesine gerek olmayan ya da yüksek hacimli, gecikmeye toleranslı senaryolar için uygundur
Verinin iletilme biçimi: push ve pull
- Veri iletiminde iki yaklaşım vardır: Pull (istekten sonra yanıt) ve Push (değişiklik olduğunda otomatik iletim)
- Pull yaklaşımı basittir ama tekrar eden istekler ve aşırı yük sorunları doğurabilir
- Push yaklaşımında sunucu, veri değiştiğinde istemciye anında iletim yapar; bu da daha verimli olmasını ve güncel veriyi korumasını sağlar
- Çok sayıda istemciyi desteklemek için her iki yaklaşımda da uygun altyapı genişletmesi (event queue, birden çok cache sunucusu vb.) gerekir
Hot path'lere odaklanmak
- Hot path, sistem içinde en kritik ve en yoğun veri akışının geçtiği yolu ifade eder
- Hot path’lerde seçenek azdır ve tasarım hatası durumunda tüm servis genelinde ciddi sorunlara yol açabilir; bu yüzden dikkatli tasarım şarttır
- Seçeneği bol küçük özellikler yerine, hot path’lere odaklanıp tasarım ve teste kaynak ayırmak daha etkilidir
Logging, metrikler ve izleme
- Arıza anında nedeni teşhis edebilmek için, olumsuz akışlar (unhappy path) hakkında ayrıntılı loglar tutmak gerekir
- Sistem kaynakları (CPU/bellek), kuyruk boyutu, istek/iş süresi gibi temel gözlemlenebilirlik metrikleri toplanmalıdır
- Sadece ortalama değerlere bakmak yerine p95, p99 gecikme süreleri gibi dağılım metrikleri de mutlaka izlenmelidir. En yavaş küçük bir istek grubu, en kritik kullanıcıların sorununu temsil ediyor olabilir
Kill switch, retry ve arıza kurtarma
- Kill switch (sistemi geçici olarak durdurma) ve retry stratejilerinin doğru kullanımı önemlidir
- Rastgele retry yapmak sadece diğer servislere yük bindirir; etkili olması için istekler önceden circuit breaker gibi mekanizmalarla kontrol edilmelidir
- Idempotency Key kullanımı, aynı isteğin yeniden işlenmesi sırasında yinelenen işlemleri önleyebilir
- Bazı arıza durumlarında fail open veya fail closed arasında seçim yapmak gerekir. Örneğin rate limiting için fail open (izin vermek) kullanıcı etkisini azaltır. Buna karşılık kimlik doğrulamada fail closed zorunludur
Sonuç
- Servis ayrımı, container, VM kullanımı, tracing gibi bazı konular atlanmış olsa da, iyi doğrulanmış bileşenleri doğru yerde kullanmak uzun vadede en istikrarlı sistemi kurmanın yoludur
- Teknik olarak sıra dışı tasarımlar gerçekte çok enderdir; sıkıcı denecek kadar basit tasarımlar ise pratikte en sık kullanılanlardır
- Özünde iyi sistem tasarımı, göze çarpmayan ve yeterince kanıtlanmış yöntemleri güvenli biçimde bir araya getirme sürecidir
Henüz yorum yok.