Seberapa efisien Meteor bisa sembari membagikan banyak koleksi di antara banyak klien?

100

Bayangkan kasus berikut:

  • 1.000 klien terhubung ke halaman Meteor yang menampilkan konten dari koleksi "Somestuff".

  • "Somestuff" adalah koleksi yang menampung 1.000 item.

  • Seseorang memasukkan item baru ke dalam koleksi "Somestuff"

Apa yang akan terjadi:

  • Semua Meteor.Collectionklien akan diperbarui yaitu penyisipan diteruskan ke semuanya (yang berarti satu pesan penyisipan dikirim ke 1.000 klien)

Berapa biaya dalam hal CPU untuk server untuk menentukan klien mana yang perlu diperbarui?

Apakah akurat bahwa hanya nilai yang dimasukkan yang akan diteruskan ke klien, dan bukan seluruh daftar?

Bagaimana cara kerjanya dalam kehidupan nyata? Apakah ada tolok ukur atau eksperimen dengan skala seperti itu?

Flavien Volken
sumber

Jawaban:

119

Jawaban singkatnya adalah bahwa hanya data baru yang dikirim melalui jaringan. Begini cara kerjanya.

Ada tiga bagian penting dari server Meteor yang mengelola langganan: fungsi publikasi , yang mendefinisikan logika untuk data apa yang disediakan langganan; yang sopir Mongo , yang jam tangan database untuk perubahan; dan kotak gabungan , yang menggabungkan semua langganan aktif klien dan mengirimkannya melalui jaringan ke klien.

Publikasikan fungsi

Setiap kali klien Meteor berlangganan koleksi, server menjalankan fungsi publikasi . Tugas fungsi publikasi adalah mencari tahu kumpulan dokumen yang harus dimiliki kliennya dan mengirim setiap properti dokumen ke dalam kotak gabungan. Ini berjalan sekali untuk setiap klien baru yang berlangganan. Anda dapat meletakkan JavaScript apa pun yang Anda inginkan dalam fungsi terbitkan, seperti penggunaan kontrol akses yang rumit secara sewenang-wenang this.userId. Fungsi terbitkan mengirimkan data ke kotak gabungan dengan memanggil this.added, this.changeddan this.removed. Lihat dokumentasi publikasi lengkap untuk lebih jelasnya.

Sebagian besar fungsi publikasi tidak harus dipusingkan dengan level rendah added, changeddan removedAPI. Jika mempublikasikan kembali fungsi kursor Mongo, server Meteor otomatis menghubungkan output dari driver Mongo ( insert, update, dan removedcallback) ke input dari kotak merge ( this.added, this.changeddan this.removed). Cukup rapi bahwa Anda dapat melakukan semua pemeriksaan izin di depan dalam fungsi publikasi dan kemudian langsung menghubungkan driver database ke kotak gabungan tanpa ada kode pengguna yang menghalangi. Dan saat penerbitan otomatis diaktifkan, meskipun sedikit ini disembunyikan: server secara otomatis menyiapkan kueri untuk semua dokumen di setiap koleksi dan memasukkannya ke dalam kotak gabungan.

Di sisi lain, Anda tidak terbatas pada kueri database penerbitan. Misalnya, Anda dapat menulis fungsi publikasi yang membaca posisi GPS dari perangkat di dalam Meteor.setInterval, atau mengumpulkan REST API lama dari layanan web lain. Dalam kasus tersebut, Anda akan memancarkan perubahan ke kotak merge dengan memanggil tingkat rendah added, changeddan removedDDP API.

Pengemudi Mongo

Tugas pengemudi Mongo adalah mengawasi database Mongo untuk melihat perubahan pada kueri langsung. Pertanyaan ini terus berjalan dan kembali update sebagai perubahan hasil dengan menelepon added, removeddan changedcallback.

