Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

Instagram gibi bir uygulamayı tasarlarken mesele sadece fotoğraf yüklemek değildir. Asıl zorluk, kullanıcıların birbirini takip ettiği, gönderilere yorum ve beğeni bıraktığı, milyonlarca kişiye kişiselleştirilmiş akış gösterilen bir sistemi ölçeklenebilir hale getirmektir. Bu yazıda Instagram benzeri bir sistemin temel veri modelini, servis mimarisini ve newsfeed üretme yaklaşımını Mermaid diyagramlarıyla adım adım kuracağız.
Instagram gibi bir uygulamayı tasarlarken mesele sadece fotoğraf yüklemek değildir. Asıl zorluk, kullanıcıların birbirini takip ettiği, gönderilere yorum ve beğeni bıraktığı, milyonlarca kişiye kişiselleştirilmiş akış gösterilen bir sistemi ölçeklenebilir hale getirmektir. Bu yazıda Instagram benzeri bir sistemin temel veri modelini, servis mimarisini ve newsfeed üretme yaklaşımını Mermaid diyagramlarıyla adım adım kuracağız.
Bir sistem tasarımı görüşmesinde en kritik hata, ürünü bildiğini varsayıp bütün özellikleri tasarlamaya çalışmaktır. Instagram dediğimizde akla hikayeler, mesajlaşma, keşfet, reels, reklamlar ve daha birçok şey gelir. Ama görüşmede genellikle beklenen şey daha dar ve tasarlanabilir bir çekirdektir.
Bu yazıda dört temel özelliğe odaklanacağız: fotoğraf gönderisi oluşturma ve görüntüleme, gönderilere beğeni ve yorum ekleme, kullanıcı takip sistemi ve kişiselleştirilmiş newsfeed üretimi.
flowchart TD
A["Instagram Benzeri Sistem"] --> B["Gönderi ve Görsel Yönetimi"]
A --> C["Beğeni ve Yorumlar"]
A --> D["Takip İlişkileri"]
A --> E["Kullanıcı Newsfeed'i"]
B --> B1["Görsel dosyası saklama"]
B --> B2["CDN üzerinden hızlı erişim"]
C --> C1["Gönderi beğenisi"]
C --> C2["Yorum beğenisi"]
C --> C3["Gönderiye yorum"]
D --> D1["Kimi takip ediyorum?"]
D --> D2["Beni kim takip ediyor?"]
E --> E1["Akışı okuma"]
E --> E2["Akışı önceden hesaplama"]Bu diyagram sistemin ana kapsamını gösteriyor. Böyle düşünmek tasarımı sadeleştirir çünkü her servisin hangi probleme cevap verdiği baştan belli olur.
Instagram’ın en görünür tarafı görsellerdir. Burada dosyanın kendisini veritabanına koymak genellikle iyi fikir değildir. Büyük medya dosyaları için object storage veya dağıtık dosya sistemi kullanılır. Object storage, yani şöyle düşün: dosyayı bir tablo satırı gibi değil, erişilebilir bir nesne olarak saklayan ucuz ve ölçeklenebilir depolama katmanıdır.
Görsel yüklendiğinde sistem dosyayı depolar, dosyanın URL veya object key bilgisini ise post verisinde tutar. Kullanıcı görseli görüntülediğinde dosya doğrudan CDN üzerinden sunulur. CDN, yani şöyle düşün: kullanıcıya en yakın lokasyondan statik içeriği veren dağıtık önbellek ağıdır.
flowchart LR
U["Mobil Uygulama"] --> G["Gateway"]
G --> P["Post Service"]
P --> DB[("Post Metadata DB")]
P --> S[("Object Storage")]
S --> CDN["CDN"]
CDN --> U
DB --> M["post_id, user_id, image_url, created_at"]Bu yapıda veritabanı gönderinin kim tarafından, ne zaman oluşturulduğunu ve görselin nerede olduğunu bilir. Görselin kendisi ise daha ucuz ve hızlı servis edilebilir bir depolama katmanında durur.
Beğeni sistemi ilk bakışta basit görünür: bir kullanıcı bir gönderiyi beğenir. Ama hemen ardından şu soru gelir: yorumlar da beğenilebilir mi? Eğer evetse, beğeni sadece gönderiye bağlı olmamalıdır. Beğenilen şeyin türünü ayrıca tutmak daha temiz bir model sağlar.
Burada polymorphic association kullanabiliriz. Polymorphic association, yani şöyle düşün: aynı tablo satırı farklı türde nesnelere bağlanabilir; bazen gönderiye, bazen yoruma işaret eder.
erDiagram
USER ||--o{ POST : creates
USER ||--o{ COMMENT : writes
USER ||--o{ LIKE : performs
POST ||--o{ COMMENT : has
POST ||--o{ ACTIVITY_COUNTER : counted_by
COMMENT ||--o{ ACTIVITY_COUNTER : counted_by
USER {
string user_id PK
string username
string profile_image_url
datetime created_at
}
POST {
string post_id PK
string user_id FK
string image_url
string caption
datetime created_at
}
COMMENT {
string comment_id PK
string post_id FK
string user_id FK
string text
datetime created_at
}
LIKE {
string like_id PK
string user_id FK
string parent_id
string parent_type
boolean active
datetime created_at
}
ACTIVITY_COUNTER {
string parent_id PK
int like_count
int comment_count
datetime updated_at
}Bu ER diyagramında LIKE.parent_type alanı beğenilen nesnenin gönderi mi yorum mu olduğunu belirtir. ACTIVITY_COUNTER ise sık okunan sayaçları ayrı bir yerde tutar; böylece her newsfeed yüklemesinde milyonlarca beğeni satırı sayılmaz.
Bir gönderinin kaç beğeni aldığını her seferinde COUNT(*) ile hesaplamak küçük ölçekte çalışır. Fakat newsfeed içinde 20 gönderi gösteriyorsan ve her gönderi için ayrı sayım yapıyorsan sistem gereksiz yere zorlanır.
Aggregation, yani şöyle düşün: ham olaylardan türetilmiş özet bilgidir. Beğeni sayısı aslında tek tek beğeni satırlarından hesaplanabilir, ama sürekli hesaplamak pahalıdır. Bu yüzden sayaçları ayrı bir tabloda veya cache katmanında tutmak daha pratiktir.
flowchart TD
A["Kullanıcı gönderiyi beğenir"] --> B["Like kaydı oluştur veya aktif hale getir"]
B --> C{"Parent type nedir?"}
C -->|"post"| D["Post için sayaç artır"]
C -->|"comment"| E["Comment için sayaç artır"]
D --> F[("Activity Counter")]
E --> F
F --> G["Newsfeed okumasında hızlı gösterim"]Bu akışta beğeni olayı hem detaylı kayıt olarak saklanır hem de okunması hızlı bir sayaç güncellenir. Böylece sistem hem geçmişi bilir hem de kullanıcıya hızlı cevap verir.
Takip sistemi için tek bir tablo çoğu temel ihtiyacı karşılar. Bir kullanıcı başka bir kullanıcıyı takip ettiğinde follower_user_id ve followee_user_id alanlarıyla ilişki kaydedilir.
erDiagram
USER ||--o{ FOLLOW : follows_as_follower
USER ||--o{ FOLLOW : followed_as_followee
USER {
string user_id PK
string username
}
FOLLOW {
string follower_user_id FK
string followee_user_id FK
datetime created_at
boolean active
}Bu model iki önemli soruya cevap verir: “Ben kimleri takip ediyorum?” ve “Beni kimler takip ediyor?” İlk soru için follower_user_id, ikinci soru için followee_user_id üzerinden sorgu yapılır. Büyük ölçekte bu iki alan için ayrı index kullanmak gerekir. Index, yani şöyle düşün: veritabanının ilgili satırları tüm tabloyu taramadan bulmasını sağlayan arama yapısıdır.
Uygulama tarafında mobil istemci doğrudan veritabanlarına gitmez. İstek önce gateway katmanına gelir. Gateway, yani şöyle düşün: dış dünyadan gelen istekleri karşılayan, kimlik doğrulama, yönlendirme ve protokol dönüşümü gibi işleri üstlenen giriş kapısıdır.
Ardından istek ilgili servise yönlendirilir. Newsfeed için User Feed Service, gönderiler için Post Service, takip ilişkileri için Follow Service gibi ayrımlar yapılabilir.
flowchart LR
M["Mobil Uygulama"] --> GW["Gateway"]
subgraph Edge["Giriş Katmanı"]
GW --> LB["Load Balancer / Service Registry Snapshot"]
end
subgraph Services["İç Servisler"]
GW --> UFS["User Feed Service"]
UFS --> PS["Post Service"]
UFS --> FS["Follow Service"]
PS --> IS["Image Service"]
UFS --> AS["Activity Service"]
end
subgraph Storage["Veri Katmanı"]
PS --> PDB[("Post DB")]
FS --> FDB[("Follow DB")]
UFS --> CACHE[("Feed Cache")]
IS --> OBJ[("Object Storage")]
AS --> ADB[("Activity DB")]
endBu yapı servisleri birbirinden ayırır. User Feed Service doğrudan tüm veriyi sahiplenmez; gerektiğinde Post Service ve Follow Service gibi uzman servislerden bilgi alır.
En basit yaklaşım şudur: kullanıcı akışını açınca önce takip ettiği kişileri bul, sonra bu kişilerin son gönderilerini al, sonra sırala ve ilk 20 tanesini dön. Küçük kullanıcı sayısında çalışır ama büyüdükçe pahalı hale gelir.
sequenceDiagram
participant App as Mobil Uygulama
participant Gateway as Gateway
participant Feed as User Feed Service
participant Follow as Follow Service
participant Post as Post Service
App->>Gateway: getUserFeed(user_id)
Gateway->>Feed: getUserFeed(user_id)
Feed->>Follow: getFollowees(user_id)
Follow-->>Feed: takip edilen kullanıcılar
loop Her takip edilen kullanıcı için
Feed->>Post: getRecentPosts(user_id)
Post-->>Feed: son gönderiler
end
Feed->>Feed: gönderileri birleştir ve sırala
Feed-->>Gateway: ilk 20 gönderi
Gateway-->>App: newsfeedBu diyagram “fan-out on read” yaklaşımını gösterir. Fan-out on read, yani şöyle düşün: kullanıcı akışı istediği anda birçok kaynaktan veri toplayıp sonucu o anda üretirsin. Okuma sayısı çok yüksekse bu yaklaşım servisleri ve veritabanlarını yorar.
Büyük sistemlerde newsfeed genellikle önceden hesaplanır. Bir kullanıcı yeni gönderi oluşturduğunda sistem o kullanıcının takipçilerini bulur ve ilgili takipçilerin feed cache’ine bu gönderiyi ekler.
Buna “fan-out on write” denir. Yani şöyle düşün: gönderi yazıldığı anda maliyeti ödersin, böylece kullanıcı akışı okurken hızlı cevap alır.
flowchart TD
A["Kullanıcı yeni gönderi oluşturur"] --> B["Post Service gönderiyi kaydeder"]
B --> C[("Post DB")]
B --> D["Feed Update Event yayınlanır"]
D --> E["User Feed Service"]
E --> F["Follow Service'ten takipçileri al"]
F --> G["Takipçi listesi"]
G --> H["Her takipçinin feed cache'ine post_id ekle"]
H --> I[("Feed Cache")]
I --> J["Kullanıcı feed açınca ilk 20 kayıt hızlı döner"]Buradaki önemli nokta feed’in tamamını sürekli yeniden üretmemektir. Yeni gönderi geldiğinde sadece ilgili kullanıcıların hazır akış listesine küçük bir ekleme yapılır.
Feed cache içinde her kullanıcı için son gösterilecek gönderilerin kısa bir listesi tutulabilir. Örneğin her kullanıcı için en güncel 20, 50 veya 100 gönderi saklanır. Cache, yani şöyle düşün: sık okunan veriyi ana veritabanına gitmeden hızlıca cevaplamak için kullanılan geçici ve hızlı depolama alanıdır.
flowchart LR
U1["user_1 feed"] --> L1["post_91 → post_84 → post_73"]
U2["user_2 feed"] --> L2["post_91 → post_80 → post_77"]
U3["user_3 feed"] --> L3["post_88 → post_84 → post_60"]
NP["Yeni post_92"] --> F["Takipçileri bul"]
F --> U1
F --> U2Bu diyagram aynı gönderinin birden fazla kullanıcının cache listesine eklenebileceğini gösterir. Cache kaybolursa sistem brute force yöntemle feed’i yeniden hesaplayabilir; bu yüzden cache’i tek gerçek kaynak gibi düşünmemek gerekir.
Fan-out on write her kullanıcı için iyi çalışmaz. Milyonlarca takipçisi olan bir hesap gönderi paylaştığında, bu gönderiyi aynı anda milyonlarca feed listesine yazmaya çalışmak sistemi zorlayabilir.
Bu noktada hibrit model kullanılır. Normal kullanıcılar için gönderi takipçilerin feed cache’ine yazılır. Çok yüksek takipçili hesaplar için ise gönderi herkese tek tek itilmez; kullanıcı feed açtığında bu hesapların son gönderileri ayrıca çekilir ve akışa dahil edilir.
flowchart TD
A["Yeni gönderi"] --> B{"Gönderen yüksek takipçili mi?"}
B -->|"Hayır"| C["Fan-out on write"]
C --> D["Takipçilerin feed cache'ine ekle"]
D --> E["Anlık ve hızlı feed"]
B -->|"Evet"| F["Fan-out sınırlanır"]
F --> G["Gönderi merkezi listede tutulur"]
G --> H["Kullanıcı feed açınca pull modeliyle eklenir"]
H --> I["Hibrit newsfeed"]
E --> IBu model push ve pull yaklaşımını birlikte kullanır. Push, yani şöyle düşün: sistem yeni içeriği kullanıcının hazır alanına kendisi iter. Pull ise kullanıcının ihtiyacı olduğunda veriyi çekmesidir.
Kullanıcıya yeni gönderi geldiğini göstermek için birkaç yöntem vardır. Mobil uygulama belirli aralıklarla sunucuya sorabilir, uzun süre açık kalan bağlantılar kullanılabilir ya da WebSocket ile sunucu istemciye doğrudan mesaj gönderebilir.
WebSocket, yani şöyle düşün: istemci ve sunucu arasında açık kalan çift yönlü bağlantıdır. Sürekli yeni HTTP isteği açmak yerine aynı bağlantı üzerinden güncelleme taşınabilir.
sequenceDiagram
participant Post as Post Service
participant Feed as User Feed Service
participant Notify as Notification Service
participant App as Mobil Uygulama
Post->>Feed: yeni gönderi olayı
Feed->>Feed: ilgili kullanıcıların feed'ini güncelle
Feed->>Notify: yeni içerik bildirimi oluştur
Notify-->>App: WebSocket veya push notification
App->>Feed: güncel feed'i iste
Feed-->>App: hazır feed listesiBu yapı gerçek zaman hissini güçlendirir. Yine de her gönderi için herkese anlık bildirim göndermek doğru değildir; özellikle yüksek takipçili hesaplarda rate limit ve önceliklendirme gerekir.
Tüm parçaları bir araya getirdiğimizde Instagram benzeri çekirdek sistem şu şekilde düşünülebilir: görseller object storage ve CDN ile servis edilir, gönderi ve aktivite bilgileri ayrı servislerde tutulur, takip ilişkileri Follow Service tarafından yönetilir, User Feed Service ise cache tabanlı kişiselleştirilmiş akış üretir.
flowchart TD
App["Mobil Uygulama"] --> Gateway["Gateway"]
Gateway --> PostService["Post Service"]
Gateway --> FeedService["User Feed Service"]
Gateway --> ActivityService["Activity Service"]
Gateway --> FollowService["Follow Service"]
PostService --> PostDB[("Post DB")]
PostService --> ObjectStorage[("Object Storage")]
ObjectStorage --> CDN["CDN"]
ActivityService --> ActivityDB[("Like / Comment DB")]
FollowService --> FollowDB[("Follow DB")]
PostService --> EventBus["Event Bus"]
EventBus --> FeedService
FeedService --> FollowService
FeedService --> FeedCache[("Feed Cache")]
FeedService --> PostService
CDN --> App
FeedCache --> FeedServiceEvent Bus, yani şöyle düşün: servislerin birbirini doğrudan sıkı sıkıya çağırmadan olay yayınlayıp tüketmesini sağlayan ara katmandır. Yeni gönderi olayı Post Service’ten çıkar, User Feed Service bu olayı dinler ve gerekli feed güncellemelerini yapar.
Instagram benzeri bir sistemde asıl mesele tek tek özellikleri yazmak değil, okuma ve yazma maliyetlerini doğru yere taşımaktır. Görseller CDN destekli bir depolama katmanından sunulur, beğeni ve yorum gibi aktiviteler ayrı modellenir, sayaçlar hızlı okuma için önceden tutulur. Takip ilişkisi basit bir tabloyla başlar ama doğru indexlerle ölçeklenir. Newsfeed tarafında ise anlık hesaplama küçük ölçekte işe yarasa da büyük ölçekte cache tabanlı ön hesaplama gerekir. En sağlıklı çözüm genellikle hibrittir: normal kullanıcılar için push, çok yüksek takipçili hesaplar için pull yaklaşımı birlikte kullanılır.
Bu yazı Designing INSTAGRAM: System Design of News Feed videosundan ilham alınarak yazılmıştır.