İçeriğe geç
Seçkin Can Şahin

100 binden 4 milyona: hangi kararlar ölçekte hayatta kalıyor

Udemy’de kurs feed’ini 100 bin kullanıcıdayken yazdım; aynı sistem 4 milyon kullanıcıda ciddi bir değişiklik görmeden çalışıyordu. İki büyüklük mertebesinden hangi tasarım kararlarının sağ çıktığı üzerine.

8 dakikalık okuma

Udemy’de kurs feed’ini yazdığımda şirketin 100 bin kullanıcısı vardı. Aynı sistem, Udemy 4 milyon kullanıcıya ulaştığında ciddi bir değişiklik görmeden çalışıyordu. Yazdığım bildirim sistemi de kullanıcı tabanı 40 kat büyüdükten sonra ayaktaydı.

Bir mühendisin bu aralığı görmesi ender bir şey. Çoğu kişi ya büyümüş bir sistemi devralır ya da kendi yazdığı sistem büyümeden önce şirketten ayrılır. İki büyüklük mertebesi boyunca kendi kararlarınızın sınandığını izlemek, hangi kararın neden dayandığını tahminle değil kayıtla bilmek demektir. Sağ kalanların ortak yanı da orada görünüyor: hepsinde büyüyen sayı, tasarımın dışında değil içindeydi.

Faturayı hangi sayıya bağladınız

Bir akışın iki kurulma yolu vardır. Ya kullanıcı sayfayı açtığında hesaplarsınız — takip ettiği kursları bulur, her birinin son olaylarını çeker, sıralar, gösterirsiniz. Ya da olay olduğu anda hesaplarsınız: o olayı görecek herkesin akışına ayrı ayrı yazar, kullanıcı sayfayı açtığında hazır listeyi okursunuz.

Birincisi ikincisinden kolay yazılır. Tek sorgu, tek doğruluk kaynağı, bozulduğunda nerede bozulduğu belli. 100 bin kullanıcıda da gayet iyi çalışır. Ben ikincisini seçtim, kullanıcı başına kalıcı toplulaştırmayı. O gün bu tercih gereksiz bir külfet gibi duruyordu.

Sebebi, iki tasarımın faturasının farklı sayılara bağlı olmasıydı. Okuma anında hesaplayan tasarımın maliyeti, sayfayı açan kullanıcı sayısıyla ve her kullanıcının takip ettiği şeyin genişliğiyle birlikte artar. Bir akış ürününde okuma sayısı yazma sayısının kat kat üstündedir; yani faturanızı sistemin en hızlı büyüyen sayısına bağlamış olursunuz. Yazma anında hesaplayan tasarımın maliyeti ise tek bir olayın kaç kişiye gideceğine bağlıdır. Udemy’de o sayının doğal bir sınırı vardı: bir kursun içinde olan biteni en fazla o kursa kayıtlı insanlar görür. Sitenin kullanıcı sayısı 40 kat büyüdüğünde tek bir kursun kitlesi 40 kat büyümez.

Ölçekte dayanan kararlar çoğunlukla böyle tanınır. Maliyeti en hızlı büyüyen sayıya değil, yerel ve sınırlı bir sayıya bağlamışsınızdır. Bedelini de baştan ödersiniz: daha fazla depolama, daha fazla yazma işi ve her kullanıcının kendi kopyasını doğru tutma yükümlülüğü. Bu bedel sabittir. Öteki tasarımın bedeli her ay biraz daha artar ve faturayı, tam da en kalabalık olduğunuz gün keser.

Tercihin karanlık tarafını da söylemek gerekir. Veriyi kullanıcı başına çoğalttığınız anda hata da kullanıcı başına çoğalır. Sıralamayı yanlış yazdıysanız tek bir sorguyu düzeltip kurtulamazsınız, yüz binlerce kopyayı yeniden üretmeniz gerekir. Kullanıcı yeni bir kursa katıldığında geçmişi geriye dönük doldurmak da ayrı bir iştir; okuma anında hesaplayan tasarımda böyle bir problem hiç yoktur. Yazma tarafına geçmek, ucuz okuma karşılığında pahalı düzeltme satın almaktır. 100 bin kullanıcıda bu takas kötü görünür, 4 milyonda iyi görünür. Karar anında elinizde yalnızca hangi sayının büyüyeceğine dair bir görüş vardır.

Kuyruğu ortalama olaya göre boyutlandırmayın

Yazma anında hesaplamayı seçtiğiniz anda ikinci problem doğar: dağılımın ucu. Bildirimlerde bunun karşılığı çok somuttu. Bir eğitmenin tek bir kursta yaptığı tek bir duyuru, arka planda yüz binlerce iş tetikleyebiliyordu.