Mongo bukanlah database waktu nyata. Jadi pengemudi jajak pendapat. Itu menyimpan salinan dalam memori dari hasil kueri terakhir untuk setiap kueri langsung yang aktif. Pada setiap siklus polling, membandingkan hasil baru dengan hasil disimpan sebelumnya, menghitung set minimum added, removeddan changed peristiwa yang menggambarkan perbedaan. Jika beberapa penelepon mendaftarkan callback untuk kueri langsung yang sama, pengemudi hanya akan melihat satu salinan kueri, memanggil setiap callback terdaftar dengan hasil yang sama.

Setiap kali server memperbarui koleksi, driver menghitung ulang setiap kueri langsung pada koleksi itu (Versi Meteor yang akan datang akan mengekspos API penskalaan untuk membatasi kueri langsung mana yang dihitung ulang saat pembaruan.) Pengemudi juga memeriksa setiap kueri langsung pada timer 10 detik ke menangkap update database out-of-band yang melewati server Meteor.

Kotak gabungan

Tugas kotak gabungan adalah menggabungkan hasil ( added, changeddan removed panggilan) dari semua fungsi publikasi aktif klien ke dalam aliran data tunggal. Ada satu kotak gabungan untuk setiap klien yang terhubung. Ini menyimpan salinan lengkap dari cache minimongo klien.

Dalam contoh Anda dengan hanya satu langganan, kotak gabungan pada dasarnya adalah pass-through. Tetapi aplikasi yang lebih kompleks dapat memiliki banyak langganan yang mungkin tumpang tindih. Jika dua langganan menyetel atribut yang sama pada dokumen yang sama, kotak gabungan memutuskan nilai mana yang diprioritaskan dan hanya mengirimkannya ke klien. Kami belum mengekspos API untuk menyetel prioritas langganan. Untuk saat ini, prioritas ditentukan oleh urutan langganan klien ke kumpulan data. Langganan pertama yang dibuat klien memiliki prioritas tertinggi, langganan kedua adalah tertinggi berikutnya, dan seterusnya.

Karena kotak gabungan menyimpan status klien, kotak ini dapat mengirim jumlah data minimum untuk menjaga setiap klien tetap up-to-date, apa pun fungsi publikasi yang memberinya makan.

Apa yang terjadi pada pembaruan

Jadi sekarang kami telah menyiapkan panggung untuk skenario Anda.

Kami memiliki 1.000 klien yang terhubung. Masing-masing berlangganan ke kueri Mongo langsung yang sama ( Somestuff.find({})). Karena kueri sama untuk setiap klien, driver hanya menjalankan satu kueri langsung. Ada 1.000 kotak gabungan aktif. Dan masing-masing klien mempublikasikan fungsi mendaftarkan added, changeddan removedpada kueri hidup yang feed ke dalam salah satu kotak merge. Tidak ada lagi yang terhubung ke kotak gabungan.

Pertama, pengemudi Mongo. Ketika salah satu klien memasukkan dokumen baru ke Somestuffdalamnya, itu memicu penghitungan ulang. Driver Mongo menjalankan kembali kueri untuk semua dokumen di Somestuff, membandingkan hasil dengan hasil sebelumnya di memori, menemukan bahwa ada satu dokumen baru, dan memanggil masing-masing dari 1.000 insertcallback terdaftar .

Selanjutnya, fungsi publikasi. Sangat sedikit yang terjadi di sini: masing-masing dari 1.000 insertpanggilan balik mendorong data ke kotak gabungan dengan memanggil added.

Akhirnya, setiap kotak gabungan memeriksa atribut baru ini terhadap salinan dalam memori dari cache kliennya. Dalam setiap kasus, ia menemukan bahwa nilai belum ada di klien dan tidak membayangi nilai yang sudah ada. Jadi kotak gabungan memancarkan DATApesan DDP pada sambungan SockJS ke kliennya dan memperbarui salinan dalam memori sisi servernya.

