Mühendislik mülakatlarının en zorlu aşamalarından biri olan sistem tasarımı soruları, çoğu zaman basit görünen ama derinlemesine bilgi ve deneyim gerektiren tuzaklar barındırır. Pek çok mühendisin beş dakikada çözdüğünü düşündüğü ancak aslında kritik detayları gözden kaçırdığı bu sorular, teknik bilginin ötesinde problem çözme ve mimari düşünme becerilerini ölçer. Bu makalede, TinyURL benzeri bir URL kısaltma servisinin tasarımını adım adım ele alarak, bu tür “5 dakikalık” soruların altında yatan karmaşıklıkları ve doğru yaklaşımı keşfedeceğiz. Hazır olun, çünkü bu makale size sadece bir sistemi nasıl tasarlayacağınızı değil, aynı zamanda sistem tasarım mülakatlarında nasıl başarılı olacağınızı da gösterecek.
Sistem tasarımı mülakatları, adayların geniş bir mühendislik problemini ele alma, yapılandırma ve çözme yeteneklerini değerlendirmek için tasarlanmıştır. “5 dakikalık” tabiri, sorunun yüzeysel olarak basit görünmesinden gelir; örneğin, “TinyURL gibi bir servis tasarlayın” veya “Bir sohbet uygulaması tasarlayın.” Ancak, bu basit görünen soruların altında yatan beklentiler, genellikle bir mühendisin hayal edebileceğinden çok daha fazlasını içerir. Mühendislerin bu tür sorularda yanılmalarının birçok nedeni vardır ve bunların çoğu, teknik bilgiden ziyade metodolojik yaklaşımdaki eksikliklerden kaynaklanır. Öncelikle, çoğu aday mülakatın başında yeterince aydınlatıcı soru sormaz. Gereksinimleri tam olarak anlamadan, kapsamı belirlemeden doğrudan çözüm üretmeye çalışmak, baştan yanlış bir yola girmeye neden olur. Örneğin, URL kısaltma servisi tasarlarken, hangi throughput (işlem hacmi) bekleniyor, kısa URL’lerin geçerlilik süresi olacak mı, yoksa sadece kalıcı mı olacaklar gibi sorular, sistemin temel mimarisini kökten değiştirebilir.
İkinci olarak, mühendisler genellikle fonksiyonel gereksinimlere odaklanıp, non-fonksiyonel gereksinimleri göz ardı ederler. Bir sistemin sadece “çalışması” yeterli değildir; aynı zamanda yüksek ölçeklenebilirlik, düşük gecikme, yüksek erişilebilirlik, veri tutarlılığı ve güvenlik gibi özelliklere de sahip olması gerekir. TinyURL örneğinde, saniyede binlerce kısaltma isteğini veya milyonlarca yönlendirme isteğini karşılayabilmek, temel bir veritabanı şemasından çok daha fazlasını gerektirir. Bu kritik performans ve güvenilirlik hedefleri belirlenmeden yapılan bir tasarım, gerçek dünyada hızla çökecek veya bakımı imkansız hale gelecektir. Bu yüzden, mülakat sırasında bu non-fonksiyonel gereksinimlerin kapsamını ve önceliklerini netleştirmek hayati önem taşır. Ayrıca, mühendisler sıkça trade-off’ları (takasları) göz önünde bulundurmazlar. Dağıtık sistem tasarımında her kararın bir bedeli vardır. Bir sistemi daha erişilebilir yapmak, genellikle tutarlılıktan ödün vermek anlamına gelebilir (CAP teoremi). Veri depolama seçimi, önbellekleme stratejileri veya kimlik oluşturma mekanizmaları gibi kararlar, sistemin genel performansı, maliyeti ve karmaşıklığı üzerinde doğrudan etki yapar. Bu takasları anlayıp, mülakatçıya sunabilmek, yalnızca teknik bilginizi değil, aynı zamanda olgun ve pragmatik bir mühendislik yaklaşımınızı da gösterir.
Son olarak, birçok mühendis çok erken detaylara takılır veya yüksek seviye bir bakış açısı geliştiremez. Bir sistem tasarımına başlarken, geniş resmi görmek ve ana bileşenleri belirlemek çok önemlidir. Mikroservis mimarisine mi gidiyoruz, monolitik mi kalacak? Hangi veri depolarını kullanacağız? Yük dengeleme nasıl yapılacak? Bu soruların cevabı, daha sonraki detaylı tasarım adımları için bir yol haritası sunar. Ancak bazı adaylar, ilk dakikalardan itibaren bir veritabanının hangi indekslerini kullanacaklarını veya belirli bir kod parçasını nasıl yazacaklarını düşünmeye başlarlar. Bu durum, genel mimariyi gözden kaçırmaya ve sistemin bütünsel resmini kaybetmeye yol açar. Özetle, “5 dakikalık” sistem tasarımı soruları, sadece teknik bilgi birikimini değil, aynı zamanda structured thinking (yapılandırılmış düşünme), problem çözme, iletişim ve mühendislik yargısı gibi birçok önemli beceriyi test eder. Bu nedenle, başarılı olmak için bu alanlarda da kendinizi geliştirmeniz şarttır.
Temel Kavramlar: Bir Sistem Tasarımında Neleri Bilmeliyiz?
Sistem tasarımına giriş yaparken, bazı temel kavramları ve prensipleri sağlam bir şekilde anlamak gereklidir. Bu kavramlar, herhangi bir karmaşık sistemi ele alırken bir çerçeve sunar ve doğru soruları sormanıza yardımcı olur. Özellikle dağıtık sistemlerin dinamik doğası, bu temel prensiplerin önemini daha da artırır. Bir projeye başlarken veya bir mülakat sorusunu yanıtlarken, öncelikle projenin temel taşlarını doğru bir şekilde belirlemelisiniz.
Fonksiyonel ve Non-Fonksiyonel Gereksinimler Nasıl Belirlenir?
Bir sistemin ne yapacağını ve bunu nasıl yapacağını belirlemek, tasarım sürecinin ilk ve en kritik adımıdır. Fonksiyonel gereksinimler, sistemin “ne” yapması gerektiğini tanımlar; yani son kullanıcının gözünden görülen özellikleri ifade ederler. TinyURL örneğinde fonksiyonel gereksinimler şunlar olabilir:
- Kullanıcılar uzun bir URL göndererek benzersiz, kısa bir URL alabilmelidir.
- Kullanıcılar kısa URL’ye eriştiğinde orijinal uzun URL’ye yönlendirilmelidir.
- (İsteğe bağlı) Kullanıcılar özel (custom) kısa URL’ler belirleyebilmelidir.
- (İsteğe bağlı) Sistemin kısaltılan URL’lerin tıklama istatistiklerini gösterebilmelidir.
Öte yandan, non-fonksiyonel gereksinimler, sistemin “nasıl” çalışması gerektiğini, yani performans, güvenilirlik, ölçeklenebilirlik gibi kalite özelliklerini tanımlar. Bunlar, kullanıcının doğrudan etkileşimde bulunmadığı ancak sistemin genel deneyimini ve sürdürülebilirliğini belirleyen faktörlerdir. TinyURL için non-fonksiyonel gereksinimler ise şunlar olabilir:
- Ölçeklenebilirlik (Scalability): Sistem, saniyede X kısaltma ve Y yönlendirme isteğini kaldırabilmelidir. Gelecekteki büyüme için kolayca genişletilebilir olmalıdır.
- Erişilebilirlik (Availability): Sistem %99.99 (dört dokuz) oranında erişilebilir olmalıdır (örneğin, yıllık kesinti süresi 5 dakikayı geçmemeli).
- Gecikme (Latency): URL kısaltma işlemi Z milisaniyeden, yönlendirme işlemi ise W milisaniyeden daha kısa sürmelidir.
- Veri Tutarlılığı (Data Consistency): Yeni oluşturulan kısa URL’ler anında yönlendirme için kullanılabilir olmalı mıdır (strong consistency), yoksa biraz gecikme kabul edilebilir mi (eventual consistency)?
- Dayanıklılık (Durability): Kısaltılmış URL’ler asla kaybolmamalıdır.
- Güvenlik (Security): Kötü niyetli URL’leri engelleme, DDoS saldırılarına karşı koruma.
- Bakım Kolaylığı (Maintainability): Sistem kolayca güncellenebilir ve izlenebilir olmalıdır.
Bu gereksinimleri baştan net bir şekilde belirlemek ve mülakatçı ile mutabık kalmak, tasarımınızın temelini oluşturur ve ilerleyen adımlarda yanlış kararlar almanızın önüne geçer. Aksi takdirde, gereksinimler belirsiz kaldığında, tasarımınız ne kadar teknik olarak kusursuz olursa olsun, mülakatçının beklentilerini karşılamayabilir.
Temel Mimari Bileşenleri ve Rolleri Nelerdir?
Modern sistem tasarımları, genellikle belirli görevleri yerine getiren çeşitli bileşenlerden oluşur. Bu bileşenleri ve rollerini anlamak, karmaşık bir sistemi parçalara ayırarak tasarlamanıza yardımcı olur:
- Yük Dengeleyiciler (Load Balancers): Gelen istekleri birden fazla sunucuya dağıtarak sunucuların aşırı yüklenmesini önler, sistemin erişilebilirliğini artırır ve ölçeklenebilirliği sağlar. L7 (uygulama katmanı) veya L4 (taşıma katmanı) seviyesinde çalışabilirler.
- API Ağ Geçitleri (API Gateways): Mikroservis tabanlı mimarilerde, dış dünyadan gelen tüm API isteklerinin tek bir giriş noktasıdır. Kimlik doğrulama, yetkilendirme, hız sınırlama (rate limiting) ve istek yönlendirme gibi görevleri üstlenir.
- Veritabanları (Databases – SQL/NoSQL): Veri depolama için kullanılır. SQL veritabanları (PostgreSQL, MySQL) ilişkisel veriler ve ACID (Atomicity, Consistency, Isolation, Durability) garantileri için iyidir. NoSQL veritabanları (Cassandra, MongoDB, Redis), yüksek ölçeklenebilirlik, esnek şema ve belirli veri erişim modelleri için tercih edilir.
- Önbellekler (Caches): Sık erişilen verileri daha hızlı erişim için depolayan yüksek hızlı veri depolama katmanlarıdır. Veritabanı yükünü azaltır ve gecikmeyi düşürür. Redis veya Memcached popüler örneklerdir.
- Mesaj Kuyrukları (Message Queues): Asenkron iletişimi ve görevleri yönetmek için kullanılır. Görevleri ayrıştırır, sistemin dayanıklılığını ve ölçeklenebilirliğini artırır. Kafka, RabbitMQ gibi sistemler örnek verilebilir.
- İçerik Dağıtım Ağları (CDN – Content Delivery Networks): Statik içerikleri (resimler, videolar, CSS/JS dosyaları) son kullanıcılara coğrafi olarak yakın sunucularda önbellekleyerek erişim hızını artırır ve ana sunucu yükünü azaltır. TinyURL gibi bir serviste yönlendirme performansını artırmak için de düşünülebilir, ancak genellikle doğrudan URL yönlendirmesi için kullanılmaz, daha çok frontend varlıkları için uygunlardır.
Bu bileşenlerin her biri, sistemin belirli bir ihtiyacını karşılamak üzere tasarlanmıştır. Doğru bileşenleri seçmek ve bunları etkili bir şekilde bir araya getirmek, başarılı bir sistem tasarımının anahtarıdır. Özellikle bir “5 dakikalık” soruda, hangi bileşenlerin neden seçildiğini ve birbirleriyle nasıl etkileşime girdiklerini açıklamak, mülakatçıyı etkileyecektir.
Uygulamalı Sistem Tasarımı: TinyURL Örneğiyle Adım Adım
Artık teorik bilgileri edindiğimize göre, TinyURL benzeri bir URL kısaltma servisinin tasarımını pratik bir senaryo üzerinden adım adım ele alalım. Bu bölümde, bir sistem tasarım mülakatında nasıl düşüneceğinizi ve soruları nasıl yapılandıracağınızı göreceksiniz. Amaç, sadece bir çözüm sunmak değil, aynı zamanda düşünme sürecinizi ve tasarım kararlarınızın ardındaki mantığı göstermektir.
Gereksinim Analizi ve Kapsam Belirleme
Mülakatçı size “TinyURL benzeri bir servis tasarlayın” dediğinde, ilk tepkiniz doğrudan kod yazmaya veya veritabanı şeması çizmeye başlamak olmamalıdır. Bunun yerine, kapsamı ve gereksinimleri netleştirmek için sorular sormaya başlamalısınız. Bu aşama, tüm tasarım sürecinin en önemli kısmıdır ve genellikle göz ardı edilir.
Örnek sorular:
- Saniyedeki kısaltma ve yönlendirme isteği sayısı ne kadar olacak? (örn: 100 kısaltma/saniye, 10.000 yönlendirme/saniye)
- Kısaltılmış URL’lerin karakter seti ne olmalı? (örn:
a-z A-Z 0-9-> Base62) ve uzunluğu ne kadar olmalı? (örn: 6-8 karakter) - Kısa URL’ler kalıcı mı olacak, yoksa belirli bir süre sonra silinecek mi? (son kullanma tarihi)
- Kullanıcılar özel (custom) kısa URL’ler belirleyebilecek mi?
- Analitik (tıklama sayısı gibi) özellikleri isteniyor mu?
- Maliyet, gecikme ve erişilebilirlik açısından öncelikler nelerdir? (örn: düşük gecikme ve yüksek erişilebilirlik çok kritik)
- URL kısaltma servisine API üzerinden mi erişilecek, yoksa bir web arayüzü de olacak mı?
Bu soruların cevapları, sistemin boyutunu, kullanılacak teknolojileri ve mimari desenleri doğrudan etkiler. Örneğin, saniyede 10.000 yönlendirme isteği, çok hızlı bir önbellek katmanı ve dağıtık bir veritabanı gerektirecektir. 6-8 karakterlik kısa URL’ler, benzersiz kimlikler üretme stratejisini belirleyecektir. Custom URL’ler ise, var olan kısaltmalarla çakışma kontrolü gerektirecektir.
Varsayalım ki mülakatçıdan aşağıdaki gereksinimleri aldık:
- Fonksiyonel: URL kısaltma, yönlendirme, custom URL’ler (isteğe bağlı), tıklama analizi (basit).
- Non-Fonksiyonel: Yüksek ölçeklenebilirlik (1000 kısaltma/sn, 100.000 yönlendirme/sn), düşük gecikme (<50ms yönlendirme), yüksek erişilebilirlik (%99.99), veri dayanıklılığı.
- Kısa URL uzunluğu: 7 karakter, Base62 karakter seti (
a-zA-Z0-9). - Beklenen ömür: Kalıcı URL’ler, ancak belirli bir süre sonra silinme özelliği de olabilir.
Yüksek Seviye Tasarım: Temel Bileşenleri Birleştirmek
Gereksinimleri netleştirdikten sonra, sistemin yüksek seviye mimarisini çizmeye başlayabilirsiniz. Bu aşamada, temel bileşenleri ve bunların birbirleriyle nasıl etkileşimde bulunacağını belirleriz. Amaç, sistemin genel yapısını ortaya koymaktır, henüz detaylara inmeyiz.
TinyURL için ana bileşenler şunlar olabilir:
- Kullanıcılar/Müşteriler: Web tarayıcıları veya API aracılığıyla servisimize erişen uygulamalar.
- Yük Dengeleyici (Load Balancer): Gelen tüm istekleri (hem kısaltma hem de yönlendirme) dağıtmak için. Bu, erişilebilirliği ve performansı artırır.
- API Gateway: Temel güvenlik (kimlik doğrulama/yetkilendirme), hız sınırlama (rate limiting) ve istek yönlendirme gibi görevleri üstlenir. İki ana servise (URL Kısaltma ve URL Yönlendirme) gelen istekleri ayrıştırır.
- URL Kısaltma Servisi (URL Shortening Service): Uzun URL’leri alır, benzersiz kısa bir kod üretir ve bunları veritabanına kaydeder.
- URL Yönlendirme Servisi (URL Redirection Service): Gelen kısa URL isteklerini işler, veritabanından orijinal URL’i alır ve kullanıcıyı yönlendirir.
- Veritabanı (Database): Uzun URL-kısa URL eşleşmelerini ve ilgili metadata’yı (oluşturulma tarihi, tıklama sayısı vb.) depolamak için.
- Önbellek (Cache): Sık erişilen kısa URL-uzun URL eşleşmelerini yüksek hızda depolayarak yönlendirme performansını artırmak için.
- Asenkron Kuyruk (Asynchronous Queue) & Analitik Servisi: Tıklama istatistiklerini gerçek zamanlı işlemeden ziyade asenkron olarak toplayıp işlemek için.
Bu bileşenlerin birbirleriyle nasıl etkileşime girdiğini bir akış şeması şeklinde görselleştirmek faydalı olacaktır:
Kullanıcı
|
V
Yük Dengeleyici (Load Balancer)
|
V
API Ağ Geçidi (API Gateway)
|
+-----> URL Kısaltma Servisi
| |
| V
| Kısa Kod Üretici (ID Generator)
| |
| V
+-----> Veritabanı (Database)
| |
| V
+-----> Önbellek (Cache)
|
+-----> URL Yönlendirme Servisi
| |
| V
+-----> Asenkron Kuyruk (Tıklama Analizleri için)
URL Kısaltma Akışı:
- Kullanıcı, API Gateway üzerinden bir uzun URL ile
/shortenisteği gönderir. - API Gateway, isteği URL Kısaltma Servisi'ne yönlendirir.
- URL Kısaltma Servisi, benzersiz bir kısa kod üretir (bu en karmaşık kısımlardan biridir).
- Orijinal URL ve kısa kod eşleşmesi veritabanına kaydedilir.
- Kısa kod önbelleğe alınır.
- Kısa kod kullanıcıya geri döndürülür.
URL Yönlendirme Akışı:
- Kullanıcı, kısa URL ile
/isteği gönderir. - API Gateway, isteği URL Yönlendirme Servisi'ne yönlendirir.
- URL Yönlendirme Servisi, önce önbellekte kısa kodu arar.
- Eğer önbellekte bulunamazsa, veritabanında arar.
- Orijinal URL bulunur ve kullanıcıya
HTTP 301/302yönlendirmesi yapılır. - Asenkron olarak tıklama sayımı için bir mesaj kuyruğuna olay gönderilir.
Bu yüksek seviye tasarım, sistemin ana iş akışını ve bileşenlerini net bir şekilde ortaya koyar. Şimdi, bu bileşenlerin her birinin içine inerek detayları inceleyelim.
Detaylara İnmek: Kritik Bileşenlerin Tasarımı ve Zorluklar
Yüksek seviye tasarımımızı oluşturduktan sonra, sistemin kritik bileşenlerinin nasıl çalışacağını ve karşılaşabileceğimiz zorlukları ele almamız gerekiyor. Bu kısım, mühendislik becerilerinizi ve problem çözme yeteneğinizi en çok göstereceğiniz yerdir.
Kısa URL Oluşturma Mekanizması: Çarpışmaları Nasıl Önleriz?
7 karakterlik Base62 bir kod üretmek, TinyURL'ün kalbindeki en önemli ve zorlu görevlerden biridir. Base62 (a-z, A-Z, 0-9), yaklaşık 62^7 farklı benzersiz kısa URL oluşturma potansiyeli sunar, bu da yaklaşık 3.5 trilyon kombinasyon demektir. Bu sayı, mevcut ve gelecekteki gereksinimlerimiz için yeterli görünmektedir. Ancak, bu kodları benzersiz ve verimli bir şekilde nasıl üreteceğiz?
- Basit Rastgele Oluşturma ve Çarpışma Kontrolü: En basit yaklaşım, 7 karakterlik rastgele bir Base62 dizesi oluşturmak ve veritabanında bu dizenin daha önce kullanılıp kullanılmadığını kontrol etmektir. Eğer kullanılmışsa, yeni bir tane oluşturup tekrar deneriz (retry).
function generateShortCode(): string { let shortCode = ''; const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; for (let i = 0; i < 7; i++) { shortCode += chars.charAt(Math.floor(Math.random() * chars.length)); } return shortCode; } // Örnek kullanım: // do { // shortUrl = generateShortCode(); // } while (database.exists(shortUrl)); // database.save(longUrl, shortUrl);Zorluk: Çok sayıda URL kısaltıldığında çarpışma olasılığı artar ve bu da veritabanı sorgularını ve dolayısıyla gecikmeyi artırır. Eğer sistem çok büyürse, bu yaklaşım bir darboğaz haline gelebilir. Ayrıca dağıtık sistemlerde aynı anda iki sunucunun aynı kısa kodu üretme ve kaydetmeye çalışma riski de vardır (race condition). Bunu çözmek için veritabanında kısa URL sütununa benzersiz bir indeks eklenerek olası çakışmaların veritabanı seviyesinde hata vermesi sağlanabilir, bu durumda uygulama hatayı yakalayıp yeniden deneme yapabilir.
- Sıralı ID'den Base62 Dönüşümü: Daha ölçeklenebilir bir yaklaşım, veritabanının otomatik artan birincil anahtar (Auto-Increment ID) özelliğini kullanmaktır. Yeni bir uzun URL kaydettiğimizde, veritabanı bize benzersiz bir sayısal ID verir. Bu sayısal ID'yi Base62'ye dönüştürerek kısa URL'imizi elde ederiz. Örneğin, veritabanı ID'si
12345ise, bunu Base62'ye dönüştürerekB8jgibi bir değer elde edebiliriz. 7 karaktere ulaşmak için gerekirse başına sıfır ekleyebiliriz veya ID'yi daha büyük bir tabana dönüştürüp daha uzun bir sonuç elde edebiliriz.function encodeToBase62(num: number): string { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let encoded = ''; while (num > 0) { encoded = chars[num % 62] + encoded; num = Math.floor(num / 62); } return encoded || 'a'; // En küçük ID (0 veya 1) için 'a' veya 'A' } // Örnek kullanım: // const newId = database.insert(longUrl); // Veritabanından gelen otomatik ID // const shortUrl = encodeToBase62(newId);Avantaj: Çarpışma riski yoktur, çünkü her ID benzersizdir. Ayrıca sıralı ID'ler sayesinde kısa URL'ler de bir nevi sıralı olur, bu da veritabanı indekslemesi için faydalı olabilir.
Zorluk: Veritabanı sharding (parçalama) yapıldığında otomatik artan ID'ler her shard'da tekrar başlayabilir ve çakışmalara yol açabilir. Bu durumda, dağıtık ortamda benzersiz ID'ler üretecek (örneğin Twitter Snowflake, UUID'ler veya önceden ayrılmış ID blokları) özel bir servis veya algoritma kullanmak gerekir.
Bizim senaryomuzda, yüksek throughput ve benzersizlik garantisi için, dağıtık bir ID üretici servisi (örneğin Snowflake benzeri) kullanıp bu ID'yi Base62'ye dönüştürmek en sağlam yaklaşım olacaktır. Bu, her zaman benzersiz ve sıralı olmaya yakın kısa kodlar üretilmesini sağlar ve çarpışma riskini ortadan kaldırır.
Veri Depolama ve Veritabanı Seçimi: SQL mi, NoSQL mi?
Veri depolama kararı, sistemin ölçeklenebilirliği, tutarlılığı ve performansını doğrudan etkiler. TinyURL için depolamamız gereken temel veriler şunlardır:
short_code(birincil anahtar olarak benzersiz string)original_url(uzun string)creation_dateexpiration_date(isteğe bağlı)user_id(eğer kullanıcılar login olabiliyorsa)click_count(analitik için)
SQL Veritabanı (örn: PostgreSQL, MySQL):
Avantajları: İlişkisel veri modeli bu tür basit bir eşleşme için çok uygundur. ACID özellikleri sayesinde yüksek veri tutarlılığı sağlar. short_code üzerinde benzersiz indeksleme, çarpışmaları önler. Analitik sorgular (örneğin, belirli bir kullanıcının tüm kısaltmaları) için güçlüdür.
Dezavantajları: Yatay ölçeklendirme (sharding) daha karmaşıktır. Yüksek okuma/yazma throughput'unda darboğaz yaşayabilir. 100.000 yönlendirme/saniye gibi hedefler için tek bir SQL veritabanı yeterli olmayabilir, bu durumda sharding kaçınılmaz hale gelir.
NoSQL Veritabanı (örn: Apache Cassandra, DynamoDB, MongoDB):
Avantajları: Doğası gereği yüksek ölçeklenebilirlik sunar, yatay olarak kolayca genişleyebilir. Yüksek okuma ve yazma hacimlerini kolayca kaldırabilir. Genellikle daha düşük gecikme süreleri sunar. Anahtar-değer (key-value) tabanlı NoSQL veritabanları, short_code -> original_url eşlemesi için idealdir.
Dezavantajları: ACID garantileri genellikle daha zayıftır (eventual consistency daha yaygındır). Kompleks sorgular (join'ler vb.) ya hiç desteklenmez ya da performansları düşüktür. Öğrenme eğrisi daha dik olabilir. Eğer tıklama analizi gibi detaylı raporlama gereksinimleri varsa, ayrı bir analitik depolama çözümüne ihtiyaç duyulabilir.
Bizim durumumuzda, 100.000 yönlendirme/saniye hedefi göz önüne alındığında, ana veri deposu olarak Cassandra gibi, anahtar-değer depolamaya uygun, yüksek yazma ve okuma throughput'una sahip bir NoSQL veritabanı tercih edilebilir. short_code'u partition key olarak kullanabiliriz. Eğer karmaşık analitik raporlama gereksinimlerimiz olsaydı, bu verileri ayrı bir veri ambarına (Data Warehouse) taşıyabilir ve oradan sorgulayabilirdik.
Yönlendirme Performansı ve Ölçeklenebilirlik
Kısaltılan bir URL'nin orijinaline yönlendirilmesi, TinyURL benzeri bir servisin en sık kullanılan ve dolayısıyla en kritik performans gereksinimi olan özelliğidir. 100.000 yönlendirme/saniye hedefine ulaşmak için güçlü bir önbellekleme stratejisi ve dağıtık bir mimari elzemdir.
Önbellekleme (Caching):
En sık yönlendirilen URL'ler için bir önbellek katmanı olmazsa olmazdır. Yönlendirme servisi, gelen kısa kodu önce önbellekte arar. Eğer bulursa, doğrudan önbellekten yönlendirmeyi yapar. Bu, veritabanı yükünü büyük ölçüde azaltır ve gecikmeyi düşürür. Redis veya Memcached, bu tür dağıtık önbellekleme için popüler seçeneklerdir.
Önbellek Stratejisi:
- Write-through: Bir URL kısaltıldığında, hem veritabanına hem de önbelleğe yazılır. Bu, yeni kısaltmaların anında önbelleğe alınmasını sağlar.
- Read-through: Bir kısa kod önbellekte bulunamadığında, veritabanından okunur ve daha sonra önbelleğe yazılır. Bu, önbelleğin popüler URL'lerle otomatik olarak dolmasını sağlar.
- Time-To-Live (TTL): Önbellekteki öğelerin belirli bir süre sonra otomatik olarak silinmesini sağlayan bir mekanizmadır. Bu, önbelleğin güncel kalmasına ve kullanılmayan URL'lerin bellekte yer kaplamamasına yardımcı olur. Örneğin, 24 saatlik bir TTL ayarlanabilir.
Dağıtık Mimari ve Yük Dengeleme:
Yönlendirme servisinin birden fazla örneğini çalıştırmak ve gelen istekleri bu örneklere dağıtmak için bir yük dengeleyici kullanmak zorunludur. Yük dengeleyiciler, sunucuların aşırı yüklenmesini önler ve hizmet kesintilerini minimize eder. Eğer coğrafi olarak dağıtılmış kullanıcı tabanına sahipsek, global yük dengeleyiciler (örneğin DNS tabanlı) kullanarak istekleri kullanıcılara en yakın veri merkezlerine yönlendirebiliriz. Bu, yönlendirme gecikmesini daha da azaltır.
CDN Kullanımı:
Normalde URL yönlendirmeleri için CDN kullanılmaz, çünkü CDN'ler statik içerik için daha uygundur. Ancak, eğer kısa URL'ler için özel bir alan adımız varsa ve bu alan adının DNS çözümlemesini hızlandırmak veya HTTP isteğini coğrafi olarak en yakın sunucuya yönlendirmek istersek, CDN'nin bazı özelliklerinden faydalanılabilir. Yönlendirme mantığı CDN'de değil, bizim uygulama sunucularımızda kalacaktır.
Performans Optimizasyonları:
- HTTP 301 (Moved Permanently) vs. 302 (Found): Yönlendirme için genellikle 301 kullanmak, tarayıcıların ve arama motorlarının kısa URL'leri önbellekleyerek daha sonra doğrudan orijinal URL'ye gitmesini sağlar. Ancak bu, istatistik toplama yeteneğimizi kısıtlayabilir. Eğer her tıklamayı saymak istiyorsak, 302 veya kendi sunucumuzda bir "meta refresh" kullanarak yönlendirme yapabiliriz.
- Minimum Veritabanı Gecikmesi: Eğer NoSQL veritabanı kullanıyorsak, partition key'in doğru seçildiğinden ve indekslemelerin optimum olduğundan emin olmalıyız.
Bu detaylar, sistemin sadece "çalışmasını" değil, aynı zamanda belirlenen performans hedeflerine ulaşmasını da sağlar.
İleri Düzey İpuçları ve Yaygın Hatalardan Kaçınma Yolları
Bir sistemin temelini attıktan ve kritik bileşenlerini tasarladıktan sonra, daha deneyimli mühendislerin göz önünde bulundurduğu ileri düzey konulara odaklanmak, tasarımınızın sağlamlığını ve uzun ömürlülüğünü artırır. Bu ipuçları, mülakatlarda rakiplerinizden sıyrılmanızı sağlayacak detaylardır.
Önbellekleme ve TTL Stratejileri
Önbellekleme, sistem performansının kilit noktalarından biridir, ancak yanlış kullanıldığında tutarlılık sorunlarına yol açabilir. Sık erişilen verileri önbellekte tutmak harikadır, ancak bu verilerin güncelliğini nasıl sağlayacağız? TTL (Time-To-Live), önbellekteki bir öğenin ne kadar süreyle geçerli kalacağını belirler. TinyURL örneğinde, bir kısa URL oluşturulduğunda ve önbelleğe alındığında, bunun bir TTL'si olmalıdır. Eğer bir URL'nin tıklanma istatistiklerini gerçek zamanlı olarak güncellemek istiyorsak, bu, önbellekteki tıklama sayısının veritabanındaki ile tutarlı olması gerektiği anlamına gelir. Ancak URL yönlendirmesi için genellikle orijinal URL değişmez, bu yüzden daha uzun bir TTL veya TTL'siz önbellekleme tercih edilebilir.
Önbellek Geçersiz Kılma (Cache Invalidation): Bu, dağıtık sistemlerdeki en zorlu problemlerden biridir. Eğer bir orijinal URL'nin hedefini değiştirme veya kısa URL'yi silme gibi bir senaryo olsaydı, önbellekteki eski verilerin anında geçersiz kılınması gerekirdi. Bunun için bir mesaj kuyruğu (örneğin Kafka) kullanarak, bir veri değiştiğinde önbellekleme servisine bir mesaj gönderebilir ve ilgili önbellek öğesini silebiliriz. Ancak TinyURL'de genellikle kısaltılmış URL'ler değişmez, bu da bu problemi basitleştirir. Yine de, sistem genelinde tutarlılık garantileri için bu mekanizmaları bilmek önemlidir.
Dağıtık Sistemlerde Tutarlılık ve Hata Yönetimi
Dağıtık sistemler doğası gereği hata yapmaya meyillidir. Ağ kesintileri, sunucu arızaları veya veri tutarsızlıkları her an meydana gelebilir. Bu nedenle, hata yönetimi ve tutarlılık stratejileri büyük önem taşır.
- Eventual Consistency vs. Strong Consistency: TinyURL'de, bir URL kısaltıldığında hemen yönlendirilebilir olması kritik bir gereksinimdir. Bu,
short_codeveoriginal_urleşleşmesinin strong consistency ile depolanması gerektiğini gösterir. Ancak tıklama istatistikleri gibi veriler için eventual consistency kabul edilebilir; yani bir tıklamanın sayılmasının birkaç saniye gecikmesi sorun teşkil etmez. Seçtiğimiz veritabanı ve önbellek stratejileri bu tutarlılık gereksinimlerini karşılamalıdır. - Retries (Yeniden Denemeler): Bir servis başka bir servisi çağırırken geçici ağ sorunları veya servis yoğunluğu nedeniyle hatalarla karşılaşabilir. Bu durumlarda, isteği birkaç kez belirli aralıklarla (exponential backoff) yeniden denemek, geçici hataların üstesinden gelmeye yardımcı olur.
- Circuit Breakers (Devre Kesiciler): Bir servisin sürekli olarak hata verdiği durumlarda, diğer servislerin bu hatalı servisi sürekli çağırmasını engellemek için devre kesici deseni kullanılır. Bu, hatalı servise iyileşme zamanı tanır ve çağıran servisin de kaynaklarını tüketmesini engeller.
- Idempotency (Tekrarlanabilirlik): Özellikle kısaltma isteği gibi yazma işlemlerinde, bir istek birden fazla kez işlense bile sistemin durumu değişmemelidir. Örneğin, aynı uzun URL için birden fazla kısaltma isteği gelirse, her seferinde yeni bir kısa URL oluşturmak yerine, var olanı döndürmek idempotency'nin bir örneğidir.
Monitoring, Loglama ve Analitik
Bir sistemin üretim ortamında başarılı bir şekilde çalışabilmesi için sürekli izlenmesi, loglanması ve analitik veriler toplaması gerekir. Bu, sorunları proaktif olarak tespit etmenizi, performans darboğazlarını gidermenizi ve kullanıcı davranışlarını anlamanızı sağlar.
- Monitoring (İzleme): Sistem metriklerini (CPU kullanımı, bellek, disk G/Ç, ağ trafiği, istek gecikmeleri, hata oranları vb.) sürekli olarak izlemek için Prometheus, Grafana gibi araçlar kullanılır. Bu sayede, performans sorunları veya anormallikler anında tespit edilebilir.
- Loglama (Logging): Uygulama ve sistem loglarını merkezi bir yerde toplamak (örn: ELK Stack - Elasticsearch, Logstash, Kibana). Bu, hata ayıklama, güvenlik denetimi ve sorun giderme için hayati önem taşır. Her bir isteğin loglanması, hatalı davranışların veya şüpheli aktivitelerin izini sürmeyi kolaylaştırır.
- Analitik (Analytics): Tıklama sayıları, coğrafi dağılım, kullanılan tarayıcılar gibi verileri toplamak ve görselleştirmek, iş kararları için değerli içgörüler sağlar. TinyURL'de her yönlendirme isteğini bir mesaj kuyruğuna (örneğin Kafka) göndermek ve bu kuyruktan verileri okuyan ayrı bir analitik servisi oluşturmak iyi bir yaklaşımdır. Bu analitik servisi, verileri bir veri ambarına (Data Warehouse) veya analitik veritabanına (örn: ClickHouse) kaydedebilir.
Bu ileri düzey konular, sadece sistemin çalışmasını sağlamakla kalmaz, aynı zamanda onun güvenilir, performanslı ve yönetilebilir olmasını da garanti eder. Mülakatlarda bu konulara değinmek, tasarıma geniş bir perspektiften yaklaştığınızı ve gerçek dünya operasyonel zorluklarının farkında olduğunuzu gösterir.
Sonuç: Daha İyi Bir Sistem Tasarımcısı Olmak İçin
Bu makalede, "5 dakikalık" gibi görünen ancak derinlemesine mühendislik bilgisi gerektiren sistem tasarım sorularının neden pek çok mühendisin zorlandığı bir alan olduğunu ve TinyURL örneği üzerinden adım adım nasıl başarılı bir tasarım yapılabileceğini inceledik. Temel prensiplerden ileri düzey konulara kadar birçok noktaya değindik. Özetle, sistem tasarımında başarılı olmak için sadece teknik bilgiye sahip olmak yeterli değildir; aynı zamanda yapılandırılmış bir düşünce süreci, doğru soruları sorma yeteneği ve farklı mimari kararların takaslarını anlama becerisi de kritik öneme sahiptir.
Unutmayın, iyi bir sistem tasarımı tek seferde mükemmel olmaz. Sürekli iyileştirme, yeniden değerlendirme ve değişen gereksinimlere uyum sağlama sürecidir. Mülakatlarda da benzer bir esnekliği ve adaptasyonu göstermek, mülakatçıyı etkileyecektir. Yüksek seviye mimariden başlayıp, kritik bileşenlere derinlemesine inmek ve ardından operasyonel kaygıları (hata yönetimi, izleme, ölçeklenebilirlik) ele almak, sağlam bir tasarım sunmanın anahtarıdır. Bu süreçte karşılaştığınız her kararın ardındaki mantığı ve nedenini açıklayabilmeniz, kendinize olan güveninizi ve bilginizi yansıtacaktır.
Gerçek dünya senaryolarında, bu tür sistemler genellikle çok daha karmaşık hale gelir. Örneğin, güvenlik katmanları, çoklu veri merkezleri, CI/CD süreçleri, A/B testleri gibi birçok ek faktör devreye girer. Ancak bir mülakat bağlamında, bu makalede ele aldığımız prensipler ve teknikler, temel bir sistem tasarımını başarıyla tamamlamanız için güçlü bir temel oluşturacaktır. Pratik yapmaya devam edin, farklı sistem tasarım sorularını inceleyin ve her zaman "neden" sorusunu sormaktan çekinmeyin. Bu, sizi sadece daha iyi bir sistem tasarımcısı değil, aynı zamanda daha iyi bir mühendis yapacaktır.
Sıkça Sorulan Sorular
S1: Neden kısa URL'ler için özel bir ID üreticiye ihtiyaç duyarız?
C1: Rastgele kısa kod üretimi, özellikle yüksek hacimli sistemlerde çarpışma (collision) olasılığını artırır ve bu da veritabanı kontrolleri nedeniyle performansı düşürür. Otomatik artan veritabanı ID'leri dağıtık sistemlerde benzersizliği garanti edemez çünkü farklı sunucular veya veritabanı parçaları aynı ID'leri üretebilir. Dağıtık bir ID üretici (örneğin Snowflake benzeri bir algoritma) kullanarak hem benzersizliği garanti ederiz hem de kod oluşturma sürecindeki gecikmeyi ve veritabanı yükünü azaltırız.
S2: Veritabanı sharding'i (parçalama) TinyURL'de nasıl uygulanabilir?
C2: Sharding, veritabanını daha küçük, yönetilebilir parçalara (shard'lara) bölerek yatay ölçeklendirmeyi sağlar. TinyURL'de short_code, sharding için iyi bir anahtar (shard key) olabilir. Örneğin, kısa kodun ilk birkaç karakterine göre verileri farklı shard'lara dağıtabiliriz. Bu, her bir shard'ın daha az veri işlemesini ve dolayısıyla daha hızlı yanıt vermesini sağlar. Ancak, bir shard'daki verilerin dengeli dağılmasını sağlamak ve shard'lar arası sorguları (eğer varsa) yönetmek karmaşık olabilir.
S3: Önbellekleme stratejileri arasında ne farklar vardır?
C3: Başlıca önbellekleme stratejileri Read-Through, Write-Through ve Write-Back'tir. Read-Through'da, veri önbellekte yoksa veritabanından okunur ve önbelleğe yazılır. Write-Through'da, veri hem veritabanına hem de önbelleğe aynı anda yazılır. Write-Back'te ise veri önce önbelleğe yazılır, ardından asenkron olarak veritabanına kaydedilir. TinyURL gibi sistemlerde hızlı okumalar (yönlendirmeler) için Read-Through ve yeni kısaltmaların hemen erişilebilir olması için Write-Through stratejileri yaygın olarak kullanılır.
S4: Custom URL'ler (kullanıcının kendi istediği kısaltma) nasıl ele alınır?
C4: Custom URL'ler, kullanıcıların istedikleri kısa kodu belirlemesine olanak tanır. Bu durumda, kullanıcı tarafından sağlanan kısa kodun veritabanında daha önce kullanılıp kullanılmadığını kontrol etmemiz gerekir. Eğer kullanılmışsa, kullanıcıya bir hata mesajı dönülür. Bu özellik, benzersizlik kontrolü açısından ek bir yük getirir ve çarpışma riski oluşturabileceği için dikkatli yönetilmelidir. Ayrıca, bazı özel veya ayrılmış anahtar kelimelerin (örneğin "admin", "login") custom URL olarak kullanılması engellenmelidir.
S5: Sistemimizin bir DDoS saldırısına karşı dayanıklılığını nasıl artırabiliriz?
C5: DDoS saldırılarına karşı korunmak için birden fazla katmanda savunma mekanizmaları uygulanabilir:
- Hız Sınırlama (Rate Limiting): IP adresi veya kullanıcı bazında belirli bir zaman diliminde yapılabilecek istek sayısını sınırlayarak aşırı yüklenmeyi önler.
- CDN ve WAF (Web Application Firewall): CDN'ler trafiği dağıtarak ve WAF'lar kötü niyetli istekleri filtreleyerek saldırıların sunuculara ulaşmasını engeller.
- Yük Dengeleyiciler: Trafiği birden fazla sunucuya dağıtarak tek bir noktadaki başarısızlık riskini azaltır.
- Otomatik Ölçeklendirme (Auto-scaling): Yoğun trafik anında otomatik olarak daha fazla sunucu başlatarak talebi karşılar.
- Bot Algılama: Gerçek kullanıcıları botlardan ayırarak bot kaynaklı saldırıları engeller.