Heroku 환경에서 Sidekiq, Redis 이용하기

최근에 개인적으로 Heroku Free Tier를 이용하여 App을 배포하는 경우가 생겼는데 Puma + Sidekiq + Redis 환경을 설정하는데 좀 삽질을 한 경험이 있어 정리하고자 한다.

Gem 설정

gem 'rails', '4.2.6'
gem 'puma', '~> 2.15'
gem 'sidekiq', '~> 4.0'

이전에 진행했었던 프로젝트에서는 puma 대신 unicorn을 이용했었는데, Heroku 환경에서는 puma가 default이고 최근 많이들 unicorn에서 puma로 넘어가는 추세인 것 같아 puma를 이용하기로 했다.

프로그램 설정 및 Heroku 설정

# config/puma.rb

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  @sidekiq_pid ||= spawn('bundle exec sidekiq -c 2 -q default -q mailer')
  ActiveRecord::Base.establish_connection
end

puma가 시작될 때 sidekiq을 몇 가지 옵션과 함께 실행도로록 했다. queue는 default, mailer queue를 생성했다. 이제 heroku에서 web instance를 실행하기 위해 Procfile을 작성할 차례이다.

예전에 unicorn 환경에서 배포를 할 때는 아래와 같이 web과 redis, worker를 별도로 돌렸었다.

# Procfile with unicorn + nginx + ubuntu 14.04

web: rails s -b 0.0.0.0
redis: redis-server
worker: bundle exec sidekiq -C config/sidekiq.yml

지금은 Heroku에 배포할 것이기 때문에 redis-server는 Heroku redistogo add-on 으로 대체하고, sidekiq은 puma 시작 시 같이 실행하도록 할 것이기 때문에 Procfile은 좀 더 간단하게 유지할 수 있다.

# Procfile with puma + heroku

web: bundle exec puma -C config/puma.rb

이제 위에서 말한 redistogo를 설치하고 환경설정 값을 지정할 차례다. 터미널에서 아래의 명령을 실행하자. Heroku Toolbelt는 이미 설치되어 있다고 가정한다.

$ heroku addons:create redistogo
$ heroku config:set REDIS_PROVIDER=REDISTOGO_URL

Heroku에 sidekiq + redis를 통해 ActiveJob을 돌릴 수 있는 준비는 되었고, ActiveJob Adapter를 sidekiq으로 지정하기 위해 ActiveJob Queue Adapter를 sidekiq으로 지정한다.

# config/application.rb

class Application < Rails::Application

  config.active_job.queue_adapter = :sidekiq

end

Rails 코딩

환영 메일을 보내기 위한 Mailer 생성

$ rails generator mailer UserMailer
# app/mailer/user_mailer.rb

class UserMailer < ApplicationMailer

  def welcome_email(user)
    @user = user
    mail to: @user.email, subject: t('email.welcome.subject') unless @user.email.empty?
  end

end

환영메일 전송을 백그라운드에서 처리하기 위해 ActiveJob 생성

$ rails generator job welcome_email --queue mailer
# app/jobs/welcome_email_job.rb

class WelcomeEmailJob < ActiveJob::Base
  queue_as :mailer

  def perform(user_id)
    user = User.find_by(id: user_id)
    UserMailer.welcome_email(user).deliver_now if user
  end
end

이제 최종적으로 회원가입을 통해 사용자가 생성되었을 경우 환영메일을 전송

# app/models/user.rb

class User < ActiveRecord::Base

  after_create :process_after_create!

  private

    def process_after_create!
      self.access_token = generate_access_token
      if save!
        # 환영메일 전송
        WelcomeEmailJob.perform_later(self.id)
      end
    end

    def generate_access_token
      loop do
        token = "#{self.id}:#{Devise.friendly_token}"
        break token unless User.where(access_token: token).first
      end
    end

end

이메일을 비동기로 전송할 수 있는 방법은 위의 방법 외에도 더 좋은 방법들이 있을 수 있다. 개인적인 코딩 취향에 따라 작성하면 된다.