Total biaya CPU adalah biaya untuk membedakan satu kueri Mongo, ditambah biaya 1.000 kotak gabungan untuk memeriksa status klien mereka dan membuat muatan pesan DDP baru. Satu-satunya data yang mengalir melalui kabel adalah objek JSON tunggal yang dikirim ke masing-masing 1.000 klien, sesuai dengan dokumen baru dalam database, ditambah satu pesan RPC ke server dari klien yang membuat penyisipan asli.

Optimasi

Inilah yang telah kami rencanakan.

  • Pengemudi Mongo yang lebih efisien. Kami mengoptimalkan driver di 0.5.1 untuk hanya menjalankan satu pengamat per kueri berbeda.

  • Tidak setiap perubahan DB harus memicu penghitungan ulang kueri. Kami dapat membuat beberapa peningkatan otomatis, tetapi pendekatan terbaik adalah API yang memungkinkan pengembang menentukan kueri mana yang perlu dijalankan ulang. Misalnya, jelas bagi pengembang bahwa memasukkan pesan ke dalam satu ruang obrolan seharusnya tidak membatalkan kueri langsung untuk pesan di ruang kedua.

  • Driver Mongo, fungsi terbitkan, dan kotak penggabungan tidak perlu dijalankan dalam proses yang sama, atau bahkan di mesin yang sama. Beberapa aplikasi menjalankan kueri langsung yang kompleks dan membutuhkan lebih banyak CPU untuk mengawasi database. Yang lain hanya memiliki beberapa kueri berbeda (bayangkan mesin blog), tetapi mungkin banyak klien yang terhubung - ini membutuhkan lebih banyak CPU untuk kotak gabungan. Memisahkan komponen-komponen ini akan memungkinkan kita menskalakan setiap bagian secara independen.

  • Banyak database mendukung pemicu yang diaktifkan saat baris diperbarui dan menyediakan baris lama dan baru. Dengan fitur itu, driver database bisa mendaftarkan pemicu daripada melakukan polling untuk perubahan.

debergalis
sumber
Apakah ada contoh bagaimana menggunakan Meteor.publish untuk mempublikasikan data non-kursor? Seperti hasil dari rest API lama yang disebutkan dalam jawaban?
Tony
@Tony: Ada dalam dokumentasi. Periksa contoh penghitungan ruangan.
Mitar
Perlu dicatat bahwa dalam rilis 0.7, 0.7.1, 0.7.2 Meteor beralih ke OpLog Observe Driver untuk sebagian besar kueri (pengecualian adalah skip, $neardan $whereberisi kueri) yang jauh lebih efisien dalam beban CPU, bandwidth jaringan, dan memungkinkan aplikasi penskalaan server.
imslavko
Bagaimana jika tidak setiap pengguna melihat data yang sama. 1. mereka berlangganan topik yang berbeda .2. Mereka memiliki peran yang berbeda sehingga dalam topik utama yang sama, terdapat beberapa pesan yang tidak seharusnya sampai kepada mereka.
tgkprog
@debergalis mengenai pembatalan cache, mungkin Anda akan menemukan ide dari makalah saya vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf bermanfaat
qbolec
29

Dari pengalaman saya, menggunakan banyak klien sambil berbagi banyak koleksi di Meteor pada dasarnya tidak bisa dijalankan, mulai versi 0.7.0.1. Saya akan mencoba menjelaskan mengapa.

Seperti yang dijelaskan dalam posting di atas dan juga di https://github.com/meteor/meteor/issues/1821 , server meteor harus menyimpan salinan data yang diterbitkan untuk setiap klien di kotak gabungan . Inilah yang memungkinkan keajaiban Meteor terjadi, tetapi juga menghasilkan database bersama yang besar yang berulang kali disimpan dalam memori proses node. Bahkan ketika menggunakan kemungkinan pengoptimalan untuk koleksi statis seperti di ( Apakah ada cara untuk memberi tahu meteor bahwa koleksi itu statis (tidak akan pernah berubah)? ), Kami mengalami masalah besar dengan penggunaan CPU dan Memori dari proses Node.

