Наскільки ефективним може бути Meteor під час обміну величезною колекцією серед багатьох клієнтів?


100

Уявіть такий випадок:

  • 1000 клієнтів підключені до сторінки "Метеор", що відображає вміст колекції "Somestuff".

  • "Somestuff" - колекція, що містить 1000 предметів.

  • Хтось вставляє новий елемент у колекцію "Somestuff"

Що станеться:

  • Усі Meteor.Collections клієнтів будуть оновлені, тобто вставка, переслана всім їм (що означає одне повідомлення про вставку, надіслане 1000 клієнтам)

Яка вартість CPU для сервера, щоб визначити, якого клієнта потрібно оновити?

Чи точно, що клієнтам буде передано лише вставлене значення, а не весь список?

Як це працює в реальному житті? Чи є якісь орієнтири або експерименти такого масштабу?

Відповіді:


119

Коротка відповідь полягає в тому, що лише нові дані надсилаються вниз. Ось як це працює.

Існує три важливі частини сервера Meteor, які керують підписками: функція публікації , яка визначає логіку, які дані надає підписка; водій Монго , який стежить за базою даних для змін; і поле злиття , яке поєднує всі активні підписки клієнта і надсилає їх по мережі клієнту.

Опублікувати функції

Щоразу, коли клієнт Meteor підписується на колекцію, сервер виконує функцію публікації . Завдання функції публікації полягає в тому, щоб визначити набір документів, які повинен мати його клієнт, і відправити кожне властивість документа у поле злиття. Він працює один раз для кожного нового клієнта, який підписався. Ви можете помістити будь-який JavaScript у функцію публікації, наприклад, довільно складний контроль доступу this.userId. Опублікувати функція відправляє дані в поле злиття по телефону this.added, this.changedі this.removed. Докладнішу інформацію див. У повній документації щодо публікації .

Більшість публікувати функції не тинятися навколо з низьким рівнем added, changedі removedAPI, хоча. Якщо опублікувати функція повертає Монго курсора, сервер Метеор автоматично підключає вихід драйвера Монго ( insert, updateі removedзворотні виклики) на вхід коробки злиття ( this.added, this.changedі this.removed). Досить акуратно, що ви можете виконати всі перевірки дозволу наперед у функції опублікування, а потім безпосередньо підключити драйвер бази даних до вікна злиття без жодного коду користувача. А коли ввімкнено автоматичну публікацію, навіть ця маленька частина прихована: сервер автоматично встановлює запит для всіх документів у кожній колекції та заносить їх у поле злиття.

З іншого боку, ви не обмежуєтесь публікацією запитів до бази даних. Наприклад, ви можете написати функцію публікації, яка зчитує GPS-позицію з пристрою всередині Meteor.setIntervalабо опитує застарілий API REST з іншої веб-служби. У тих випадках, ви що випускаються зміни в поле злиття, викликавши низький рівень added, changedі removedDDP API.

Водій Монго

Завдання водія Монго - спостерігати за базою даних Mongo за змінами на запити в реальному часі. Ці запити виконуються постійно та повертають оновлення, оскільки результати змінюються за допомогою дзвінків added, зворотних дзвінків removedта changedзворотних дзвінків

Монго - це не база даних у режимі реального часу. Тож водій опитується. Він зберігає в пам'яті копію останнього результату запиту для кожного активного живого запиту. На кожному циклі опитування, він порівнює новий результат з попереднім збереженим результатом, обчисленням мінімального набору added, removedі changed подій , які описують різницю. Якщо декілька абонентів реєструють зворотні дзвінки для одного і того ж запиту в реальному часі, драйвер дивиться лише одну копію запиту, викликаючи кожен зареєстрований зворотний дзвінок з тим самим результатом.

