Meteor, birçok müşteri arasında devasa bir koleksiyonu paylaşırken ne kadar verimli olabilir?


100

Aşağıdaki durumu hayal edin:

  • 1.000 müşteri, "Somestuff" koleksiyonunun içeriğini gösteren bir Meteor sayfasına bağlanmıştır.

  • "Somestuff" 1.000 öğe içeren bir koleksiyondur.

  • Birisi "Somestuff" koleksiyonuna yeni bir öğe ekliyor

Ne olacak:

  • Meteor.Collectionİstemcilerdeki tüm e-postalar güncellenecektir, yani ekleme hepsine iletilecektir (bu, 1.000 istemciye gönderilen bir ekleme mesajı anlamına gelir)

Sunucunun hangi istemcinin güncellenmesi gerektiğini belirlemesi için CPU açısından maliyet nedir?

Listenin tamamına değil, yalnızca eklenen değerin istemcilere iletileceği doğru mu?

Bu gerçek hayatta nasıl işliyor? Bu ölçekte herhangi bir kıyaslama veya deney var mı?

Yanıtlar:


119

Kısa cevap, kabloya yalnızca yeni verilerin gönderilmesidir. İşte nasıl çalıştığı.

Meteor sunucusunun abonelikleri yöneten üç önemli bölümü vardır: aboneliğin sağladığı verilerin mantığını tanımlayan yayınlama işlevi ; Mongo sürücü değişiklikleri veritabanı saatler; ve bir müşterinin tüm aktif aboneliklerini birleştiren ve bunları ağ üzerinden müşteriye gönderen birleştirme kutusu .

İşlevleri yayınla

Bir Meteor istemcisi bir koleksiyona her abone olduğunda, sunucu bir yayınlama işlevi çalıştırır . Yayınlama işlevinin görevi, istemcinin sahip olması gereken belge setini bulmak ve her belge özelliğini birleştirme kutusuna göndermektir. Her yeni abone olan müşteri için bir kez çalışır. Kullanarak rastgele karmaşık erişim kontrolü gibi yayınlama işlevine istediğiniz herhangi bir JavaScript koyabilirsiniz this.userId. Yayınlama işlevi this.added, this.changedve çağırarak verileri birleştirme kutusuna gönderir this.removed. Daha fazla ayrıntı için yayın belgelerinin tamamına bakın.

Yayımlamak Çoğu fonksiyon düşük seviyede boğuşulmamasıdır gerekmez added, changedve removedde, API. Bir fonksiyon döner bir Mongo imleç yayınlarsanız, Meteor sunucusu otomatik Mongo sürücü (çıktısını bağlayan insert, updateve removedbirleştirme kutusunun girişine geri aramalar) ( this.added, this.changedve this.removed). Bir yayınlama işlevinde tüm izin kontrollerini önceden yapabilmeniz ve ardından herhangi bir kullanıcı kodu olmadan veritabanı sürücüsünü doğrudan birleştirme kutusuna bağlayabilmeniz oldukça güzel. Ve otomatik yayın açıldığında, bu küçük kısım bile gizlenir: sunucu, her koleksiyondaki tüm belgeler için otomatik olarak bir sorgu oluşturur ve bunları birleştirme kutusuna iter.

Öte yandan, veritabanı sorgularını yayınlamakla sınırlı değilsiniz. Örneğin, bir cihazdaki bir GPS konumunu okuyan Meteor.setIntervalveya başka bir web hizmetinden eski bir REST API'sini sorgulayan bir yayınlama işlevi yazabilirsiniz . Bu gibi durumlarda, düşük seviyeli arayarak birleştirme kutusuna değişiklikleri yayarlar ediyorum added, changedve removedDDP API.

Mongo sürücüsü

Mongo sürücünün işi canlı sorgularına değişiklikler için Mongo veritabanı izlemektir. Bu sorgular sürekli çalışacak ve arayarak sonuçları değişiklik olarak güncellemeleri dönmek added, removedve changedgeri aramalar.