Dalam kasus kami, kami menerbitkan koleksi 15 ribu dokumen untuk setiap klien yang benar-benar statis. Masalahnya adalah bahwa menyalin dokumen-dokumen ini ke kotak penggabungan klien (dalam memori) setelah koneksi pada dasarnya membawa proses Node ke 100% CPU selama hampir satu detik, dan menghasilkan penggunaan memori tambahan yang besar. Ini secara inheren tidak dapat diskalakan, karena setiap klien yang terhubung akan membuat server bertekuk lutut (dan koneksi simultan akan memblokir satu sama lain) dan penggunaan memori akan meningkat secara linier dalam jumlah klien. Dalam kasus kami, setiap klien menyebabkan penggunaan memori tambahan ~ 60MB , meskipun data mentah yang ditransfer hanya sekitar 5MB.

Dalam kasus kami, karena koleksi itu statis, kami menyelesaikan masalah ini dengan mengirim semua dokumen sebagai .jsonfile, yang di-gzip oleh nginx, dan memuatnya ke dalam koleksi anonim, menghasilkan hanya transfer data ~ 1MB tanpa CPU tambahan atau memori dalam proses node dan waktu buka yang lebih cepat. Semua operasi atas koleksi ini dilakukan dengan menggunakan _ids dari publikasi yang jauh lebih kecil di server, memungkinkan untuk mempertahankan sebagian besar manfaat Meteor. Ini memungkinkan aplikasi untuk menskalakan lebih banyak klien. Selain itu, karena sebagian besar aplikasi kami hanya-baca, kami semakin meningkatkan skalabilitas dengan menjalankan beberapa instance Meteor di belakang nginx dengan load balancing (meskipun dengan satu Mongo), karena setiap instance Node adalah single-threaded.

Namun, masalah berbagi koleksi yang besar dan dapat ditulisi di antara banyak klien merupakan masalah teknik yang perlu diselesaikan oleh Meteor. Mungkin ada cara yang lebih baik daripada menyimpan salinan segalanya untuk setiap klien, tetapi itu membutuhkan pemikiran serius sebagai masalah sistem terdistribusi. Masalah saat ini dari penggunaan CPU dan memori yang masif tidak akan skala.

Andrew Mao
sumber
@Harry oplog tidak masalah dalam situasi ini; datanya statis.
Andrew Mao
Mengapa tidak melakukan diff dari salinan minimongo sisi server? Mungkin itu semua berubah di 1.0? Maksud saya biasanya mereka sama dengan yang saya harapkan, bahkan fungsi yang dipanggil kembali akan serupa (jika saya mengikutinya adalah sesuatu yang disimpan di sana juga dan berpotensi berbeda.)
MistereeDevlord
@MistereeDevlord Perbedaan perubahan dan cache data klien saat ini terpisah. Meskipun setiap orang memiliki data yang sama dan hanya satu perbedaan yang diperlukan, cache per klien berbeda karena server tidak dapat memperlakukannya secara identik. Ini pasti bisa dilakukan dengan lebih cerdas atas implementasi yang ada.
Andrew Mao
@AndrewMao Bagaimana Anda memastikan file gzip diamankan saat mengirimnya ke klien, yaitu hanya klien yang masuk yang dapat mengaksesnya?
FullStack
4

Eksperimen yang dapat Anda gunakan untuk menjawab pertanyaan ini:

  1. Pasang meteor uji: meteor create --example todos
  2. Jalankan di bawah Webkit inspector (WKI).
  3. Periksa konten pesan XHR yang bergerak melintasi kabel.
  4. Amati bahwa seluruh koleksi tidak dipindahkan melintasi kabel.

Untuk tips tentang cara menggunakan WKI, lihat artikel ini . Agak ketinggalan zaman, tetapi sebagian besar masih valid, terutama untuk pertanyaan ini.

javajosh
sumber
2
Penjelasan tentang mekanisme pemungutan suara: eventedmind.com/posts/meteor-liveresultsset
cmather