- 26 Haziran 2024’te yayınlanan One Million Checkboxes, herkesin aynı 1 milyon onay kutusunu gerçek zamanlı olarak manipüle ettiği bir siteydi ve 2 hafta sonra kapanmadan önce 650 milyondan fazla işaretlemeyi işledi
- Durumun kendisi yalnızca 1 milyon bit, yani 125KB idi; ancak nginx, Flask/gunicorn ve Redis pubsub ile kurulan ilk yapı beklenmedik trafik karşısında hızla sınırlarına ulaştı
- Hacker News, Reddit, Mastodon ve Twitter’dan gelen trafikle on binlerce kişi akın edince Redis bağlantı tükenmesi, bant genişliği patlaması, girdi doğrulama eksikliği ve eski güncellemelerin uygulanması sorunları art arda ortaya çıktı
- Müdahale, sunucu ve Redis ölçeklendirmesi, güncellemeleri toplu işleme, aktarım formatını küçültme, Linux
tctabanlı 250Mbit/s bant genişliği üst sınırı ve süreç yeniden başlatma betiği gibi hızlı uygulanabilecek önlemlere odaklandı - Sonrasında backend Go’ya taşınarak istikrara kavuşturuldu ve en sonunda Redis Lua script’i ile onay kutusu dondurma mantığı atomik olarak işlendi; site 11 Temmuz 2024 Doğu Saati ile 4:35 PM’de tamamlandı
Site ve ilk tasarım
- One Million Checkboxes(OMCB), 26 Haziran 2024’te yayına alınan ve 1 milyon küresel onay kutusu sunan bir web sitesiydi
- Bir kullanıcı bir onay kutusunu açıp kapattığında bu değişiklik tüm kullanıcıların ekranına anında yansıyordu
- Yapımı 2 gün sürdü ve beklenen kullanıcı sayısı en fazla birkaç yüz düzeyindeydi
- Gerçek tepki beklentinin çok üzerindeydi
- Yayına alındıktan sonraki birkaç saat içinde on binlerce kişi geldi ve milyonlarca onay kutusunu değiştirdi
- Trafik Hacker News, /r/InternetIsBeautiful, Mastodon ve Twitter üzerinden geldi
- Birkaç gün sonra Washington Post ve New York Times da siteden bahsetti
- İlk günün erken saatlerine ait bazı loglar elde kalmadı
- Çünkü başlangıçta günlük logların yalnızca en son 1 milyon adedi saklanıyordu
- İkinci günden itibaren istikrar sağlanmaya başladı ve o gün 50 milyondan fazla işaretleme yapıldı
- Site kapanmadan önce toplam işaretleme sayısı 650 milyonu aştı
Redis merkezli ilk mimari
- Onay kutusu durumu 1 milyon bit ile ifade ediliyordu
- İşaretli kutu
1, işaretsiz kutu0idi - Toplam durum boyutu 125KB idi
- İstemci, bitset’i saklıyor ve render sırasında buna başvuruyordu
- İşaretli kutu
- İstemci, DOM yükünden kaçınacak şekilde yapılandırılmıştı
- 1 milyon öğenin tamamı DOM’a eklenmiyordu
- react-window kullanılarak yalnızca ekranda görünen onay kutuları ve küçük bir buffer render ediliyordu
- Sunucu yapısı, basit yatay ölçeklemeyi dikkate alacak şekilde kurulmuştu
- nginx statik içeriği sunuyor, API istekleri ile websocket bağlantılarını Flask sunucusuna iletiyordu
- Flask sunucusu, gunicorn ile çalışan iki instance’tan oluşuyordu
- Redis hem onay kutusu durumunu saklıyor hem de mesaj kuyruğu görevi görüyordu
- Redis’in kullanım biçimi de doğrudandı
- Redis’in bit işleme primitive’leriyle tek tek onay kutularının durumu değiştiriliyordu
- İstemci bir işaretleme olayı gönderdiğinde Flask, Redis’te ilgili biti çeviriyor ve olayı pubsub’a yazıyordu
- İki Flask sunucusu da pubsub’ı okuyup kendilerine bağlı istemcilere değişikliği bildiriyordu
- Tüm durum anlık görüntüsü, kaçırılan güncellemeleri telafi etmek için kullanılan bir mekanizmaydı
- Arka planda kalan sekmeler yüzünden güncelleme kaçıran istemcileri senkronize etmek için kullanılıyordu
- İlk uygulamada her 30 saniyede bir tüm durum gönderiliyordu
Ölçekleme ilkeleri
- Maliyet için üst sınır hesaplanabilir olmalıydı
- Sınırsız otomatik ölçekleme ile maliyetin patladığı bir yaklaşım özellikle tercih edilmedi
- Beklenenden fazla yük gelirse sistemin kırılmasına izin verme tarafı seçildi
- Popülerliğin kısa süreceği varsayıldı
- Günler veya haftalar sürecek kusursuz çözümler yerine birkaç saat içinde üretilebilecek müdahaleler öne alındı
- Bu süreçte oluşan teknik borç kabul edildi
- Teknoloji seçiminde basitlik ve doğrudan işletilebilirlik tercih edildi
- Sunucuya doğrudan bağlanıp komut çalıştırıp debug edilebilen bir yapı seçildi
- Ağırlıklı olarak kendi başına işletilebilen ve debug edilebilen bağımlılıklar kullanıldı
- Sitenin temel deneyimi küresel senkronizasyondu
- Nereye gidilirse gidilsin değişimin anında görülebilmesi gerekiyordu
- Kullanıcının gördüğü onay kutularını göndermeye dayalı bir ölçekleme yöntemi seçilmedi
İlk gün: sunucu ekleme ve Redis darboğazı
- Yayından sonraki 30 dakika içinde yük hızla arttı; site ayaktaydı ama uzun süre dayanması zordu
- En açık iyileştirme sunucu sayısını artırmaktı
- nginx, başka bir VM’deki Flask instance’larına kolayca reverse proxy yapabiliyordu ve durum zaten Redis’teydi
- İkinci sunucu yaklaşık 12:30 PM’de eklendi ve kısa sürede yük %100’e ulaştı
- Başta bir veya iki ek sunucunun yeterli olacağı düşünülmüştü
- Gerçekte ise ölçeklendikçe trafik de aynı oranda arttı
- Site Hacker News’te 1 numaraya çıktı ve Twitter etkinliği de hızla arttı
- Flask sunucuları ile Redis bağlantıları darboğaz haline geldi
- Redis connection pool yoktu ve Redis bağlantı sınırına dayanmaya başlamıştı
- Bunun üzerine güncellemeleri toplu gönderme yaklaşımına geçildi
- Mevcut istemcilerle geriye dönük uyumluluk gözetilmedi; kullanıcıların sayfayı yenileyeceği varsayıldı
- Redis connection pool da eklendi ancak gunicorn ve Flask kombinasyonunda temiz çalışmadı
- Yine de Redis bağlantı sayısını azaltmaya yardımcı olmuş görünüyor
- Sonrasında bu sorun derinlemesine incelenmeden Go geçişine gidildi
- Oturum oluşturma rate limit’i kaldırıldı
- Çünkü rate limit durumu Redis’te tutuluyordu ve Redis bağlantıları tükeniyordu
- Sorun yeni oturum patlaması değil, tek bir oturumdan çok fazla veri gönderilmesiydi
- Kısa vadede riskli olsa da kabul edilebilir bir hamle olarak değerlendirildi
- Redis instance’ı da daha büyük bir yapılandırmaya geçirildi
- Digital Ocean managed Redis kullanılıyordu
- 1 shared CPU ve 2GB RAM’li küçük instance’tan 4 dedicated CPU ve 32GB RAM’li instance’a geçildi
- Yeniden boyutlandırma yaklaşık 30 dakika sürdü
Bant genişliği sorunu ve aktarım miktarını azaltma
- Başlangıçta bant genişliği maliyeti yeterince hesaba katılmamıştı
- Digital Ocean, ücretsiz bant genişliği aşıldıktan sonra GB başına $0.01 ücret alıyordu
- Önceki işlerden kalan 1TB ücretsiz bant genişliği vardı ve OMCB’nin bunun üzerinde büyük etkisi olmayacağı düşünülmüştü
- Tüm durum anlık görüntüleri bant genişliğini çok hızlı tüketebilirdi
- 1 milyon bit, 1Mbit eder
- Her 30 saniyede 1.000 kişiye gönderildiğinde dakikada yaklaşık 2GB, saatte 120GB düzeyine çıkıyordu
- Bu hesap incremental update’leri içermiyordu
- Bant genişliğini kontrol etme ve maliyet üst sınırı koyma işi nginx kutusunda yapıldı
- Gönderilen bayt miktarı
ip -s link show dev eth0ile kontrol edildi - Tek bir nginx reverse proxy kullanıldığı için bant genişliğinin kaynağını tahmin etmek kolaydı
- Gönderilen bayt miktarı
- Aktarım miktarını azaltma iki koldan yürüdü
- Tüm durum anlık görüntülerinin sıklığı düşürüldü
- Incremental update formatı küçültüldü
- Toplu güncelleme formatı ciddi biçimde sıkıştırıldı
- Eski format
{ "index": 123, "value": true }gibi dict listelerinden oluşuyordu - Son format, true index dizileri ile false index dizilerinden oluşan
[[123, 125], [124]]çiftiydi - Bu yaklaşım ilk uygulamaya göre 5 kat daha kısaydı
- Eski format
- Maliyet patlamasını önlemek için Linux
tcile hard cap uygulandı- Public interface
eth0üzerindeki trafik 250Mbit/s ile sınırlandı - Bu, dakikada yaklaşık 2GB ve günde 3TB’ye biraz az kalan bir seviyeye denk geliyordu
- GB başına $0.01 fiyatla, maliyetin gece boyunca kontrolden çıkma riskinden kaçınıldı
- Public interface
İkinci gün: girdi doğrulama eksikliği ve Redis replica
- Ertesi sabah site kapalıydı; neden girdi doğrulama eksikliğiydi
- 1 milyonu aşan index’lerdeki onay kutuları engellenmiyordu
- Birisi yüz milyonlar seviyesindeki index’lerdeki onay kutularını değiştirdi
- Bu yüzden işaretli kutu sayısı 1 milyona ulaşmış gibi göründü ve sitenin bittiği düşünüldü
- Redis verisi de gereksiz yere büyüdü
- 1 milyoncü bit ile 100 milyonuncu bit arasına milyonlarca
0eklendi - İstemcilere gönderilen veri 100 kat büyüdü
- 1 milyoncü bit ile 100 milyonuncu bit arasına milyonlarca
- Kurtarma hızlı ilerledi
- nginx durduruldu
- Eski bitset’in yalnızca ilk 1 milyon biti yeni bir bitset’e kopyalandı
- Eski bitset debug amacıyla saklandı
- Kod yeni bitset’e yönlendirildi ve girdi doğrulaması eklendi
- İlk sayfa yüklemesi de yavaşlamıştı
- Redis yükü yüksekti ve connection pool bug’ı yüzünden aşırı sayıda bağlantı da açılıyordu
- Connection pool sorununu debug etmek yerine primary yükünü ve bağlantıları dağıtmak için bir Redis replica eklendi
- Replica’nın private IP’si elle bulunmak zorunda kaldı
- Digital Ocean yönergelerindeki gibi public DNS’te
replica-öneki çalışıyordu ama private DNS’te çalışmıyordu - Public IP kullanılırsa public internet üzerinden geçiş ve bant genişliği ücretlendirmesi riski olduğu düşünüldü
- Primary ve diğer sunucuların private IP’lerine yakın adresler denenerek üçüncü ya da dördüncü denemede replica private IP’si bulundu
- Sonrasında bu IP hardcode edildi
- Digital Ocean yönergelerindeki gibi public DNS’te
Süreç yeniden başlatma ve stale update düzeltmesi
- Flask süreçleri çökmeye devam etti ve bunun nedeninin Redis bağlantı eksikliği olduğu düşünüldü
- Ayrıntılı debug yerine, çalışan Flask süreçlerinin sayısını kontrol eden bir bash script’i yazıldı
- Çalışan süreç sayısı 3’ün altına inerse systemd unit’i yeniden başlatıyordu
- Script crontab’e eklendi
- nginx yapılandırması da buna uygun şekilde ayarlandı
- Düşen sunucuların geçici olarak rotation dışına alınması sağlandı
- Bu değişiklikten sonra site istikrara kavuştu
- İstemci durum senkronizasyonunda bir stale update bug’ı vardı
- İstemci hem incremental update hem de full-state snapshot alıyordu
- Bu iki güncellemede timestamp olmadığı için istemci yeni bir snapshot aldıktan sonra eski bir incremental update uygulayabiliyordu
- Sonuç olarak bir sonraki full-state snapshot gelene kadar tamamen yanlış bir durum görülebiliyordu
- Timestamp tabanlı bir hafifletme eklendi
- Full-state snapshot’a timestamp eklendi
- Redis pubsub’a yazılan her update’e de timestamp eklendi
- İstemciye gönderilen batch içine, kapsadığı incremental update’ler arasındaki en büyük timestamp kondu
- İstemci artık son full-state snapshot’tan daha eski batch’leri atıyordu
- Bu çözüm kusursuz değildi
- Batch içindeki update’lerden yalnızca biri bile yeniyse, çoğu eski olsa da batch uygulanabiliyordu
- Yine de önceki duruma göre büyük bir iyileşmeydi
Go ile yeniden yazım ve istikrar
- Ertesi sabah site ayaktaydı ve sonrasında odak backend’in yeniden yazımına kaydı
- Bu sırada Washington Post’tan da bir e-posta gelmişti
- Siteyi nasıl kapatacaklarına dair plan da aynı anda düşünülüyordu
- Kapanış planı, işaretli kutuların hızlıca kaldırılmaması halinde donması üzerine kuruluydu
- Bu değişiklik etkinlikte yeni bir patlamaya ve ek sunucu çalışmasına yol açabilirdi
- Mevcut Flask tabanlı yapının bunu kaldırıp kaldıramayacağından emin değillerdi
- Arkadaşı Eliot ile birlikte backend Go ile yeniden yazıldı
- Pazar günü 2 PM’den 2 AM’e kadar uygulama konuşulup tüm backend port edildi
- Yapı büyük ölçüde değiştirilmeden taşındı
- En güncel protokolü destekleyen bir Go
socketiokütüphanesi bulmak gibi noktalar engel oluşturdu
- Performans artışı çok büyüktü
- Sistem o kadar iyi ölçeklenmeye başladı ki botlar aşırı trafik yükleyebilir hale geldi
- Daha iyi bir rate limit ihtiyacı ortaya çıktı
- Pazar gecesi bir DDoS da yaşandı
- Site Cloudflare arkasına alınarak ve nginx ayarları biraz değiştirilerek buna yanıt verildi
Siteyi kapatma mantığı
- Go ile yeniden yazımdan sonra site istikrarlı çalıştı
- Sonraki bir hafta boyunca röportajlar ve yoğun ilgiyle ilgilenildi
- Ardından siteyi kapatma işine geçildi
- Kapanış yöntemi onay kutularının donmasıydı
- İşaretli bir kutu hızla kaldırılmazsa frozen durumuna geçiyordu
- Zaman ilerledikçe tüm site tamamen frozen hale geliyordu
- Redis’e ek durum bilgisi eklendi
- Her onay kutusunun en son ne zaman işaretlendiğini tutan bir hashtable eklendi
- Bu durum istemciye göndermek için büyük olsa da Redis’te saklamak için uygundu
time_to_freezedeğeri de saklandı
- Donma kararı uncheck anında veriliyordu
now - last_checked > time_to_freezeise uncheck yapılmıyordu- Bunun yerine
frozen_bitsetgüncellenerek ilgili onay kutusunun frozen olduğu işaretleniyordu frozen_bitset, checked durumu gibi istemcilere dağıtılıyordu- İstemci, frozen biti açık olan onay kutularını devre dışı bırakıyordu
- Kimse uncheck yapmasa da donmanın gerçekleşmesi için ayrı bir iş eklendi
- Periyodik olarak donması gereken ama henüz işaretlenmemiş bitler bulunup frozen durumuna geçiriliyordu
- İlgili mantık Redis Lua script’ine konarak atomik biçimde çalıştırıldı
- Böylece race condition’lardan kaçınmak kolaylaştı
- Kapanış değişikliği yayına alındıktan 2 hafta 1 gün sonra uygulandı
- 11 Temmuz 2024 Doğu Saati ile 4:35 PM’de 491915 numaralı kutu işaretlendi ve site tamamlandı
Maliyet ve çıkarılan dersler
- Siteyi işletmenin maliyeti yaklaşık $850 oldu
- Bağışlar bu maliyete oldukça yaklaştı
- Sonuçta büyük bir zarar olmadığı değerlendirildi
- Redis ve nginx seçiminden memnun kalındı
- Redis ve nginx çok faydalı teknolojiler olarak değerlendirildi
- Doğrudan işletildikleri için debug ve düzeltme kolay oldu
- Ancak managed Redis instance üzerinde tam kontrol olmaması biraz rahatsız ediciydi
- Başından itibaren uzun süreli büyük ölçek tasarımı yapmama kararı olumlu değerlendirildi
- İnternette neyin başarılı olacağını öngörmenin zor olduğu düşünülüyor
- En başta haftalarca scale üzerine düşünülseydi muhtemelen site hiç yayımlanamayacaktı
- Çok sayıda kullanıcının gelmesi, bakım motivasyonu ve öncelik belirleme açısından yardımcı oldu
- Sınırlı anonim etkileşime yönelik talep de doğrulandı
- İnsanların yabancılarla sınırlı biçimde etkileşime girilen sitelere ilgi gösterdiği görüldü
- Bu tür siteleri yapmaya devam etme konusunda güven arttı
1 yorum
Hacker News yorumları
Dağıtık sistemlerin tarihsel bilgisiyle birlikte öğrenilecek çok şey olan bir yazıydı.
Depolama alanı hariç neredeyse her tür kesinti ve arıza noktasını yaşamış gibi görünüyor; çözüm sürecini görebilmek güzeldi.
Redis'in Lua desteklediğini bilmiyordum; bunu görünce alternatif bir durum deposu olarak denemek istedim.
Bant genişliği, bulut servislerindeki en büyük şikâyetlerimden biri. Çünkü aşırı ücretlendirmeyi engelleyecek sert bir limit yok.
Ama ikisi de büyük sorun değildi; depolama alanının anlamlı bir sorun olmadığı bir proje görmek epey ilginçti. Benim için yeni bir deneyimdi.
Bant genişliği gerçekten baş ağrıttı. Yaklaşık iki gün boyunca sürekli tetikte kalıp NIC'in gönderilen baytlarını kontrol ettim, hesapları tekrar yaptım; hard cap olmaması korkutucuydu. Digital Ocean'ın fiyatları oldukça makul sayılmasına rağmen böyleydi.
Popüler sunucusuz servisleri kullanmadım ama oralarda bant genişliği maliyetinin epey sert vurduğunu anlıyorum.
Ayrıca Redis içindeki Lua gerçekten güçlü; biraz performans kaybını göze alabiliyorsanız zor ve yarış koşulu bol birçok problemi atlayabiliyorsunuz, çalışması da keyifliydi.
Harika bir yazıydı; web sitesi de tebriki hak ediyor.
Ama kişisel olarak en çok gurur duyulacak kısmın bu yazılmış metin olduğunu düşünüyorum.
“Ölçeklenebilirliği neredeyse hiç önemsemeden siteyi iki günde yapmanın iyi bir seçim olduğu” kısmının öz olduğunu düşünüyorum.
Özellikle kariyerinin başındaki mühendislerin öğrenmesi gereken bölüm bu. Ölçeklenebilirlik, sorun olana kadar sorun değildir.
Sorun olduğu noktada ise aslında iyi bir sorundur ve düzeltmesi sanıldığından zor da değildir.
Ölçekleme ya da ekip ayrımı gerektiği için değil, geliştiriciler sadece öyle yapmak istediği için mikroservislerin “bariz seçim” olduğu çok sistem gördüm.
Böyle sistemleri ölçeklemek gerçekten eziyet.
Yakın tarihli ilgili yazı: One Million Checkboxes - https://news.ycombinator.com/item?id=40800869 - Haziran 2024, 305 yorum
Böyle projeler eğlenceli.
Yaklaşık 6 yıl önce Android'de Pixmap'i çıkarmıştım; 1024x1024 gibi daha büyük ızgaraları destekleyen küçük bir iş birliğine dayalı piksel düzenleme uygulamasıydı.
Her olayı bir PNG görüntüsüne uygulayan bir kuyruk vardı; istemci bağlandığında ilk PNG'yi yüklüyor, sonrasında her piksel çizme olayı için yalnızca küçük bir nesne alıyordu.
Böylece ilk yüklemede görüntü sıkıştırmasından yararlanabiliyorsunuz, sonraki değişiklik kümeleri ise çok küçük oluyor. Ayrıca tüm olaylar logda saklandığı için görüntüyü “geri sarabiliyorsunuz” [0]
[0] 22mb: https://blog.winricklabs.com/images/pixmap-rewind-demo.gif
Bu yüzden API çağrısıyla adreslenebilen bir tuval üzerinde kurcalıyorum.
https://x.com/RussTheMagic/status/1816749136487588311
İyi yazıydı. Sonunda maliyetin ne kadar tuttuğunu merak ediyorum.
Toplam maliyet yaklaşık 850 dolar oldu ve bağışlarla neredeyse karşılandı.
Go'ya geçtikten sonra altyapıyı düzgün kapatmama hatası yaptım; ayrıca açtığım ikinci Redis replikasını da kaldırabilirdim. Maliyete odaklansaydım yarıya indirebilirdim diye düşünüyorum.
Ama bağışlar maliyeti neredeyse karşılıyordu ve başka çok iş vardı, bu yüzden buna fazla odaklanmadım.
Siteyi kapattıktan sonra da grafik hazırlığı vb. için altyapıyı bir süre açık tuttum, bu yüzden biraz daha para gitti; şu anda az da olsa zarardayım ama büyük bir tutar değil.
Backend'i yeni öğrenen biri olarak, bu proje için daha basit bir alternatif mimari var mı merak ediyorum.
1 milyon bitlik durumu barındırmanın ve istemcilerle senkronize etmenin daha kolay bir yolu olsa iyi olurdu. Yazıdaki çözümlerin bir kısmını anlamak zordu.
Yazarın projeleri harika.
Kullandığım teknolojileri daha uzun açıklamak istedim ama yazı zaten çok uzundu, daha fazlasını eklemek zor geldi.
Sorunuz varsa memnuniyetle yanıtlarım.
Dürüst olmak gerekirse mimariyi çok daha basitleştirmenin yolunu pek bilmiyorum. Bu amaçla kullanılabilecek servisler vardır, ama bu daha çok karmaşıklığı başka birine devretmek gibi olur.
Sonuçta gerekenler şunlar: işaretlenmiş kutuları takip eden bir veritabanı, veriyi veritabanına koyma yöntemi, mevcut durumu istemciye bildirme yolu, istemci bir kutuyu işaretlediğinde sunucuya haber verip durumu güncelleme yolu, kutu işaretlendiğinde ya da işareti kaldırıldığında istemcilere bildirme yolu ve her zaman 1 milyon DOM öğesi render etmeme yöntemi.
Burada işaret durumunu Redis ile sakladım; basitlik için 1 milyon biti olduğu gibi tuttum ve istemcilere 1 milyon bitin tamamını gönderdim. Verinin çok büyük olmaması iyiydi.
Flask ve WebSocket ile işaretleme olaylarını ve güncellemeleri ele aldım; hem tekil kutu güncellemelerini hem de 1 milyon kutunun tamamına ait güncellemeleri gönderdim, render sorununu da react-window ile önledim.
Geri kalan nginx statik içerik ve reverse proxy çoğunlukla ölçeklemeyi kolaylaştırmak için vardı; bu ayrıntılar olmadan da uygulamak ve siteyi çalıştırmak mümkün. Sadece aynı yükü kaldıramaz.
Veritabanı yerine bit kümesini bir dosyada saklayıp mmap edersiniz. Reverse proxy yerine uygulama içinde HTTP isteklerini ve WebSocket bağlantılarını doğrudan işleyebilirsiniz.
Arkasında cache ve publish/subscribe kuyruğu olan birkaç web sunucusu düzeni.
Büyük bir host üzerinde hepsini bellek içinde de işleyebilirdiniz ama talebi karşılayamazsa ya da herhangi bir nedenle başarısız olursa tamamen tıkanırsınız.
Backend API ile aynı süreç içinde 1 milyon boolean'dan oluşan global liste tutmak gibi ölçeklenemez bir yöntem hariç tabii.
(checked, start_x, start_y, end_x, end_y)tuple'ları olarak özetlemek için dörtlü ağaç kullanırsınız. Çok bariz bir yöntem değil mi?Harika.
Bir sonraki yazının hangi checkbox'ların en az ya da en çok işaretlendiğine dair istatistiksel analiz olup olmayacağını merak ediyorum.
Epey aşağı kaydırıp seçtiğim checkbox'ın neredeyse anında kaldırıldığını hatırlıyorum; biraz üzülmüştüm.
Ondan önce siteyle ilgili anlatmam gereken bir hikâye daha var.
Oyunun hâlâ canlı olup olmadığını merak ediyorum.
https://onemillioncheckboxes.com/ adresine girince hiçbir şey işaretli görünmüyor ve JS konsolunda sadece şunu görüyorum:
{"total":0,"totalGold":0,"totalRed":0,"totalGreen":0,"totalPurple":0,"totalOrange":0,"recentlyChecked":false}Ölçeklenebilir bir uygulamanın tam zıttı örnek olarak, 1000 karakterden kısa 1 milyon checkbox uygulaması var. Deno sürümü.
https://gist.github.com/jeff-hykin/4cdebafd8698298d021f103e2...