技術ブログ

(技術系中心)基本自分用備忘録なので、あくまで参考程度でお願いします。

Sidekiq アンチパターンのまとめ

Sidekiq アンチパターン

業務でsidekiqを利用するケースがあったのですが、sidekiqのアンチパターンについて知らなかったのでまとめました。

1. 命名規則について

ワーカーの名前は後から変更するのは後から変更するのは大変です。

ワーカーの命名規則をつけるときは動作を表すようにしましょう。

BAD

1. WebhookWorker 

2. ImportWorker

この命名規則はどうでしょうか?

1はwebhookで何をするのかの情報が不十分です。

2は何をimportするのかの情報が不十分です。

GOOD

1. WebhookNotificationWorker 

2. CrewImportWorker

2. 非同期処理内の例外握り潰し

def perform(arg)
  # something to do
rescue => e
  logger.error "#{e.class}: #{e.message}"
end

非同期の中で例外握りつぶしをするのはNGです。

perform 実行中にいかなる例外が発生しようとも rescue 節でキャッチしてしまうので、 perform を呼び出した worker はジョブが成功したものとみなしてしまいます。

つまり、失敗したことに気づかない状態になってしまいます。

def perform(arg)
  # something to do
rescue => e
  raise '処理に失敗しました><'
end

resucueの中で発生した例外を RuntimeError で上書きしてしまうと。

performの中でどのような例外が発生したか、わからなくなってしまうのでこちらもNGです。

def perform(arg)
  # something to do
rescue => e
  1 / Random.rand(2) # 50% chance to raise ZeroDivisionError
  raise e
end

resucueの中で例外が発生し得る処理を記述するのもNGです。

rescueの中では例外が発生すると、rescueの意味がなくなってしまいます。

引数について

performの引数を実装途中で変更するのは非常に危険です。

class WebhookWorker
  include Sidekiq::Worker

  def perform(url, channel, message)
    payload = {channel: channel, message: message}
    RestClient.post(url, payload.to_json)
  end
end

もし途中でWebhook パラメータを追加/変更したい場合に単純にワーカーが受け取れる引数を増やしたらどうなるでしょうか?

class WebhookWorker
  include Sidekiq::Worker

  def perform(url, channel, username, message)
    payload = {channel: channel, message: message, username: username}
    RestClient.post(url, payload.to_json)
  end
end

Sidekiq ではジョブを予約した時の変数との対応は実行時に考慮してもらえないので思わぬトラブルを招きます。

WebhookWorker.perform_async('https://hooks.example.com/T00000000/XXXXX', 'general', 'hello, webhooks!')

このように引数の順序がメチャクチャになってしまいます

url = 'https://hooks.example.com/T00000000/XXXXX'
channel = 'general'
username = 'hello, webhooks!
message = nil

参考

Sidekiq アンチパターン: 序 - SmartHR Tech Blog

ActiveRecord::Base.connected_toの利用方法

ActiveRecord::Base.connected_toの利用方法

今の現場でActiveRecord::Base.connected_toを利用した実装をしており、初めてみた書き方だったのでメモを残す。

データベースのルールを明示的に指定することが出来る

ActiveRecord::Base.connected_to(role: :writing) do
  Dog.create! # 書き込みコネクションで書き込み成功
end

ActiveRecord::Base.connected_to(role: :reading) do
  Dog.create! # 読み込みコネクションなのでエラー発生
end

ActiveRecord::Base.connected_to(role: :unknown_role) do
  # 不明コネクションでエラー発生
end

参考

ActiveRecord::ConnectionHandling

connected_to (ActiveRecord::ConnectionHandling) - APIdock

Rails.application.config_forで設定ファイル全体を読み込む方法

Rails::Application.config_forを使うと、設定ファイル全体を読み込むことができます。

例)

Rails.application.config_for(:for)

config_for(:for)とする事によって、config/foo.ymlに設定された内容を、そのまま定義することができます。

config/foo.ymlの内容

default: &default
  api: "https://stg.tsite.jp/hogehoge"

出力

pry> Rails.application.config_for(:foo)
=> {:api=>"https://stg.tsite.jp/hogehoge"}

config_for(:for)とする事によって、config/foo.ymlに設定された内容を、そのまま定義することができます'

参考:

Ruby on Rails: Rails標準の`config_for`を使ってカスタム設定を管理する⚙ - Madogiwa Blog

Rails.application の config_for を使って、ユーザ定義の設定値を定義する - Qiita

Rubyの条件分岐判定方法

Rubyの条件分岐判定方法

Rubyの条件判定は癖があるのでメモ残す。

Rubyの条件分岐は他のプログラミング言語同様に条件式が成立する時に返される「true」は真として扱われます。 また成立しなかった時に返される「false」は偽として扱われます。

falseとnilは偽
falseとnil以外は全て真

実は「false」と「nil」だけが偽となり、それ以外は全て「真」となります。「true」は「false」でも「nil」でもありませんので結果的に真となっています。

falseとnil以外は全て真なので、falseとnil以外の値が入れば全てtrueになると覚えておくと良いかもです。