Mongo, gerçek zamanlı bir veritabanı değildir. Yani sürücü anketler. Her etkin canlı sorgu için son sorgu sonucunun bellek içi bir kopyasını tutar. Her yoklama döngüsünde üzerinde, minimum set bilgisayar, önceki kayıtlı sonuçla yeni sonucu karşılaştırır added, removedve changed farkı açıklamak olaylar. Birden fazla arayan aynı canlı sorgu için geri aramalar kaydederse, sürücü sorgunun yalnızca bir kopyasını izler ve her kayıtlı geri aramayı aynı sonuçla arar.

Sunucu bir koleksiyonu her güncellediğinde, sürücü o koleksiyondaki her canlı sorguyu yeniden hesaplar (Meteor'un gelecekteki sürümleri, hangi canlı sorguların güncelleme sırasında yeniden hesaplanacağını sınırlamak için bir ölçeklendirme API'sini ortaya çıkaracaktır.) Sürücü ayrıca her bir canlı sorguyu 10 saniyelik bir zamanlayıcıda yoklar. Meteor sunucusunu atlayan bant dışı veritabanı güncellemelerini yakalamak.

Birleştirme kutusu

İş birleştirme kutusundan sonuçları (birleştirmektir added, changedve removed tek bir veri akışına bir müşterinin aktif yayımlamak her türlü fonksiyonun aramaları). Bağlı her istemci için bir birleştirme kutusu vardır. Müşterinin minimongo önbelleğinin tam bir kopyasını tutar.

Yalnızca tek bir abonelik içeren örneğinizde, birleştirme kutusu esasen bir geçiştir. Ancak daha karmaşık bir uygulamanın birden çok aboneliği olabilir ve bunlar çakışabilir. İki abonelik aynı belgede aynı özniteliği ayarlarsa, birleştirme kutusu hangi değerin öncelikli olduğuna karar verir ve bunu yalnızca istemciye gönderir. Henüz abonelik önceliğini belirleme API'sini ifşa etmedik. Şimdilik öncelik, müşterinin veri setlerine abone olma sırasına göre belirlenir. Bir müşterinin yaptığı ilk abonelik en yüksek önceliğe sahiptir, ikinci abonelik en yüksek önceliğe sahiptir ve bu böyle devam eder.

Birleştirme kutusu istemcinin durumunu tuttuğu için, yayınlama işlevi ne olursa olsun her müşteriyi güncel tutmak için minimum miktarda veri gönderebilir.

Bir güncellemede ne olur

Şimdi senaryonuz için sahneyi hazırladık.

1.000 bağlantılı müşterimiz var. Her biri aynı canlı Mongo sorgusuna ( Somestuff.find({})) abone olur . Sorgu her istemci için aynı olduğundan, sürücü yalnızca bir canlı sorgu çalıştırıyor. 1.000 aktif birleştirme kutusu vardır. Ve her müşterinin yayınlama işlevi added, bir changed, ve removedbirleştirme kutularından birine beslenen canlı sorguda kaydedildi. Birleştirme kutularına başka hiçbir şey bağlı değildir.

Önce Mongo sürücüsü. İstemcilerden biri içine yeni bir belge eklediğinde Somestuff, bir yeniden hesaplamayı tetikler. Mongo sürücüsü içindeki tüm belgeler için sorguyu yeniden çalıştırır, Somestuffsonucu bellekteki bir önceki sonuçla karşılaştırır, bir yeni belge olduğunu bulur ve 1.000 kayıtlı insertgeri aramadan her birini çağırır .

Ardından, yayınlama işlevleri. Burada çok az şey oluyor: 1.000 insertgeri aramadan her biri, verileri arayarak birleştirme kutusuna aktarıyor added.

Son olarak, her bir birleştirme kutusu, bu yeni öznitelikleri, istemcisinin önbelleğinin bellek içi kopyasına göre kontrol eder. Her durumda, değerlerin henüz istemcide olmadığını ve mevcut bir değeri gölgelemediğini bulur. Dolayısıyla, birleştirme kutusu DATA, SockJS bağlantısında istemcisine bir DDP mesajı gönderir ve sunucu tarafındaki bellek içi kopyasını günceller.

