Насколько эффективным может Метеор быть вместе с огромной коллекцией среди многих клиентов?

Представьте себе следующий случай:

  • 1000 клиентов подключены к странице Meteor, отображающей содержимое коллекции "Somestuff" .

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

  • Кто-то вставляет новый элемент в коллекцию "Somestuff"

Что произойдет:

  • Все Meteor.Collection на клиентах будут обновлены, то есть вставка переадресовывается ко всем из них (что означает одно вставное сообщение, отправленное 1000 клиентам).

Какова стоимость в терминах процессора для сервера, чтобы определить, какой клиент нужно обновить?

Точно ли, что только вставленное значение будет перенаправлено клиентам, а не всему списку?

Как это работает в реальной жизни? Существуют ли какие-либо критерии или эксперименты такого масштаба?

Ответ 1

Короткий ответ заключается в том, что по проводам отправляются только новые данные. Вот как это работает.

Существуют три важные части сервера Meteor, которые управляют subscriptions: функция публикации, которая определяет логику для чего данные, предоставляемые подпиской; монгольский водитель, который следит за база данных для изменений; и блок слияния, который объединяет все клиентские активные подписки и отправляет их по сети на клиент.

Опубликовать функции

Каждый раз, когда клиент Meteor подписывается на коллекцию, сервер запускает публиковать функцию. Задача функции публикации заключается в определении набора документов, которые его клиент должен иметь и отправлять каждое свойство документа в поле слияния. Он запускается один раз для каждого нового клиента-подписчика. Вы может поместить любой JavaScript, который вы хотите в функцию публикации, например произвольно сложное управление доступом с помощью this.userId. Публикация функция отправляет данные в поле слияния, вызывая this.added, this.changed и this.removed. См. полная публикация документации для более подробная информация.

Большинство функций публикации не нужно гадать с помощью низкоуровневого added, changed и removed API. Если функция публикации возвращает Mongo курсор, Meteor-сервер автоматически соединяет выходные данные Mongo драйвер (insert, update и removed обратные вызовы)) на вход (this.added, this.changed и this.removed). Это довольно аккуратно что вы можете выполнить все проверки прав доступа в функции публикации и затем напрямую подключите драйвер базы данных к ящику слияния без какого-либо пользователя код в пути. И когда автообновление включено, даже этот маленький бит hidden: сервер автоматически настраивает запрос для всех документов в каждом собирать и выталкивать их в поле слияния.

С другой стороны, вы не ограничены публикацией запросов к базе данных. Например, вы можете написать функцию публикации, которая считывает позицию GPS от устройства внутри Meteor.setInterval или опроса устаревшего API REST с другой веб-службы. В этих случаях вы должны испускать изменения в слияние, вызывая низкоуровневые added, changed и removed DDP API.

Драйвер Mongo

Задача гонщика Mongo - следить за базами данных Mongo за изменениями живые запросы. Эти запросы выполняются непрерывно и возвращают обновления в виде изменение результатов путем вызова added, removed и changed обратных вызовов.

Mongo - это не база данных в реальном времени. Так опрос водителей. Он держит в-копии последнего результата запроса для каждого активного запроса в реальном времени. На каждый цикл опроса, он сравнивает новый результат с предыдущим сохраненным результат, вычисляя минимальный набор added, removed и changed события, которые описывают разницу. Если зарегистрировано несколько абонентов обратные вызовы для одного и того же прямого запроса, драйвер просматривает только одну копию запрос, вызывающий каждый зарегистрированный обратный вызов с тем же результатом.

Каждый раз, когда сервер обновляет коллекцию, драйвер пересчитывает каждый живой запрос в этой коллекции (будущие версии Meteor будут API масштабирования для ограничения того, какие живые запросы пересчитываются при обновлении.) драйвер также проверяет каждый живой запрос на 10-секундный таймер, чтобы поймать внеполосные обновления базы данных, минуя сервер Meteor.

Блок слияния

Задача поля слияния состоит в объединении результатов (added, changed и removed звонки) всех активных функций публикации клиента в единые данные поток. Для каждого подключенного клиента есть один блок слияния. Он содержит полная копия клиентского кэша minimongo.

В вашем примере с помощью только одной подписки поле слияния по существу, сквозной. Но более сложное приложение может иметь несколько которые могут перекрываться. Если две подписки устанавливают тот же атрибут в том же документе, поле слияния решает, какое значение имеет приоритет и только отправляет это клиенту. Мы не выставили API для настройки приоритета подписки. На данный момент приоритетом является определяется заказом, который клиент подписывает наборы данных. Первый подписка на клиента имеет наивысший приоритет, второй подписка является следующей самой высокой и т.д.

Поскольку поле слияния содержит состояние клиента, оно может отправлять минимальное количество данных, чтобы каждый клиент обновлялся, независимо от того, что публикует функция передает его.

Что происходит при обновлении

Итак, теперь мы создали сцену для вашего сценария.

