+2

Dùng Sidekiq vào tính năng xếp hạng 1 dãy các dữ liệu

Dạo gần đây mình có khởi động lại project cá nhân để luyện tập tay nghề(Hàng dùng view mặc định của Ruby, không dùng framework JS tử tế cho frontend và dùng Bootstrap 5.3 làm giao diện thôi. Cũng tối cổ so với trend TailwindCSS với chỗ React và VueJS). Ý tưởng thì là 1 website chứa lyrics, tua nhạc và có metronome chạy theo đúng tempo bài hát. Cái mà mình lần trước có deploy với AWS AppRunner rồi nhưng chưa đưa video được.

Vấn đề

Trong quá trình làm thì mình có nảy ra thêm việc tích hợp 1 số tính năng bảng xếp hạng âm nhạc, thông tin nghệ sĩ,.... Nhưng với quá trình xếp hạng ấy cũng nảy ra vấn đề làm sao để đánh giá nghệ sĩ từ bài hát và album của họ?

Và một trong những giải pháp ở đây là đánh giá dựa trên lượt view bài hát và lượt view album. Các nghệ sĩ sẽ được đánh giá dựa theo tổng lượt view của tất cả các bài hát và tất cả các album.(Đây là giải pháp của mình. Thực tế có thể sẽ có khác)

Vì vậy trước tiên mình có db như này và đã triển khai ổn thoả:

image.png

OK, đã có lượt view của album và lượt view của bài hát. 2 dữ liệu này sẽ được refresh mỗi khi có 1 người dùng vào trang info của 1 album hay 1 bài hát nào đó.

Tuy nhiên, nếu cứ mỗi lượt view được add vào như vậy, dẫn đến albums_pointssongs_points sẽ thay đổi realtime, dẫn đến mọi người khác nhau có thể sẽ nhìn bản xếp hạng ở những vị trí khác nhau(rất phù hợp với tính năng trending nhưng không phù hợp lắm với xếp hạng). Mình muốn bảng xếp hạng theo lượt view được cố định trong 1 tuần rồi có bảng xếp hạng mới.

Và để giải quyết thì mình quyết định chạy scheduled job để cập nhật hàng tuần. Đó là lý do động vào Sidekiq

Code

Gemfile

Trước tiên, ở Gemfile mình thêm sidekiq như sau

# Gemfile
# ...
# Use Sidekiq
gem 'sidekiq'
gem 'sidekiq-scheduler'

Sau đó chạy bundle update

docker-compose

  redis:
    image: redis
    ports:
      - "6379:6379"

  app:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails assets:precompile && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/rails-lyrics-site
      - bundle:/usr/local/bundle
      - tmp-data:/rails-lyrics-site/tmp
      - public-data:/rails-lyrics-site/public
    ports:
      - "3000:3000"
    depends_on:
      - db
      - redis

  sidekiq:
    build: .
    command: bundle exec sidekiq
    volumes:
      - .:/rails-lyrics-site
    depends_on:
      - db
      - redis

sidekiq muốn hoạt động được phải phụ thuộc vào redis, và cũng cần được tách ra như 1 component riêng chạy song song song với phía container web nên ta thêm định nghĩa container:

  redis:
    image: redis
    ports:
      - "6379:6379"

  sidekiq:
    build: .
    command: bundle exec sidekiq
    volumes:
      - .:/rails-lyrics-site
    depends_on:
      - db
      - redis

Cuối cùng thêm container phụ thuộc ở app.

Setup phía config

Ta cần setup:

  • config/application.rb:
    config.active_job.queue_adapter = :sidekiq
  • Tạo mới config/initializers/sidekiq.rb:
redis_config= { url: 'redis://redis:6379/0' }

Sidekiq.configure_server do |config|
  config.redis = redis_config
end

Sidekiq.configure_client do |config|
  config.redis = redis_config
end
  • Thêm Sidekiq view ở config/routes.rb
# frozen_string_literal: true
require 'sidekiq/web'
require 'sidekiq-scheduler/web'

Rails.application.routes.draw do
  # ....

  mount Sidekiq::Web, at: '/sidekiq'
  match '*unmatched', to: 'application#not_found', via: :all
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
  • Tạo mới config/sidekiq.yml:
:scheduler:
  :schedule:
    update_artist_pop_points:
      cron: '0 30 6 * * 1'
      class: ExampleJob

Bước cuối là start server lên kiểm tra localhost:3000/sidekiq. Khi hiện màn hình giống như này tức là các bạn thành công.

image.png

Tạo 1 job và chạy thử

Để tạo 1 Job trong Rails, chúng ta có command rails g job. Ví dụ, để tạo 1 Job có trên là Blo.....omJob, ta chạy rails g job bloom Ở tình huống này mình sẽ chạy:

rails g job UpdateArtistPopPointsJob

app/jobs/update_artist_pop_points_job.rb, mình thêm code:

# frozen_string_literal: true

class UpdateArtistPopPointsJob < ApplicationJob
  queue_as :default

  def perform(*_args)
    # Do something later
    Artist.find_each do |artist|
      artist.songs_points = 0
      artist.albums_points = 0

      artist.albums.each do |album|
        artist.albums_points += album.views_count
      end

      artist.songs.each do |song|
        artist.songs_points += song.views_count
      end

      artist.save!
    end
  end
end

Chỉnh lại config/sidekiq.yml:

      class: UpdateArtistPopPointsJob

Các bạn có thể thấy là giờ Job của mình đã được lên ở trong Sidekiq và kích hoạt trong 6 ngày nữa. Mình có thể lựa chọn kích hoạt luôn.

Bonus: Authentication cho sidekiq

Rõ ràng trang kia thiên hướng về phía admin. Thế nên để chỉ admin xem được trang này, mình sẽ chỉnh ở routes:

  authenticate :user do
    mount Sidekiq::Web, at: '/sidekiq'
  end

Kết

Bài mình viết tới đây là hết


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.