Rails, Heroku и Resque: оптимизация длительных фоновых заданий

#ruby-on-rails #heroku #resque

#ruby-on-rails #heroku #resque

Вопрос:

Мы создаем приложение в стиле tinder, которое позволяет пользователям «лайкать» или «не лайкать» события. С каждым событием связано около 100 ключевых слов. Когда пользователю «нравится» или «не нравится» и событие, мы связываем ключевые слова этого события с пользователем. Пользователи могут быстро получить тысячи ключевых слов.

Мы используем сквозные таблицы для привязки пользователей и событий к ключевым словам (event_keywords и user_keywords). В сквозной таблице есть дополнительный столбец relevance_score с плавающей точкой (например, ключевое слово может быть равно 0.1, если оно очень незначительно или 0.9, если оно очень актуально).

Наша цель — показать пользователям наиболее релевантные события на основе их ключевых слов. Таким образом, Events имеет много event_rankings, которые принадлежат пользователю. Теоретически мы хотим ранжировать все события по-разному для каждого пользователя.

Вот модели:

User.rb:

   has_many :user_keywords, :dependent => :destroy
  has_many :keywords, :through => :user_keywords
  has_many :event_rankings, :dependent => :destroy
  has_many :events, :through => :event_rankings
  

Event.rb

   has_many :event_keywords, :dependent => :destroy
  has_many :keywords, :through => :event_keywords
  has_many :event_rankings, :dependent => :destroy
  has_many :users, :through => :event_rankings
  

userKeyword.rb:

   belongs_to :user
  belongs_to :keyword
  

EventKeyword.rb:

   belongs_to :keyword
  belongs_to :event
  

EventRanking.rb:

   belongs_to :user
  belongs_to :event
  

Ключевое слово.rb:

   has_many :event_keywords, :dependent => :destroy
  has_many :events, :through => :event_keywords
  has_many :user_keywords, :dependent => :destroy
  has_many :users, :through => :user_keywords
  

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

User.rb:

 def calculate_event_relevance(event_id)
  ## Step 1: Find which of the event keywords the user has 
  ## Step 2: Compare those keywords and do math to calculate a score 
  ## Step 3: Update the event_ranking for this user
end
  

Каждый раз, когда пользователю «нравится» или «не нравится» событие, создается фоновое задание:

Пересчитайте значения evantevents.rb:

 def self.perform(event_id)
  ## Step 1: Find any events that that share keywords with Event.find(event_id)
  ## Step 2: calculate_event_relevance(event) for each event from above step
end
  

Итак, вот краткое описание процесса:

  1. Пользователю нравится или не нравится событие
  2. Создается фоновое задание, которое находит события, аналогичные событию на шаге 1
  3. Каждое подобное событие пересчитывается на основе ключевых слов пользователя

Я пытаюсь найти способы оптимизировать свой подход, поскольку он может быстро выйти из-под контроля. Средний пользователь просматривает около 20 событий в минуту. Событие может содержать до 1000 похожих событий. И каждое событие содержит около 100 ключевых слов.

Итак, с моим подходом, за один свайп мне нужно перебирать 1000 событий, а затем перебирать 100 ключевых слов в каждом событии. И это происходит 20 раз в минуту для каждого пользователя.

Как я должен подойти к этому?

Ответ №1:

вам нужно вычислять за пролистывание? не могли бы вы debounce это сделать и пересчитывать для пользователя не чаще одного раза в 5 минут?

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

С 5-минутным дебатированием вы переходите от 6000 (20 * 60 * 5 ) пересчет на одного пользователя до 1 за тот же период — довольно большая экономия.

Я бы также рекомендовал использовать sidekiq, если сможете, с его многопоточной обработкой вы получите огромное увеличение количества одновременных заданий — я большой поклонник.

И как только вы его используете, вы можете попробовать такой драгоценный камень, как: https://github.com/hummingbird-me/sidekiq-debounce

… это обеспечивает тот тип устранения, который я предлагал.

Комментарии:

1. Нет, мне не обязательно вычислять каждый свайп. Итак, при таком подходе я бы вычислял 1000 событий каждые 5 минут для каждого недавно активного пользователя?

2. в этом и заключается идея. 5 минут — это число, взятое из ниоткуда, но идея заключается в том, что эти данные не нужно обновлять 20 раз в секунду, чтобы быть полезными, фактически обновляться каждую секунду, вероятно, чаще, чем полезно. При пятиминутном дебатировании вы переходите от 6000 (20 * 60 * 5 ) пересчет на одного пользователя до 1 за тот же период — довольно большая экономия.

3. Да, это отличный момент. Есть ли какое-либо другое очевидное место, где его можно было бы оптимизировать?

4. Не очевидно. На ум приходит несколько вопросов: имеет ли смысл иметь так много ключевых слов для каждого события? Имеет ли смысл использовать их все для вычисления релевантности? Имеет ли смысл добавлять их все для пользователя или только те, которые наиболее важны для события? Есть ли причина, по которой вы создаете это самостоятельно, вместо того, чтобы использовать elastic, который имеет встроенную актуальность?

5. Хорошо, это все отличные моменты, на которые я должен обратить внимание. Большое спасибо.