Sistemden aynı anda üç şey isteniyordu. Bildirimler kişiselleştirilmiş olacaktı, toplulaştırılmış olacaktı ve kalıcı olacaktı. Üçü birlikte istendiğinde iş zorlaşır. Kişiselleştirme, herkes için ayrı bir hesap demektir. Toplulaştırma, o ayrı hesabın tek satıra inmesi ve yeni olay geldikçe aynı satırın güncellenmesi demektir. Kalıcılık ise kullanıcının üç gün sonra girdiğinde aynı satırı, aynı okundu bilgisiyle bulmasıdır — hiçbir şeyi anlık üretip atamazsınız.

Buradaki asıl karar, kuyruğun ortalama olaya göre değil uçtaki olaya göre tasarlanmasıydı. Ortalama olay birkaç kişiye dokunur, uçtaki olay yüz binlerce iş üretir, ikisi de aynı yoldan geçer. Kapasiteyi ortalamaya göre seçerseniz sistem ortalama günlerde çalışır; oysa ortalama günler zaten sorun değildir. Kullanıcı tabanı 40 kat büyüdüğünde değişen şey ortalamanın büyüklüğü değil, ucun ne sıklıkla geldiğidir.

Uçtaki olayın ikinci sonucu şudur: yüz binlerce iş üreten bir kuyrukta o işlerin bir kısmı her zaman yarıda kalır. Bir süreç ölür, bir zaman aşımı olur, bir yeniden deneme devreye girer. Bu yüzden her işin iki kez çalıştırıldığında zarar vermemesi gerekir; aksi hâlde kullanıcı aynı duyuruyu iki kez görür ve bunu sonradan düzeltmenin yolu yoktur, çünkü bildirim kalıcıdır. Ölçek burada sisteme yeni bir gereksinim eklemiyor, var olan gereksinimi görünür kılıyor. Ortalama günlerde yanlışı fark etmezsiniz.

%50 bir kez kazanılır

Aynı dönemde API yanıt süresini %50 düşürdüm. Yolu şuydu: kodun tamamının profilini çıkarmak, Memcache önbellek katmanını kurmak, feed ve bildirimler için Redis tabanlı arka ucu yazmak.

Bu iş yukarıdakilerle aynı cinsten değil ve ikisini karıştırmak yaygın bir hata. Profil çıkarıp kazandığınız süre bir kere kazanılır, büyüme onu geri alır. Önbellek ölçek problemini çözmez, erteler. Erteleme meşru bir mühendislik ürünüdür — ama ne satın aldığınızı bilmek şartıyla: zaman satın alırsınız, bağışıklık değil.

Ayrımı şöyle koyuyorum. Yapısal kararlar büyüdükçe size çalışır, iyileştirmeler büyüdükçe tükenir. İkisine de ihtiyaç var. Bir ekibin “ölçeklenme problemini çözdük” dediği anda çoğunlukla ikincisini yapmış, birincisini yapmamış olması, sonraki yılın planını da belirler.

Bir yorumun kaç oy aldığı, kaçının olumlu olduğu kadar önemli

Kurs açılış sayfasını sıfırdan yazdım; o dönemde sitenin en karmaşık sayfasıydı. En çok uğraştıran parçası yorumların sıralamasıydı ve zorluk görsel değil istatistikseldi.

Yorumları olumlu oy oranına göre sıralarsanız, iki oy alıp ikisi de olumlu olan bir yorum, iki yüz oyun yüz doksanını toplamış bir yorumun üstüne çıkar. Farka göre, yani olumlu eksi olumsuz sıralarsanız bu sefer hacim kazanır: 1.000 oyun 600’ünü toplamış bir yorum, 50 oyun 50’sini toplamış yorumu geçer. İki yöntem de aynı şeyi atlıyor: oy sayısının kendisi bir bilgidir ve orana ne kadar güvenileceğini o belirler.

Doğru yöntem, olumlu oy oranı için Wilson skoru güven aralığının alt sınırını almaktır (Bernoulli parametresi için). Sorduğunuz şey şudur: elimdeki oylara bakınca, gerçek olumlu oy oranının en az ne olduğundan %95 eminim? Sıralamayı tahmine göre değil, bu alt sınıra göre yaparsınız.

from math import sqrt
 
Z = 1.96  # %95 güven düzeyi
 
def alt_sinir(olumlu: int, toplam: int) -> float:
    if toplam == 0:
        return 0.0
    p = olumlu / toplam
    pay = (p + Z*Z / (2*toplam)
           - Z * sqrt(p * (1 - p) / toplam + Z*Z / (4*toplam*toplam)))
    return pay / (1 + Z*Z / toplam)

Formül soyut duruyor, sayılar durmuyor.