У нас есть 1000 подключенных клиентов. Каждый из них подписывается на одну и ту же живую Mongo query (Somestuff.find({})). Поскольку запрос для каждого клиента одинаковый, драйвер работает только один живой запрос. Имеется 1000 активных полей слияния. А также каждая функция публикации клиента зарегистрировала added, changed и removed в этом живом запросе, который подается в один из полей слияния. Ничего другого не связано с полями слияния.

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

Далее, функции публикации. Здесь мало что происходит: каждый из 1000 обратных вызовов insert толкает данные в поле слияния посредством вызывая added.

Наконец, каждый блок слияния проверяет эти новые атрибуты на в-копии его клиентского кеша. В каждом случае он обнаруживает, что значения еще не на клиенте и не теневое существующее значение. Так поле слияния выдает сообщение DDP DATA на SockJS-соединение с его клиент и обновляет свою копию на стороне сервера в памяти.

Общая стоимость ЦП - это стоимость разграничения одного запроса Mongo, плюс стоимость 1000 слияний, проверяющих состояние своих клиентов и создание нового DDP. Единственными данными, которые протекают по проводу, являются одиночные данные Объект JSON, отправленный каждому из 1000 клиентов, соответствующий новому документ в базе данных, а также одно сообщение RPC для сервера из клиент, который сделал оригинальную вставку.

Оптимизация

Здесь мы определенно планировали.

  • Более эффективный драйвер Mongo. Мы оптимизирован драйвер в 0.5.1 для запуска только одного наблюдателя на отдельный запрос.

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

  • Драйвер Mongo, функция публикации и слияние не нужно запускать в том же процессе или даже на одной машине. Некоторые приложения запускать сложные живые запросы и нуждаться в большем количестве CPU для просмотра базы данных. У других есть только несколько отдельных запросов (представьте себе движок блога), но возможно, много подключенных клиентов - для этого требуется больше CPU для слияния коробки. Разделение этих компонентов позволит нам масштабировать каждую часть независимо друг от друга.

  • Многие базы данных поддерживают триггеры, которые срабатывают при обновлении строки и предоставить старые и новые строки. С этой функцией драйвер базы данных может регистрировать триггер вместо опроса для изменений.

Ответ 2

По моему опыту, использование многих клиентов при совместном использовании огромной коллекции в Meteor практически неработоспособно, начиная с версии 0.7.0.1. Я попытаюсь объяснить, почему.

Как описано выше, а также в https://github.com/meteor/meteor/issues/1821, метеоритный сервер должен хранить копию опубликованных данных для каждого клиента в поле слияния. Это то, что позволяет магии Метеор, но также приводит к тому, что любые большие общие базы данных многократно хранятся в памяти процесса node. Даже при использовании возможной оптимизации для статических коллекций, таких как (Есть ли способ сказать метеору, что коллекция статична (никогда не изменится)?), мы столкнулись с огромной проблемой использования процессора и памяти в процессе node.

В нашем случае мы публиковали сборник из 15 тыс. документов каждому клиенту, который был полностью статичным. Проблема заключается в том, что копирование этих документов в поле слияния клиентов (в памяти) при соединении в основном привело к процессу node к 100% процессору в течение почти секунды и привело к большому дополнительному использованию памяти. Это неотвратимо, потому что любой подключаемый клиент доводит сервер до своих коленей (а одновременные соединения блокируют друг друга), а использование памяти будет линейно увеличиваться по количеству клиентов. В нашем случае каждый клиент вызвал дополнительное использование ~ 60 МБ, хотя исходные данные передавались только около 5 МБ.

В нашем случае, поскольку коллекция была статической, мы решили эту проблему, отправив все документы в виде файла .json, который был gzipped от nginx, и загрузив их в анонимную коллекцию, в результате чего была передана только 1 МБ данных без дополнительного процессора или памяти в процессе node и гораздо более быстрого времени загрузки. Все операции над этой коллекцией выполнялись с использованием _id из гораздо меньших публикаций на сервере, что позволило сохранить большую часть преимуществ Meteor. Это позволило приложению масштабироваться для большего числа клиентов. Кроме того, поскольку наше приложение в основном доступно только для чтения, мы также улучшили масштабируемость, запустив несколько экземпляров Meteor за nginx с балансировкой нагрузки (хотя и с одним Mongo), так как каждый экземпляр node является однопоточным.

Однако проблема совместного использования больших, записываемых коллекций среди нескольких клиентов - это инженерная проблема, которую необходимо решить Meteor. Вероятно, есть лучший способ, чем хранить копию всего для каждого клиента, но для этого требуется серьезная мысль как проблема распределенных систем. Текущие проблемы массового использования процессора и памяти просто не будут масштабироваться.

Ответ 3

Эксперимент, который вы можете использовать для ответа на этот вопрос:

  • Установите тестовый метеорит: meteor create --example todos
  • Запустите его под инспектором Webkit (WKI).
  • Изучите содержимое сообщений XHR, перемещающихся по проводу.
  • Обратите внимание, что вся коллекция не перемещается по проводу.

Чтобы узнать, как использовать WKI, ознакомьтесь с этой статьей . Это немного устарело, но в основном все еще актуально, особенно для этого вопроса.