Кожен раз, коли сервер оновлює колекцію, драйвер перераховує кожен запит на реалізацію цієї колекції (майбутні версії Meteor піддають API масштабування для обмеження того, який перелік переходів в реальному часі буде оновлений. ловити оновлення позабазової бази даних, які обійшли сервер Meteor.

Коробка злиття

Робота в поле злиття є об'єднання результатів ( added, changedі removed дзвінки) всіх активних публікують функцій організму клієнта в єдиний потік даних. Для кожного підключеного клієнта є одне поле злиття. У ньому зберігається повна копія мінімального кешу клієнта.

У вашому прикладі лише з однією підпискою поле злиття по суті є прохідним. Але більш складний додаток може мати кілька підписок, які можуть перекриватися. Якщо обидві підписки встановлюють один і той же атрибут на один і той же документ, поле злиття визначає, яке значення має пріоритет, і надсилає його лише клієнту. Ми ще не відкрили API для встановлення пріоритету передплати. Наразі пріоритет визначається порядком, яким клієнт підписується на набори даних. Перша підписка, яку робить клієнт, має найвищий пріоритет, друга підписка - наступна найвища і так далі.

Оскільки вікно злиття містить стан клієнта, він може надсилати мінімальну кількість даних для постійного оновлення кожного клієнта, незалежно від того, яка функція публікації подає його.

Що відбувається під час оновлення

Тож тепер ми встановили основу для вашого сценарію.

У нас є 1000 підключених клієнтів. Кожен підписаний на один і той же живий запит Mongo ( Somestuff.find({})). Оскільки запит однаковий для кожного клієнта, драйвер виконує лише один живий запит. Є 1000 активних коробок злиття. І кожна функція публікації клієнта зареєструвала запит на added, changedа також removedу цьому запиті, який живиться в одному з полів злиття. Ніщо інше не пов'язане з полями злиття.

Спочатку водій Монго. Коли один із клієнтів вставляє новий документ у Somestuffнього, він запускає перерахунок. Драйвер Mongo повторно виконує запит на всі документи в Somestuff, порівнює результат із попереднім результатом у пам'яті, виявляє, що є один новий документ, і викликає кожен з 1000 зареєстрованих insertзворотних зворотних дзвінків .

Далі, функції публікації. Тут відбувається дуже мало: кожен з 1000 insertзворотних викликів передає дані у поле злиття, зателефонувавши added.

Нарешті, кожне поле злиття перевіряє ці нові атрибути на копії кеш-пам'яті свого клієнта в пам'яті. У кожному конкретному випадку він виявляє, що значення ще не є клієнтом і не затьмарюють існуюче значення. Таким чином, поле злиття DATAпосилає DDP- повідомлення на підключенні SockJS до свого клієнта та оновлює його копію пам'яті на стороні сервера.

Загальна вартість процесора - це вартість, яка відрізняється одним запитом Mongo, плюс вартість 1000 скриньок злиття, що перевіряють стан своїх клієнтів та створюють нову корисну навантаження повідомлення DDP. Єдині дані, що протікають по дроту, - це один об'єкт JSON, що надсилається кожному з 1000 клієнтів, що відповідає новому документу в базі даних, плюс одне повідомлення RPC на сервер від клієнта, який зробив оригінальну вставку.

Оптимізація

Ось що ми напевно запланували.

  • Більш ефективний водій Монго. Ми оптимізували драйвер у 0.5.1 для запуску лише одного спостерігача за окремим запитом.

  • Не кожна зміна БД повинна викликати перерахунок запиту. Ми можемо внести деякі автоматизовані вдосконалення, але найкращим підходом є API, який дозволяє розробнику вказувати, які запити потрібно повторити. Наприклад, розробнику очевидно, що вставлення повідомлення в одну кімнату чату не повинно визнати недійсним запит на реалізацію повідомлень у другій кімнаті.

  • Драйвер Mongo, функція публікації та поле злиття не потрібно запускати в одному процесі або навіть на одній машині. Деякі програми виконують складні запити в реальному часі і для перегляду бази даних потрібно більше процесора. Інші мають лише кілька чітких запитів (уявіть собі блог-движок), але, можливо, багато підключених клієнтів - їм потрібно більше процесора для злиття вікон. Відокремлення цих компонентів дозволить нам масштабувати кожен фрагмент самостійно.

  • Багато баз даних підтримують тригери, які спрацьовують, коли рядок оновлюється та надає старі та нові рядки. За допомогою цієї функції драйвер бази даних може зареєструвати тригер замість опитування змін.


Чи є приклад того, як використовувати Meteor.publish для публікації не курсорних даних? Такі як результати від застарілого API відпочинку, згаданого у відповіді?
Тоні

@Tony: Це в документації. Перевірте приклад підрахунку кімнати.
Мітар

Варто відзначити , що в версіях 0.7, 0.7.1, 0.7.2 Метеора переключився на OpLog Спостерігайте драйвера для більшості запитів (виключення skip, $nearі $whereмістять запити) , який є набагато більш ефективним в CPU навантаженні, пропускної здатності мережі і дозволяє масштабування додатки сервери.
imslavko

Що робити, коли не кожен користувач бачить однакові дані. 1. вони підписалися на різні теми .2. вони мають різні ролі, тому в межах однієї основної теми є декілька повідомлень, які, як передбачається, не доходять до них.
tgkprog

@debergalis щодо недійсного кешу, можливо, ви знайдете ідеї з моєї роботи vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf варто
qbolec

29

З мого досвіду, використання багатьох клієнтів під час обміну величезною колекцією в Meteor є практично нездійсненним, як це стосується версії 0.7.0.1. Спробую пояснити, чому.

Як описано у вищезгаданому дописі, а також у https://github.com/meteor/meteor/isissue/1821 , метеорний сервер повинен зберігати копію опублікованих даних для кожного клієнта у полі злиття . Це те, що дозволяє здійснити магію Метеора, але також призводить до того, що будь-які великі спільні бази даних неодноразово зберігаються в пам'яті процесу вузла. Навіть при використанні можливої ​​оптимізації для статичних колекцій, таких як в ( Чи існує спосіб сказати, що колекція метеора статична (ніколи не зміниться)? ), Ми зіткнулися з величезною проблемою з використанням процесора та пам'яті процесу Node.

У нашому випадку ми публікували колекцію 15 тис. Документів для кожного клієнта, яка була повністю статичною. Проблема полягає в тому, що копіювання цих документів у поле злиття клієнта (у пам'яті) при з'єднанні в основному доводило процес Node до 100% ЦП протягом майже секунди і призводило до великого додаткового використання пам'яті. Це за своєю суттю не можна змінити, оскільки будь-який клієнт, що підключається, поставить сервер на коліна (і одночасне з'єднання заблокує один одного), і використання пам'яті буде лінійно зростати за кількістю клієнтів. У нашому випадку кожен клієнт спричинив додаткове використання ~ 60 МБ пам’яті, навіть якщо передані необроблені дані склали лише близько 5 МБ.

У нашому випадку, оскільки колекція була статичною, ми вирішили цю проблему, надіславши всі документи у вигляді .jsonфайлу, зібраного nginx, та завантаживши їх в анонімну колекцію, в результаті чого було передано дані ~ 1 МБ без додаткових процесорів або пам'ять у вузловому процесі та набагато швидший час завантаження. Усі операції над цією колекцією були виконані за допомогою _ids із значно менших публікацій на сервері, що дозволило зберегти більшість переваг Meteor. Це дозволило додатку масштабуватись значно більше клієнтів. Крім того, оскільки наш додаток здебільшого лише для читання, ми додатково покращили масштабованість, запустивши декілька екземплярів Meteor за nginx з балансуванням навантаження (хоча з одним Mongo), оскільки кожен екземпляр Node є однопоточним.

Однак питання спільного використання великих колекцій, що записуються, серед декількох клієнтів - це інженерна проблема, яку потрібно вирішити Meteor. Напевно, є кращий спосіб, ніж зберігати копію всього для кожного клієнта, але для цього потрібна серйозна думка як проблема розподілених систем. Поточні проблеми масового використання процесора та пам'яті просто не розширяться.


@ Harry oplog не має значення в цій ситуації; дані були статичними.
Ендрю Мао

Чому це не робить розбіжностей копій на сервері minimalongo копій? Можливо, це все змінилося в 1,0? Я маю на увазі, як правило, вони однакові, я б сподівався, що навіть функції, які він передзвонює, були б подібними (якщо я слідкую за тим, що це те, що зберігається там і, можливо, відрізняється.)
MistereeDevlord

@MistereeDevlord Наразі зміни та кеші даних клієнтів зараз окремі. Навіть якщо всі мають однакові дані і потрібен лише один розріз, кеш-пам'ять на кожного клієнта відрізняється, оскільки сервер не може обробляти їх однаково. Це, безумовно, можна зробити розумнішим щодо існуючої реалізації.
Андрій Мао

@AndrewMao Як ви гарантуєте, що gzipped файли захищені при відправці їх клієнту, тобто лише клієнт, який увійшов до нього, може отримати доступ до нього?
FullStack

4

Експеримент, за допомогою якого можна відповісти на це запитання:

  1. Встановіть тестовий метеор: meteor create --example todos
  2. Запустіть його під інспектором Webkit (WKI).
  3. Вивчіть вміст повідомлень XHR, що рухаються по дроту.
  4. Зауважте, що вся колекція не переміщена по дроту.

Щоб отримати поради щодо використання WKI, ознайомтеся з цією статтею . Це трохи застаріло, але в основному все ще діє, особливо для цього питання.


2
Пояснення механізму опитування: eventedmind.com/posts/meteor-liveresultsset
cmather

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.