【Ruby on Rails】sidekiqの導入手順（ローカル、Heroku、AWS EC2、Docker、Capistrano）

## redisを準備する

sidekiqを使う為にredisが必要です。

### Macでインストールする場合

```
brew install redis
```

**redis起動**

```
brew services start redis
```

### docker-composeを使う場合

```yaml:docker-compose.yml
  redis:
    image: redis:latest
    volumes:
      - redis:/data
    ports:
      - '6379:6379'
```

## アプリケーションにsidekiqを導入する

```ruby:Gemfile
gem 'sidekiq'
```

```ruby:config/application.rb
config.active_job.queue_adapter = :sidekiq
```

### sidekiqを起動する
```
sidekiq -q default -q mailers
```

もしくは、以下の設定ファイルを作成して、簡単に起動できます。

```yml:config/sidekiq.yml
:concurrency: 25
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:queues:
  - default
  - mailers
  - active_storage_analysis
  - active_storage_purge
:daemon: true
```

```
sidekiq -C config/sidekiq.yml
```

## サンプルアプリケーション

アプリケーションには `Message` というモデルがあり、そのモデルが `content` と　`sidekiq_executed_time` カラムを持つとします。

例えば、`SampleJob`を作ります。

```
rails g job Sample
```

生成したJobは全ての `Message` の `sidekiq_executed_time` を現時点に更新します。

```ruby:app/jobs/sample_job.rb
class SampleJob < ApplicationJob
  queue_as :default

  def perform(*args)
    sleep(1)
    Message.find_each do |message|
      message.update(sidekiq_executed_time: DateTime.current)
    end
  end
end
```

コントローラーでは、新規 Message が作成された時に、Jobを非同期実行します。

```ruby:app/controllers/messages_controller.rb
def create
  @message = Message.new(message_params)
  if @message.save
    redirect_to messages_path, notice: 'Message was successfully created.'
    SampleJob.perform_later
##### 省略 #####
```

![スクリーンショット 0002-08-15 0.14.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/dd8d5b9d-b844-3e84-559b-4b848a1642cb.png)

実際に新規 `Message` を作成したら、最初は `sidekiq_executed_time` がなかったが、数秒後ページをリロードすると値があって、Jobがちゃんと実行されたことが分かります。

![スクリーンショット 0002-08-15 0.14.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/3546a421-9999-25ba-12dc-810f76c1dec3.png)

### sidekiqジョブの管理画面

```ruby:config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'

  # 認証が必要の場合
  Sidekiq::Web.use(Rack::Auth::Basic) do |user, password|
    [user, password] == [ENV['SIDEKIQ_USERNAME'], ENV['SIDEKIQ_PASSWORD']]
  end
##### 省略 #####
```

そして、`localhost:3000/sidekiq`にアクセスすると管理画面が現れるはずです。

![スクリーンショット 0002-08-15 0.20.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35605/892f5a97-0af1-335a-e16e-da01be9db9e6.png)


### Redis DBのデータを全て削除する

Railsコンソールで以下のコマンドを実行すると、Redis DBのデータを全て削除することができます。

```ruby:
Sidekiq.redis { |r| puts r.flushall }
```


## Herokuで使う場合

### Heroku redisの追加

```
heroku addons:create heroku-redis:hobby-dev
```

### Heroku Redis との接続設定
```ruby:config/initializes/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379')}
end
```

### Procfile の作成

```Procfile
web: bin/rails server -p $PORT -e $RAILS_ENV
worker: bundle exec sidekiq -c 3 -q default -q mailers
```

### デプロイの後、worker dyno を作る

```zsh
heroku ps:scale web=1 worker=1
```

## AWS EC2で使う場合

### AWS ElastiCache との接続設定
```ruby:config/initializes/sidekiq.rb
Sidekiq.configure_server do |config|
  if Rails.env.production?
    redis_conn = proc {
      Redis.new(host: ENV['AWS_REDIS_ENDPOINT'], port: 6379, db: 2)
    }
    config.redis = ConnectionPool.new(size: 27, &redis_conn)
  else
    config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
  end
end

Sidekiq.configure_client do |config|
  if Rails.env.production?
    redis_conn = proc {
      Redis.new(host: ENV['AWS_REDIS_ENDPOINT'], port: 6379, db: 2)
    }
    config.redis = ConnectionPool.new(size: 27, &redis_conn)
  else
    config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
  end
end
```

### capistrano-sidekiqの設定

```ruby:Gemfile
group: :development do
  gem 'capistrano-sidekiq', require: false
end
```

```ruby:Capfile
require 'capistrano/sidekiq'
```

```ruby:config/deploy.rb
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a.concat(%w(rake gem bundle ruby rails puma pumactl sidekiq sidekiqctl))
set :init_system, :systemd
```

### sidekiqをサーバーにインストールする

```zsh
cap production sidekiq:install
```

そして、サーバーにアクセスして、`sidekiq-production.service`を修正する必要があります。

```:/home/USER/.config/systemd/user/sidekiq-production.service
ExecStart=/home/deploy/.rbenv/shims/bundle exec sidekiq -e production -C config/sidekiq.yml
```

上の`/home/deploy/.rbenv/shims/bundle`が```which bundle``` で確認できたものです。

修正が終わってから、以下のコマンドを実行する

```zsh
loginctl enable-linger deploy && systemctl --user daemon-reload && systemctl --user stop sidekiq-production.service && systemctl --user daemon-reload && systemctl --user start sidekiq-production.service && systemctl --user enable sidekiq-production.service
```

### sidekiqを再起動する

```zsh
cap production sidekiq:restart
```

### capistrano-sidekiqを使いたくない場合

`capistrano-sidekiq`の設定が面倒だったら、別のやり方があります。
**同サーバーに複数のアプリケーションを運用する場合は、このやり方をお勧めします。**

#### .serviceファイルを作成する

サーバーでこのようなファイルを作成します。
`/etc/systemd/system/sidekiq_<application>_<stage>.service`

例えば、アプリ名は`myapp`、`production`環境であれば、以下のようになります：


```systemd:/etc/systemd/system/sidekiq_myapp_production.service
[Unit]
Description=sidekiq for myapp (production)
After=syslog.target network.target

[Service]
Type=simple
Environment=RAILS_ENV=production
WorkingDirectory=/deploy/apps/myapp/current
# When using anyenv: "/home/deploy/.anyenv/envs/rbenv/shims/bundle"
ExecStart=/home/deploy/.rbenv/shims/bundle exec sidekiq -e production -C config/sidekiq.yml
ExecReload=/bin/kill -TSTP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID

RestartSec=1
Restart=on-failure

SyslogIdentifier=sidekiq

[Install]
WantedBy=default.target
```

#### .serviceファイルを有効にする

これを実行します。

```zsh
sudo systemctl enable sidekiq_myapp_production.service
```

#### capistranoのタスクを作成する

`capistrano`でデプロイした後で、sidekiqを再起動するためです。

```ruby:lib/capistrano/tasks/sidekiq.rake
namespace :sidekiq do
  desc 'Restart sidekiq before finishing deployment'
  task :restart do
    on roles(:app) do
      execute "sudo systemctl restart sidekiq_#{fetch(:application)}_#{fetch(:stage)}.service"
    end
  end
end

before 'deploy:finishing', 'sidekiq:restart'
```