# 全ての値とtrueそのものは真
1
2
3
[1, 2, 3]
'apple'
''
true

# falseとnilは偽
false
nil

参考:【Ruby】真偽値と論理演算子あれこれ - Qiita

sidekiqの使い方備忘録

sidekiqの使い方備忘録

仕事でsidekiq利用する機会があったので使い方の備忘録を残す(自分用)

sidekiqとは?

github.com

Simple, efficient background processing for Ruby. Sidekiq uses threads to handle many jobs at the same time in the same process. It does not require Rails but will integrate tightly with Rails to make background processing dead simple.

バックグラウンドプロセス効率化することができる。 Railsを利用して利用するケースが多い、マルチスレッド処理ジョブを同一スレッドで処理することが可能。

始め方

sidekiq管理画面

バックグランドで処理されているジョブを管理画面から確認することが可能です。

Sidekiqには管理画面があり、routes.rbに以下のコードを記述すると実装できます。

   require 'sidekiq/web'
   mount Sidekiq::Web, at: "/sidekiq"

http://localhost:3000/sidekiqにアクセスすると、管理画面に飛ぶことができます。

下記のようなエラーになる場合はRedisの設定ができていない可能性が高いです、sidekiqをローカルで利用する方法を参照してredisをインストールして起動すればエラーが消えるはずです。

sidekiqをローカルで利用する方法

redisインストール(Mac OS

brew install redis

redis起動

brew services start redis

redis終了

brew services stop redis

sidekiqの利用方法

config以下に書いた設定コマンドを、以下のコマンドを実行します

bundle exec sidekiq -C config/sidekiq.yml

参考サイト

【Ruby on Rails】sidekiqの導入手順(ローカル、Heroku、AWS EC2、Docker、Capistrano) - Qiita

macOS に Redis を Homebrew でインストールして brew services で起動する - Qiita

【Rails】更新系メソッドまとめ

Rails】更新系メソッドまとめ

すでにテーブルに保存されているデータを、新しい情報に更新するメソッドに関してまとめてみました。 自分用備忘録です。

updateメソッド

user = User.create(name: 'Taro', age: 15)

# 成功
user.update(name: 'Taro')
# => true

# 失敗
user.update(name: nil)
# => false

更新に成功したら、true返却 失敗したら、false返却

update!

user = User.create(name: 'Taro', age: 15)

# 成功
user.update!(name: 'Taro')
# => true

# 失敗
user.update!(name: nil)
# ActiveRecord::RecordInvalid (Validation failed: Name can't be blank)

updateとupdate!メソッドの違いは、結果の返し方が異なります。 updateの場合、保存の成否をtrueかfalseで返します。それに対してupdate!は保存に失敗したときに例外を返します。

update_attributeメソッド

@item.update_attribute(:num, 100)

update_attributeの実行時は、バリデーションのチェックがされないため、特別な理由がある場合以外は使用しないことが好ましい。

update_attributesメソッド

update_attributesはupdateのエイリアスメソッド(別名)なので、動作は全く同じ。

update_all

Item.update_all( num: 1)

update_allは、レコードの全ての値を一括で変更できるメソッド。 このような記述で、Itemsテーブルの全てのレコードのnumカラムの値を1に変更することができる。

ポスグレの初期化で詰まった話

DBをUTF-8で初期化しようとして下記のコマンドを入力

initdb /usr/local/var/postgres -E utf8

すると下記のようなエラーに遭遇

The files belonging to this database system will be owned by user "inouehiroki".
This user must also own the server process.

The database cluster will be initialized with locale "ja_JP.UTF-8".
initdb: could not find suitable text search configuration for locale "ja_JP.UTF-8"
The default text search configuration will be set to "simple".

Data page checksums are disabled.

creating directory /usr/local/var/postgres ... initdb: error: could not create directory "/usr/local/var": Permission denied

perimissionエラーぽい。 この記事を参考にsudo権限で755(読み出せて、書き込めて、実行可能)権限を付与するようにしてみた。

すると下記のエラーに遭遇

✗ sudo mkdir /usr/local/var/postgres

mkdir: /usr/local/var: No such file or directory

そもそも/usr/local/varが存在しないということなので、/usr/local/varを作成した後に権限付与する。

sudo mkdir /usr/local/var
sudo mkdir /usr/local/var/postgres
sudo chmod 775 /usr/local/var/postgres
sudo chown ユーザー名 /usr/local/var/postgres

この後にもう一度initdb /usr/local/var/postgres -E utf8を実施すると上手くいった

➜  local initdb /usr/local/var/postgres -E utf8
The files belonging to this database system will be owned by user "inouehiroki".
This user must also own the server process.

The database cluster will be initialized with locale "ja_JP.UTF-8".
initdb: could not find suitable text search configuration for locale "ja_JP.UTF-8"
The default text search configuration will be set to "simple".

Data page checksums are disabled.

fixing permissions on existing directory /usr/local/var/postgres ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Asia/Tokyo
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    pg_ctl -D /usr/local/var/postgres -l logfile start