Toplam CPU maliyeti, bir Mongo sorgusunu ayırt etmenin maliyeti artı müşterilerinin durumunu kontrol eden ve yeni bir DDP ileti yükü oluşturan 1.000 birleştirme kutusunun maliyetidir. Kablo üzerinden akan tek veri, veritabanındaki yeni belgeye karşılık gelen 1.000 istemcinin her birine gönderilen tek bir JSON nesnesidir ve ayrıca orijinal eklemeyi yapan istemciden sunucuya bir RPC mesajıdır .

Optimizasyonlar

İşte kesinlikle planladığımız şey.

  • Daha verimli Mongo sürücüsü. Sürücüyü , her bir sorgu için yalnızca tek bir gözlemci çalıştırmak için 0.5.1'de optimize ettik .

  • Her DB değişikliğinin bir sorgunun yeniden hesaplanmasını tetiklemesi gerekmez. Bazı otomatik iyileştirmeler yapabiliriz, ancak en iyi yaklaşım, geliştiricinin hangi sorguların yeniden çalıştırılması gerektiğini belirlemesini sağlayan bir API'dir. Örneğin, bir geliştirici için bir sohbet odasına mesaj eklemenin, ikinci bir odadaki mesajlar için canlı bir sorguyu geçersiz kılmaması gerektiği açıktır.

  • Mongo sürücüsü, yayınlama işlevi ve birleştirme kutusunun aynı işlemde hatta aynı makinede çalışması gerekmez. Bazı uygulamalar karmaşık canlı sorgular çalıştırır ve veritabanını izlemek için daha fazla CPU'ya ihtiyaç duyar. Diğerlerinin yalnızca birkaç farklı sorgusu vardır (bir blog motoru hayal edin), ancak muhtemelen birçok bağlı istemci - bunların birleştirme kutuları için daha fazla CPU'ya ihtiyacı var. Bu bileşenleri ayırmak, her bir parçayı bağımsız olarak ölçeklendirmemizi sağlayacaktır.

  • Birçok veritabanı, bir satır güncellendiğinde tetiklenen tetikleyicileri destekler ve eski ve yeni satırları sağlar. Bu özellikle, bir veritabanı sürücüsü değişiklikleri sorgulamak yerine bir tetikleyici kaydedebilir.


İmleç dışı verileri yayınlamak için Meteor.publish'in nasıl kullanılacağına dair herhangi bir örnek var mı? Yanıtta bahsedilen eski bir dinlenme API'sinin sonuçları gibi mi?
Tony

@Tony: Belgelerdedir. Oda sayım örneğini kontrol edin.
Mitar

Bültenleri 0.7, 0.7.1 yılında, 0.7.2 Meteor çoğu sorguları için Sürücü (istisnalar dikkate OPLOG geçiş dikkati çekiyor skip, $nearve $whereCPU yükü, ağ bant genişliği çok daha verimlidir ve başvuru ölçekleme verir içeren sorgular) sunucular.
imslavko

Ne zaman her kullanıcı aynı verileri görmez? 1. farklı konulara abone oldular .2. farklı rolleri vardır, bu nedenle aynı ana konu içinde onlara ulaşmaması gereken birkaç mesaj vardır.
tgkprog

@debergalis önbellek geçersiz kılınması ile ilgili, belki de benim kağıt gelen fikirler bulacaksınız vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf değerli
qbolec

29

Deneyimlerime göre, Meteor'da büyük bir koleksiyon paylaşırken birçok müşteriyi kullanmak, 0.7.0.1 sürümünden itibaren esasen işe yaramaz. Nedenini açıklamaya çalışacağım.

Yukarıdaki gönderide ve ayrıca https://github.com/meteor/meteor/issues/1821 adresinde açıklandığı gibi , meteor sunucusu, birleştirme kutusunda her müşteri için yayınlanan verilerin bir kopyasını saklamak zorundadır . Bu, Meteor büyüsünün gerçekleşmesine izin veren şeydir, ancak aynı zamanda herhangi bir büyük paylaşılan veritabanının tekrar tekrar düğüm sürecinin belleğinde tutulmasına neden olur. ( Meteor bir koleksiyonun statik olduğunu söylemenin bir yolu var mı (asla değişmeyecek)? ) Gibi statik koleksiyonlar için olası bir optimizasyon kullanırken bile , Düğüm işleminin CPU ve Bellek kullanımıyla ilgili büyük bir sorun yaşadık.

