follow_recommendations_scheduler.rb 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. # frozen_string_literal: true
  2. class Scheduler::FollowRecommendationsScheduler
  3. include Sidekiq::Worker
  4. include Redisable
  5. sidekiq_options retry: 0
  6. # The maximum number of accounts that can be requested in one page from the
  7. # API is 80, and the suggestions API does not allow pagination. This number
  8. # leaves some room for accounts being filtered during live access
  9. SET_SIZE = 100
  10. def perform
  11. # Maintaining a materialized view speeds-up subsequent queries significantly
  12. AccountSummary.refresh
  13. FollowRecommendation.refresh
  14. fallback_recommendations = FollowRecommendation.order(rank: :desc).limit(SET_SIZE)
  15. Trends.available_locales.each do |locale|
  16. recommendations = begin
  17. if AccountSummary.safe.filtered.localized(locale).exists? # We can skip the work if no accounts with that language exist
  18. FollowRecommendation.localized(locale).order(rank: :desc).limit(SET_SIZE).map { |recommendation| [recommendation.account_id, recommendation.rank] }
  19. else
  20. []
  21. end
  22. end
  23. # Use language-agnostic results if there are not enough language-specific ones
  24. missing = SET_SIZE - recommendations.size
  25. if missing.positive? && fallback_recommendations.size.positive?
  26. max_fallback_rank = fallback_recommendations.first.rank || 0
  27. # Language-specific results should be above language-agnostic ones,
  28. # otherwise language-agnostic ones will always overshadow them
  29. recommendations.map! { |(account_id, rank)| [account_id, rank + max_fallback_rank] }
  30. added = 0
  31. fallback_recommendations.each do |recommendation|
  32. next if recommendations.any? { |(account_id, _)| account_id == recommendation.account_id }
  33. recommendations << [recommendation.account_id, recommendation.rank]
  34. added += 1
  35. break if added >= missing
  36. end
  37. end
  38. redis.multi do |multi|
  39. multi.del(key(locale))
  40. recommendations.each do |(account_id, rank)|
  41. multi.zadd(key(locale), rank, account_id)
  42. end
  43. end
  44. end
  45. end
  46. private
  47. def key(locale)
  48. "follow_recommendations:#{locale}"
  49. end
  50. end