67 lines
		
	
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			67 lines
		
	
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
class Scheduler::FollowRecommendationsScheduler
 | 
						|
  include Sidekiq::Worker
 | 
						|
  include Redisable
 | 
						|
 | 
						|
  sidekiq_options retry: 0
 | 
						|
 | 
						|
  # The maximum number of accounts that can be requested in one page from the
 | 
						|
  # API is 80, and the suggestions API does not allow pagination. This number
 | 
						|
  # leaves some room for accounts being filtered during live access
 | 
						|
  SET_SIZE = 100
 | 
						|
 | 
						|
  def perform
 | 
						|
    # Maintaining a materialized view speeds-up subsequent queries significantly
 | 
						|
    AccountSummary.refresh
 | 
						|
    FollowRecommendation.refresh
 | 
						|
 | 
						|
    fallback_recommendations = FollowRecommendation.order(rank: :desc).limit(SET_SIZE)
 | 
						|
 | 
						|
    I18n.available_locales.map { |locale| locale.to_s.split(/[_-]/).first }.uniq.each do |locale|
 | 
						|
      recommendations = begin
 | 
						|
        if AccountSummary.safe.filtered.localized(locale).exists? # We can skip the work if no accounts with that language exist
 | 
						|
          FollowRecommendation.localized(locale).order(rank: :desc).limit(SET_SIZE).map { |recommendation| [recommendation.account_id, recommendation.rank] }
 | 
						|
        else
 | 
						|
          []
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      # Use language-agnostic results if there are not enough language-specific ones
 | 
						|
      missing = SET_SIZE - recommendations.size
 | 
						|
 | 
						|
      if missing.positive? && fallback_recommendations.size.positive?
 | 
						|
        max_fallback_rank = fallback_recommendations.first.rank || 0
 | 
						|
 | 
						|
        # Language-specific results should be above language-agnostic ones,
 | 
						|
        # otherwise language-agnostic ones will always overshadow them
 | 
						|
        recommendations.map! { |(account_id, rank)| [account_id, rank + max_fallback_rank] }
 | 
						|
 | 
						|
        added = 0
 | 
						|
 | 
						|
        fallback_recommendations.each do |recommendation|
 | 
						|
          next if recommendations.any? { |(account_id, _)| account_id == recommendation.account_id }
 | 
						|
 | 
						|
          recommendations << [recommendation.account_id, recommendation.rank]
 | 
						|
          added += 1
 | 
						|
 | 
						|
          break if added >= missing
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      redis.pipelined do
 | 
						|
        redis.del(key(locale))
 | 
						|
 | 
						|
        recommendations.each do |(account_id, rank)|
 | 
						|
          redis.zadd(key(locale), rank, account_id)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  private
 | 
						|
 | 
						|
  def key(locale)
 | 
						|
    "follow_recommendations:#{locale}"
 | 
						|
  end
 | 
						|
end
 |