Уведомления - это сложно. Будь то Facebook, Gmail или ваше собственное приложение, получение пользователям нужной информации в нужное время имеет решающее значение. Слишком болтливый, рискуешь отключиться; слишком тихо, и ваш пользователь может пропустить критическое обновление. По сути, уведомление представляет собой сводку события (изменения состояния), например «Майк отправил вам сообщение», «Ваш рейс задерживается» или «Водитель уезжает через 5 минут». Мы создали декларативную выразительную систему подписки, которая позволяет отдельным пользователям настраивать слушателей для важных для них событий.

Авторы Зик Ниренберг, Алекс Баженов и Омри Бернштейн.

Когда мы запускали Fraight, наша система уведомлений была простой. Каждое бизнес-событие (доставка груза, новое сообщение, телефонный звонок и т. Д.) Запускало мобильное уведомление для каждого сотрудника. Это было идеально для того времени. Наш бизнес координировал перевозки грузовыми автомобилями, и у нас был только 1 сотрудник, перевозивший грузы. Если что-то пойдет не так, мы должны быть уверены, что все знаем. Более того, сложные уведомления и подписки не должны были добавить к нашему ценностному предложению на нашем раннем этапе. Нам нужно было сосредоточиться на том, что нас отличает: диалоговый интерфейс.

Чтобы предсказать проблему масштабируемости, не требуется степень CS. Когда Райан Шрайбер (вице-президент по операциям) начал разделять операционную ответственность с Паркером Холкомбом (генеральным директором), оба их канала уведомлений становились беспорядочными. Мы нанимаем прямо сейчас, и мы понимаем, что проблема станет намного хуже.

По мере того, как наша работа становится все более параллельной, требующей большего количества людей, релевантность каждого уведомления снижалась в зависимости от функции (numEmployees) => 1/numEmployees

Первая идея: каждая загрузка получает user_id

Поначалу решение не показалось слишком сложным: сегментируйте события для сотрудников по нагрузке. Другими словами, события загрузки сегментов по user_id. Мы посчитали, что большинство событий вероятно могут быть легко связаны с нагрузкой, поэтому просто уведомите нужного сотрудника.

Проблема 1: пользователи работают вместе над нагрузками

Мы хотим создать программное обеспечение для совместной работы. Установив связь Load belongsTo User, мы исключили возможность совместной работы двух пользователей над нагрузкой. Это означало, что Райан не мог сказать: «Эй, Паркер, ты можешь посмотреть загрузку 3722, пока я встречаюсь с новым клиентом?» Одна только эта проблема может быть смягчена за счет belongsToMany отношений, но система владения по-прежнему очень ограничивает, потому что ...

Проблема 2: не всегда понятно, с какой нагрузкой связано событие

Иногда события создаются в результате общения, которое сложно связать с нагрузкой. Например, нам может позвонить клиент, который в настоящее время перевозит с нами 5 грузов. Кто должен получать «гудение»?

Проблема 3. Некоторые события вообще не связаны с нагрузками

Некоторые события шире, чем нагрузки. Например, клиент отправляет электронное письмо, чтобы обсудить свою стратегию цепочки поставок на второй квартал. Несмотря на то, что нагрузки являются центральным элементом нашего программного обеспечения, не все четко помещается в загрузку.

Решение: подписки

Мы создали инструмент, который позволяет пользователям подписываться на события. Давайте закрепим некоторую терминологию высокого уровня.

Событие

По сути, Событие можно представить как отдельное изменение в нашей базе данных.

  • verb (думаю, CRUD)
  • subject (table, foreign_key пара)
  • meta (разница измененных полей для обновления, удаления)

Мы создаем события, подключившись к нашей ORM для нескольких таблиц.

Подписка

Подписка - это предикатная функция фильтрации пользователя. Он определяет, актуально ли событие для пользователя. Требуется событие и пользователя. Он возвращает логическое значение о своей релевантности. (event, user) => …relevant?.

Подписка на самом деле не выражается в коде, а сохраняется в базе данных. Он имеет следующие поля

  • user_id
  • channel_id - как уведомить пользователя?
  • criteria - подробнее об этом через секунду, мы создали схему JSON для выражения функции предиката.

Актуальность DAG

Когда происходит событие, мы каскадируем его через наши модели данных, чтобы найти все релевантные записи. Это не просто все связанные записи, а набор записей, которые определяются программно.

Любая модель в нашей системе может зарегистрировать асинхронный метод, который определяет, какие другие записи имеют отношение к запущенному событию.

Например, участники телефонного звонка релевантны звонку.

Пользователи, в свою очередь, переходят каскадом к другим объектам.

Этот процесс продолжается.

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

Чтобы понять, как работают совпадения по подписке, давайте рассмотрим несколько примеров.

«Сообщать мне обо всех новых входящих сообщениях»

В нашей системе мы пишем

По этому запросу сопоставляются все релевантные группы доступности базы данных. Если какой-либо узел группы DAG является входящим сообщением, подписка совпадает, и создается уведомление.

«Расскажите мне обо всех входящих сообщениях клиентов»

Между первым объектом в нашем запросе и вторым существует неявное . Это почти как в CSS. nav a { color: blue; } относится ко всем тегам привязки в теге навигации, а не только к прямым дочерним элементам. Действительно, в нашей базе данных SQL нет organization_id на Message. В группе DAG может быть несколько путей между сообщением и организацией.

Несколько замечаний по производительности

Это безумно крутая система, но у вас, вероятно, возникает странное ощущение в глубине горла. Добавление такого количества логики в середине цикла запроса / ответа может привести к остановке веб-службы. Действительно, наши первые прототипы этой системы были медленными - настолько медленными, что сломали нашу телефонную систему в процессе разработки!

Мы знали, что асинхронные рабочие процессы могут пригодиться для повышения производительности, потому что они могут взять эту дорогостоящую задачу и поставить ее в очередь. Мы разбили процесс на:

  • Сгенерировать событие - мы сохранили это в цикле ответа на запрос. В конце концов, это только один INSERT. После того, как он вставлен, мы ставим задание в очередь и делаем следующий шаг.
  • Build Relevance DAG - это выполняется в работе. Мы получаем все соответствующие записи DAG в памяти, а затем сравниваем их с кэшированной копией всех наших активных подписок. Для каждого матча мы ставим в очередь задание на уведомление
  • Отправить уведомления - это задание, которое фактически отправляет сообщение с уведомлением, будь то SMS, электронная почта или веб-push.

Эта система вернула нас к базовой производительности.

Где мы оказались

  • Ранние тесты показывают сокращение объема новостной ленты примерно на 60%.
  • Мы можем быстро удовлетворить запросы пользователей, не внося изменений в код.
  • Мы не ожидаем каких-либо проблем с производительностью или масштабируемостью как минимум в следующем году.
  • Таким образом, уровень шума снижается, сигнал повышается, пользовательский опыт на высоте.

Куда мы идем отсюда

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

Мы рассматриваем возможность превратить это в микросервис и открыть его исходный код. Дайте нам знать, если вы хотите поработать над этим или использовать!

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