Bir Milyon Kullanıcıyı Destekleyecek Sistemi Nasıl Tasarlarsınız? Adım Adım X Ölçeğinde Bir Sistem Tasarımı

Sadece kod yazmak yetmez. Peki, sıfırdan başlayıp X gibi bir milyon kullanıcının yükünü kaldırabilecek bir sistemi nasıl tasarlarsınız? Yük dengeleyicilerden veritabanı seçimine, anasayfa akışının optimizasyonundan önbelleğe almaya kadar her adımı, tüm 'neden'leriyle birlikte bu dev rehberde inceliyoruz.
Giriş: Kodun Bittiği, Mimarinin Başladığı Yer
Yazılım geliştirmede belirli bir seviyeye geldiğinizde, yazdığınız kodun kalitesinden çok, o kodun içinde çalıştığı sistemin mimarisi önem kazanmaya başlar. Bir özellik geliştirebilirsiniz, peki ya o özellik saniyede binlerce istek aldığında ne olur? On kullanıcı için çalışan bir sistem, bir milyon kullanıcıya çıktığında neden alevler içinde kalır? İşte bu soruların cevabı 'Sistem Tasarımı' disiplininde yatar.
Bu rehber, soyut bir kavram olan sistem tasarımını, hepimizin bildiği bir örnek üzerinden, 'X benzeri bir servis' üzerinden somutlaştıracak. Sıfırdan başlayıp, bir milyon kullanıcıyı destekleyebilecek, ölçeklenebilir, dayanıklı ve performanslı bir sistemi adım adım, tüm kararların arkasındaki 'neden'lerle birlikte tasarlayacağız. Bu sadece bir mülakat sorusunun cevabı değil, aynı zamanda daha iyi bir mühendis olmanın yol haritasıdır.
Bölüm 1: Temelleri Atmak - Gereksinimleri ve Kısıtlamaları Anlamak
Körlemesine bir yola çıkamayız. Önce ne inşa edeceğimizi ve bu yapının hangi koşullara dayanması gerektiğini netleştirmeliyiz.
Fonksiyonel Gereksinimler (Sistem ne yapmalı?)
- Kullanıcılar, metin ve (isteğe bağlı olarak) bir görsel içeren kısa mesajlar ('Tweet'ler) gönderebilmelidir.
- Kullanıcılar birbirlerini takip edebilmelidir.
- Kullanıcılar, takip ettikleri kişilerin Tweet'lerinden oluşan, kronolojik sıraya göre dizilmiş bir anasayfa akışını ('Timeline') görebilmelidir.
- Kullanıcılar kendi profillerini ve Tweet'lerini görüntüleyebilmelidir.
Fonksiyonel Olmayan Gereksinimler (Sistem nasıl çalışmalı?)
- Yüksek Erişilebilirlik (High Availability): Sistem her zaman ayakta olmalı. %99.99 erişilebilirlik hedeflenmelidir.
- Yüksek Dayanıklılık (High Durability): Gönderilen bir Tweet asla kaybolmamalıdır.
- Düşük Gecikme (Low Latency): Anasayfa akışı çok hızlı, tercihen 200ms altında yüklenmelidir.
- Ölçeklenebilirlik (Scalability): Sistem, kullanıcı sayısı arttıkça artan yükü kaldırabilmelidir.
Bölüm 2: Sayılarla Konuşmak - Kaba Bir Kapasite Tahmini
Mimari kararlarımızı yönlendirecek en önemli şey, karşılaşacağımız yükün boyutudur. Şimdi biraz matematik yapalım.
- Toplam Kullanıcı: 1,000,000
- Günlük Aktif Kullanıcı (DAU): Toplamın %20'si diyelim -> 200,000 DAU.
- Tweet Yazma Oranı: Her aktif kullanıcı günde ortalama 0.5 Tweet atsın -> 100,000 Tweet/gün.
- Saniye Başına Yazma (Write TPS): 100,000 / (24 saat * 3600 sn) ≈ 1.2 TPS. Ancak trafik hiçbir zaman düzgün dağılmaz. Zirve anlarda (peak) bu oranın 10 katı olabileceğini varsayalım -> ~12 TPS (write).
- Okuma/Yazma Oranı: Sosyal medya platformları ezici bir şekilde okuma ağırlıklıdır. Bir kullanıcı bir Tweet atarken, yüzlerce Tweet okur. Ortalama olarak 1 yazmaya karşılık 100 okuma yapıldığını varsayalım (1:100 ratio).
- Saniye Başına Okuma (Read QPS): ~12 TPS (write) * 100 ≈ ~1200 QPS (read). Gördüğünüz gibi, asıl problemimiz okuma yükünü yönetmek.
- Depolama (Metin): Bir Tweet:
id (8 byte) + user_id (8 byte) + içerik (280 karakter * 2 byte/char = 560 byte) + meta (100 byte)≈ 700 byte/Tweet.
Günlük: 100,000 Tweet * 700 byte ≈ 70 MB/gün.
5 Yıllık: 70 MB * 365 * 5 ≈ 128 GB. Bu, yönetilebilir bir boyut. - Depolama (Medya): Tweet'lerin %10'u görsel içerse ve her görsel ortalama 1 MB olsa:
Günlük: 10,000 Tweet * 1 MB = 10 GB/gün.
5 Yıllık: 10 GB * 365 * 5 ≈ 18.25 TB. İşte bu büyük bir rakam. Bu bize, metin ve medya verilerini farklı yerlerde saklamamız gerektiğini söylüyor.
Sonuç: Sistemimiz okuma ağırlıklı (read-heavy) olacak ve medya depolaması için özel bir çözüme ihtiyaç duyacak.
Bölüm 3: Yüksek Seviye Mimari - İlk Çizim
Artık servislerimizi ve aralarındaki ilişkiyi çizebiliriz.
[Kullanıcı] <--> [Yük Dengeleyici (Load Balancer)] <--> [API Gateway]
|
+--------------------+--------------------------+-----------------------+
| | | |
[Kullanıcı Servisi] [Tweet Servisi] [Anasayfa Akış Servisi] [Medya Servisi]
| | | |
[Kullanıcı Veritabanı] [Tweet Veritabanı] [Anasayfa Önbelleği] [Blob Depolama (S3)]
- Yük Dengeleyici: Gelen istekleri birden fazla API sunucusuna dağıtarak tek bir sunucunun aşırı yüklenmesini önler.
- API Gateway: Tüm istekler için tek bir giriş noktasıdır. Kimlik doğrulama, istek sınırlama (rate limiting) gibi ortak görevleri yönetir ve isteği ilgili servise yönlendirir.
- Servisler: Her servis, belirli bir iş alanından sorumlu küçük, bağımsız birimlerdir (Mikroservis Mimarisi).
Bölüm 4: Veritabanı Seçimi ve Şema Tasarımı
Bu ölçekte, veritabanı kararları sistemin kaderini belirler. SQL mi, NoSQL mi?
Verilerimiz (kullanıcılar, tweetler, takipler) oldukça ilişkisel. Bir kullanıcının Tweet'leri var, kullanıcılar birbirini takip ediyor. Bu ilişkileri yönetmek için İlişkisel Veritabanları (SQL), örneğin PostgreSQL, başlangıç için harika bir seçimdir. Güçlü tutarlılık (consistency) garantisi verir ve esnek sorgulama imkanı sunar. NoSQL (örn: Cassandra) daha büyük ölçeklerde (milyarlarca Tweet) anasayfa akışı için düşünülebilir, ancak 1 milyon kullanıcı için SQL hem daha basit hem de daha etkilidir.
Veritabanı Şeması (PostgreSQL)
CREATE TABLE Users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE Tweets (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES Users(id),
content VARCHAR(280) NOT NULL,
media_url VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE Follows (
follower_id BIGINT NOT NULL REFERENCES Users(id),
followee_id BIGINT NOT NULL REFERENCES Users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (follower_id, followee_id) -- Bir kullanıcı diğerini sadece bir kez takip edebilir.
);
Bölüm 5: Derinlemesine Bakış - Anasayfa Akışını Tasarlamak (En Zor Kısım)
Sistemimizin en kritik ve en zorlu kısmı, bir kullanıcının anasayfa akışını oluşturmaktır. Okuma yükümüzün büyük çoğunluğu buradan gelir.
Yöntem 1: Saf Yaklaşım (Okuma Sırasında Hesaplama - Pull Model)
Kullanıcı anasayfasını istediğinde, her seferinde veritabanından hesaplama yaparız.
- Kullanıcının takip ettiği kişilerin listesini al:
SELECT followee_id FROM Follows WHERE follower_id = :current_user_id. - Bu kişilerin son Tweet'lerini çek:
SELECT * FROM Tweets WHERE user_id IN (takip_edilenler_listesi) ORDER BY created_at DESC LIMIT 50.
Problem: Bu yöntem, kullanıcının takip ettiği kişi sayısı arttıkça korkunç derecede yavaşlar. Binlerce kişiyi takip eden bir kullanıcı için yapılacak IN sorgusu veritabanını felç edebilir. Bu ölçeklenemez bir çözümdür.
Yöntem 2: Önceden Hesaplama (Yazma Sırasında Dağıtım - Push Model)
Bu, profesyonel sistemlerin kullandığı yöntemdir. Bir kullanıcı Tweet attığında, o Tweet'i tüm takipçilerinin anasayfa akışlarına "iteriz".
Bu akışları nerede saklayacağız? Hızlı okuma için ideal bir yer: Önbellek (Cache). Redis gibi bellek-içi (in-memory) bir veritabanı bu iş için mükemmeldir. Her kullanıcının anasayfa akışı için bir Redis listesi tutarız: timeline:user_id.
- Yazma Akışı:
- Kullanıcı A bir Tweet atar.
- Tweet Servisi, Tweet'i PostgreSQL'deki
Tweetstablosuna yazar. - Ardından, Kullanıcı A'nın tüm takipçilerini
Followstablosundan bulur. - Her bir takipçinin (B, C, D...) anasayfa listesine (
timeline:B,timeline:C, ...) yeni Tweet'in ID'sini ekler (LPUSHkomutuyla).
- Okuma Akışı:
- Kullanıcı B anasayfasını istediğinde, Anasayfa Akış Servisi sadece Redis'ten
LRANGE timeline:B 0 49komutuyla ilk 50 Tweet ID'sini okur. Bu, ışık hızında bir işlemdir. - Gerekli Tweet detayları (içerik, yazar adı vb.) bu ID'ler kullanılarak veritabanından veya başka bir önbellekten toplu halde çekilir (Hydration).
- Kullanıcı B anasayfasını istediğinde, Anasayfa Akış Servisi sadece Redis'ten
Yeni Problem: "Ünlü Etkisi" (Celebrity Problem). 500,000 takipçisi olan bir ünlü Tweet attığında ne olur? Yazma anında bu Tweet'i 500,000 kişinin listesine tek tek itmek dakikalar sürebilir ve sistemi yavaşlatabilir. Bu duruma 'fan-out on write' fırtınası denir.
Yöntem 3: Hibrit Yaklaşım (En İyi Çözüm)
Herkes için aynı stratejiyi kullanmak zorunda değiliz.
- Normal Kullanıcılar İçin (örn: < 1000 takipçi): Yöntem 2'yi (Push Model) kullanmaya devam et. Bir kullanıcı Tweet attığında, takipçilerinin Redis listelerine Tweet'i it.
- Ünlüler İçin (örn: > 1000 takipçi): Push yapma! Bir ünlü Tweet attığında, sadece Tweet'i veritabanına yaz.
- Anasayfa Birleştirme: Bir kullanıcı anasayfasını istediğinde:
- Redis'teki önceden hesaplanmış akışını (normal kullanıcıların Tweet'leri) çek.
- Kullanıcının takip ettiği ünlüler listesini ayrı bir yerden (örn: Redis Set) al.
- Bu ünlülerin son Tweet'lerini veritabanından veya önbellekten çek.
- Bu iki listeyi (kullanıcının kendi akışı ve ünlülerin Tweet'leri) uygulama katmanında birleştir, tarihe göre sırala ve kullanıcıya sun.
Bu hibrit model, hem normal kullanıcılar için okuma hızını korur hem de ünlülerin yazma performansını felç etmesini engeller. Bu, sistem tasarımında 'trade-off' yapmanın mükemmel bir örneğidir.
Bölüm 6: Sistemi Güçlendirmek - Ölçeklendirme ve Darboğazları Aşmak
Temel akışlarımız tamam. Şimdi sistemi daha dayanıklı hale getirelim.
Asenkron İşlemler için Mesaj Kuyrukları (Message Queues)
Bir kullanıcı Tweet attığında, takipçilerine bu Tweet'i dağıtma işlemi (fan-out) saniyeler sürebilir. Kullanıcıyı bu süre boyunca bekletmek kötü bir deneyimdir. Çözüm: Mesaj Kuyrukları (örn: RabbitMQ, Kafka).
Yeni akış: Kullanıcı Tweet atar -> Tweet servisi Tweet'i veritabanına yazar ve kuyruğa {"tweet_id": 123, "user_id": 456} gibi bir mesaj bırakır -> Kullanıcıya anında "Başarılı" yanıtı döner. Arka planda çalışan ayrı bir 'worker' servisi bu kuyruktan mesajları okur ve yavaş olan dağıtım işini yapar. Bu, kullanıcı deneyimini iyileştirir ve sistemi daha esnek hale getirir.
Her Yerde Önbellek (Caching)
Okuma ağırlıklı sistemlerde önbellek kraldır. Sadece anasayfa akışını değil, şunları da önbelleğe almalıyız:
- Kullanıcı Profilleri: Redis'te
user:user_idanahtarıyla. - Tweet Nesneleri: Redis'te
tweet:tweet_idanahtarıyla. - Takipçi/Takip Edilen Listeleri: Redis Set'leri ile.
Kural: Veritabanına gitmeden önce her zaman önbelleği kontrol et.
Medya için CDN (Content Delivery Network)
Kullanıcıların yüklediği görselleri kendi sunucularımızdan sunmak, bant genişliğimizi tüketir. Bunun yerine, görselleri Blob Depolama (örn: AWS S3) üzerinde saklarız ve bir CDN (örn: Cloudflare, AWS CloudFront) aracılığıyla sunarız. CDN, görselleri kullanıcılara en yakın coğrafi konumlardaki sunuculardan sunarak yükleme sürelerini dramatik şekilde düşürür.
Veritabanı Ölçeklendirme
- Okuma Replikaları (Read Replicas): Okuma yükümüz çok fazla olduğu için, ana veritabanımızın salt okunur kopyalarını (replikalar) oluştururuz. Tüm yazma işlemleri ana veritabanına, tüm okuma işlemleri ise replikalara yönlendirilir. Bu, okuma darboğazını büyük ölçüde hafifletir.
- Parçalama (Sharding): Verilerimiz tek bir makineye sığmayacak kadar büyüdüğünde (bu, 1 milyon kullanıcı için pek olası değil ama bilmekte fayda var), veritabanını parçalara (shard'lara) ayırırız. Örneğin, kullanıcıları ID'lerine göre farklı veritabanı sunucularına bölebiliriz. Bu, yatay ölçeklenmenin son noktasıdır.
Sonuç: Hiç Bitmeyen Bir Yolculuk
Tebrikler! Sıfırdan başladık ve basit gereksinimlerden yola çıkarak; yük dengeleyiciler, mikroservisler, SQL ve NoSQL (Redis) veritabanları, mesaj kuyrukları, CDN'ler ve veritabanı replikasyonu gibi kavramları kullanarak bir milyon kullanıcıyı kaldırabilecek karmaşık ama sağlam bir sistem tasarladık. Gördüğünüz gibi, sistem tasarımı, "doğru" bir cevap bulmaktan çok, farklı çözümler arasındaki değiş tokuşları (trade-off'ları) anlamak ve projenin gereksinimlerine en uygun kararları vermektir. Bu, sürekli öğrenme ve evrimleşme gerektiren bir yolculuktur.