Bizim durumumuzda, her müşteriye tamamen statik olan 15 bin belgeden oluşan bir koleksiyon yayınlıyorduk. Sorun şu ki, bağlantı kurulduktan sonra bu belgelerin bir istemcinin birleştirme kutusuna (bellekte) kopyalanması Düğüm işlemini neredeyse bir saniye için% 100 CPU'ya getirdi ve büyük bir ek bellek kullanımına neden oldu. Bu, doğası gereği ölçeklenemez, çünkü bağlanan herhangi bir istemci sunucuyu diz çöktürür (ve eşzamanlı bağlantılar birbirini engeller) ve bellek kullanımı istemci sayısında doğrusal olarak artar. Bizim durumumuzda, aktarılan ham veri yalnızca yaklaşık 5MB olmasına rağmen , her istemci ~ 60MB ek bellek kullanımına neden oldu.

Bizim durumumuzda, koleksiyon statik olduğundan, tüm belgeleri .jsonnginx tarafından gziplenmiş bir dosya olarak göndererek ve bunları anonim bir koleksiyona yükleyerek bu sorunu çözdük ve sonuçta ek CPU olmadan yalnızca ~ 1 MB veri aktarımı sağladık. veya düğüm sürecinde bellek ve çok daha hızlı yükleme süresi. Bu koleksiyon üzerindeki tüm işlemler _id, Sunucudaki çok daha küçük yayınlardan gelen e-postalar kullanılarak yapıldı ve Meteor'un faydalarının çoğunun muhafaza edilmesini sağladı. Bu, uygulamanın daha fazla müşteriye ölçeklenmesini sağladı. Ek olarak, uygulamamız çoğunlukla salt okunur olduğu için, her Node örneği tek iş parçacıklı olduğundan, yük dengelemeyle (tek bir Mongo ile) nginx arkasında birden fazla Meteor örneği çalıştırarak ölçeklenebilirliği daha da iyileştirdik.

Bununla birlikte, birden fazla müşteri arasında büyük, yazılabilir koleksiyonların paylaşılması sorunu, Meteor tarafından çözülmesi gereken bir mühendislik sorunudur. Muhtemelen her müşteri için her şeyin bir kopyasını saklamaktan daha iyi bir yol vardır, ancak bu, dağıtılmış bir sistem sorunu olarak ciddi bir düşünce gerektirir. Büyük CPU ve bellek kullanımının mevcut sorunları ölçeklenmeyecek.


@Harry oplog bu durumda önemli değil; veriler durağandı.
Andrew Mao

Neden sunucu tarafı minimongo kopyalarının farklarını yapmıyor? Belki bunların hepsi 1.0'da değişmiştir? Demek istediğim, genellikle aynıdırlar, umarım, geri çağırdığı işlevler bile benzer olur (bunu takip ediyorsam, orada da depolanmış ve potansiyel olarak farklıdır.)
MistereeDevlord

@MistereeDevlord Müşteri verilerindeki değişikliklerin ve önbelleklerin ayrılması şu anda ayrıdır. Herkes aynı verilere sahip olsa ve yalnızca bir farklılık gerekli olsa bile, sunucu bunları aynı şekilde ele alamayacağı için istemci başına önbellek farklılık gösterir. Bu kesinlikle mevcut uygulamaya göre daha akıllıca yapılabilir.
Andrew Mao

@AndrewMao İstemciye gönderirken gzip'lenmiş dosyaların güvenli olduğundan nasıl emin olursunuz, yani sadece oturum açmış bir istemci ona erişebilir?
FullStack

4

Bu soruyu cevaplamak için kullanabileceğiniz deney:

  1. Bir test meteoru kurun: meteor create --example todos
  2. Webkit denetçisi (WKI) altında çalıştırın.
  3. Kablo boyunca hareket eden XHR mesajlarının içeriğini inceleyin .
  4. Tüm koleksiyonun tel boyunca hareket etmediğine dikkat edin.

WKI'nin nasıl kullanılacağına ilişkin ipuçları için bu makaleye göz atın . Biraz güncel değil, ancak özellikle bu soru için çoğunlukla hala geçerli.


2
Oylama mekanizmasının açıklaması: eventedmind.com/posts/meteor-liveresultsset
cmather

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.