Yorumun oylarıOlumlu oranıWilson alt sınırı
1 olumlu, 1 oy%1000,21
9 olumlu, 10 oy%900,60
90 olumlu, 100 oy%900,83
190 olumlu, 200 oy%950,91
600 olumlu, 1.000 oy%600,57

Tabloda üç şey var. Tek oyla %100 olumlu görünen yorum listenin altına düşüyor, çünkü tek gözlem hiçbir şey vaat etmez. Aynı %90 oranı on oyda 0,60, yüz oyda 0,83 ediyor; belirsizliğin bedelini küçük örneklem ödüyor. Ve 1.000 oyun 600’ü, farka göre sıralamada tepede duracakken burada on oyluk bir yorumun bile altında kalıyor.

E-posta başına 1 dolar, kullanıcı sayısından bağımsız

İstek listesi tek başına küçük bir özellik. Kursu beğendiniz, almaya hazır değilsiniz, listeye ekliyorsunuz. Arkasına bir kampanya bağladık: bir kursu listesine ekleyip bir ay içinde almayan kullanıcılara indirim kodu gidiyordu. Bu e-postalar ortalama olarak e-posta başına 1 dolar kazandırıyordu.

Rakamın bu yazıdaki yeri şurası: 1 dolar, kullanıcı sayısından bağımsız bir birim. Kullanıcı tabanı büyüdükçe kampanyanın tasarımı değişmez, yalnızca listedeki satır sayısı değişir. Niyetin tarihiyle birlikte kaydedildiği bir veri yapısı kurduğunuzda, pazarlama tarafı da feed tarafı gibi davranmaya başlar: büyüyen tek şey listedir.

Aynı dönemde kurs önizlemeyi de yazdım. Fikir bana aitti, şirketin çoğu karşı çıktı, insanların içeriği hızlıca indirip gideceğini düşünüyorlardı. Dönüşüm oranları belirgin biçimde arttı. Bunun ölçekle ilişkisi ters yönde ve daha önemli. Dönüşüm bir orandır, gelen her yeni kullanıcıyla birlikte çarpılır. Yanıt süresinden kazandığınız milisaniyeleri büyüme harcar, dönüşümden kazandığınız puanı büyüme çoğaltır. İkisi aynı haftanın işi olabilir, ama biri geçici, öteki kalıcıdır.

Neyin dayandığı, neyin dayanmadığı

Dayanan tarafta yapısal olanlar var: maliyeti yerel ve sınırlı bir sayıya bağlamak, toplulaştırmayı okuma anına bırakmamak, kullanıcı başına kalıcı durumu baştan kabul etmek, sıralama kuralını gözlem sayısını içeren bir istatistikle yazmak. Bunların hiçbiri 100 bin kullanıcıda kendini haklı çıkarmıyordu. Hepsi o gün gereğinden ağır görünen tercihlerdi.

Dayanmayan tarafta ise ölçekle ilgisi olmayan şeyler duruyor. Feed’i iki parça olarak yazmıştım: her kullanıcı için kişisel bir akış, her kurs için bir akış. Kişisel akışı bir süre sonra emekliye ayırdık, kurs akışı şirketin en başarılı ürünlerinden biri oldu. Kişisel akışı öldüren yük değildi, kimsenin ona ihtiyaç duymamasıydı. Yazdığım ekranların çoğu da sonradan yeniden tasarlandı. Bir özelliğin ömrünü çoğunlukla trafik değil, insanların onu isteyip istememesi belirliyor.

Kendi kodumun 40 kat büyümesini izlemekten çıkardığım şey bir teknik liste değil, bir bakma biçimi. Bir tasarımın dayanıp dayanmayacağını anlamak için sisteme değil maliyet ifadesine bakın: hangi sayı büyüyor ve o sayı ifadenin neresinde duruyor, çarpanda mı yoksa sınırlı bir terimin içinde mi. Bu soruyu en az bilgiye sahip olduğunuz anda, en başta cevaplamak zorundasınız — 100 bin kullanıcıdayken. 4 milyonda o kararı ya hiç hatırlamazsınız ya da her gün hatırlarsınız.

Buraya kadar okuduysan

Bunun gibi yazıları yayımladığımda e-postana göndereyim. Ayda birkaç kez. Yapay zeka, mühendislik ve yatırım üzerine. Spam yok.

Ayda birkaç kez. Yapay zeka, mühendislik ve yatırım üzerine. Spam yok.

Yorumlar

Burada henüz kimse konuşmamış. İlk sözü siz söyleyin — katılmadığınız yeri yazarsanız daha da iyi.

Yorum yazın

Yorumlar önce bana geliyor. Onayladığımda burada, adınızla birlikte yayımlanıyor.

Yayımlanmaz. Sadece gerekirse size dönebilmek için.