Sidekiqのベストプラクティス
概要
Sidekiqのベストプラクティスに沿って、メッセージ機能を実装していきます。
0. Sidekiqとは
Sidekiqとはbackgroundでタスクを処理してくれるライブラリです。日付指定でセットして実行したりもできます、便利そうです。
ベストプラクティスに沿ってWorker用のテーブルを用意します。
# messages class CreateMessages < ActiveRecord::Migration[5.1] def change create_table :messages do |t| t.integer "sender_id" t.integer "recipient_id" t.string "body" t.timestamps end end end
# message_tasks class CreateMessageTasks < ActiveRecord::Migration[5.1] def change create_table :message_tasks do |t| t.integer "sender_id" t.integer "recipient_id" t.string "body" t.timestamps end end end
今回はミニマムのデモのため構造は全く同じですが、実際は送信するユーザが複数だったりプッシュ通知をしたりするかもしれません。
1. ジョブのパラメータを小さく・シンプルに
上記のMessageTasks
のテーブルを用意することで、引数をtask_id
のみにすることができます。
class MessageWorker include Sidekiq::Worker def perform(task_id) end end
また呼び出し側では以下のようになります。
task = MessageTask.create(sender_id: sender_id, recipient_id: recipient_id, body: body) MessageWorker.perform_async(task.id)
2. 冪等性とトランザクション
perform
内でエラーが発生した場合、自動リトライ機能が働きます。デフォルトでは25回のリトライ後(おおよそ21日後)そのジョブは削除されマニュアルで対応しないといけません。
それを防ぐためにもMessageTasks
とトランザクションを使用することで、より安全にデータを扱うことができます。
class MessageWorker include Sidekiq::Worker def perform(task_id) task = MessageTask.find(task_id) if task.nil? return end ActiveRecord::Base.transaction do Message.create(sender_id: task.sender_id, recipient_id: task.recipient_id, body: task.body) task.destroy end end end
3. Concurrencyの設定
Concurrencyを設定することによりsidekiqの処理を並列化することが可能です。デフォルト値は25スレッドで推奨値は50未満。100以上設定した場合、安定性の問題があるとの。
config/database.yml
を変更することで変えることができます。この値は可変ではないので、用途に応じて変更する必要があるようです。
production: adapter: mysql2 database: foo_production pool: 25
まとめ
Sidekiqを使うことで同期処理が必要がないタスクは非同期で処理させることができます。同期処理させる必要ないものは非同期にしてレスポンス速度を改善できそうですね。
リンク
ESLintのメソッドチェインのindentの設定について
概要
ESLint
のメソッドチェイン(chain)のindentの設定に問題があり3 -> 4のアップグレードができなかったのですが、なんとか苦戦してなんとかアップグレードした話です。
もっといい方法知っている方いたら教えて欲しいです、ホントに。
問題のコード
例としてarrow functionのショートハンドなどは使用していません。実際のコードはもっと大きいものを想定してください。
さて。
元々のESLint
の設定はこうでした。
rules: indent: - error - 2
Array編
最初のchainのfunctionに複数行にまたがるArrayを渡す際、以下のコードはLintエラーになります。
// 1 (Lintエラー) Promise.all([ 1, 2, 3 ]) .then(value => { console.log(value); });
解決方法としては、
// 2 Promise.all([ 1, 2, 3 ]) .then(value => { console.log(value); }); // 3 Promise .all([ 1, 2, 3 ]) .then(value => { console.log(value); }); // 4 (Lintエラー) Promise.all([ 1, 2, 3 ]) .then(value => { console.log(value); });
2は気持ち悪すぎるので不採用として(Lintエラーにして欲しいレベル)、4でLintエラーにするのであれば、もはや1を採用して欲しいです。 3はアリと考えていましたが、以下の例でとてもいただけないので不採用としました。
// 5 (気持ち悪い) _ .chain([ 1, 2, 3 }) .map(n => { return n * 2; }) .value(); // 6 (短いときはこう書きたい) Promise.all([1, 2, 3]) .then(value => { console.log(value); });
Function編
Functionのほうがもっと厄介です。
// 7 (Lintエラー) new Promise(resolve => { // do something setTimeout(resolve); }) .then(value => { console.log(value); }); // 8 (Lintエラー) [1, 2, 3].map(n => { return n * 2; }) .filter(n => { return n % 2; });
解決方法はArrayの例と同じく、
// 9 new Promise(resolve => { // do something setTimeout(resolve); }) .then(value => { console.log(value); }); // 10 [1, 2, 3].map(n => { return n * 2; }) .filter(n => { return n % 2; });
一つの案としては変数を定義することですが、Functionの例で毎度変数を作らないといけなくなるため不採用にしました。
解決方法
IndentにMemberExpressionというオプションがあり、chain時のindentを定義できます。
上記の例を実現したいとき、chainのindentが0だったり2だったりするため、off
にすることで回避することができました。
rules: indent: - error - 2 - MemberExpression: off
まとめ
ESLintのバージョンをアップデートすることで、多くの機能が追加されているのでもっと厳しくチェックしていただけるようになりました。助かります、ありがとうございます。
がしかし、果たしてこれは良いコードなのか…という疑問も残ります。
例えば、
// 11 const obj = {}; const array = _.chain([ 1, 2, 3, ]).map(n => { return n * 2; }) .filter(n => { return n % 2; }); obj.a = 1;
この例の場合、arrayのchainがどこで終わりかわかりづらいため、その後に続く行が読みづらい可能性が高いです。
// 12 const obj = {}; const array = _ .chain([ 1, 2, 3 ]) .map(n => { return n * 2; }) .filter(n => { return n % 2; }); obj.a = 1;
そういう意味ではこう書いたほうが読みやすい可能性も…
リーダブルコードって難しいですね。
リンク
Perforce + Vim
概要
PerforceをVimで仲良くやっていくためのセットアップを考え中なのですが、特に良いライブラリもなく、はたまた需要もそこまで大きくなく… いったん作業ができるようvim-perforceとvimdiffのセットアップをしました。
vim-perforce
ドキュメントによるとP4info
, P4edit
, P4revert
と P4movetocl
だけサポートしてるそうです。
とVim
上で手動でチェックアウトする必要があります。WebStormを使っていると自動チェックアウトができるそうです。
p4 delete
が一番の曲者なのでサポートして欲しいところではあります。
vimdiff
vimdiffのセットアップは一行足すだけでp4 diff
がvimに置き換わります。
$ export P4DIFF=vimdiff
これはとても見やすく便利です。
まとめ
vim-perforceを改良したいところではありますが、いったんこの2つさえあれば作業は捗るかと思います。
もう少し効率よくするために、ローカル環境ではgitを用意して、gitの情報を元にPerforceのコマンドを自動化しています(謎)。 Perforceはあまりブランチを作成したり複数のことを同時にしたりすることが難しいので、gitでできることはgitに移行したいところです。
リンク
Perforceのセットアップ for Mac
はじめに
PerforceはSubversionのようなバージョン管理システムでbinaryの管理に向いているそうです。今回はp4v
というツールとp4
というコマンドラインツールを導入したのでそのセットアップ方法について書きます。
p4v
のセットアップ
設定はこちらのリンク に沿ってやるだけですが、管理者からServerのアドレスとuser名をいただき入力するだけです。
以上で設定は完了です。
p4v
の使い方
ログイン後、始めに差分を取得するためルートディレクトリで右クリックを押しGet Latest Revision
を取得します。
全てのファイルはRead Onlyになっており編集権限がありません。もし編集したい場合はファイルを右クリックでCheck out
する必要があります。チェックアウトをすると編集が可能になり、その情報は他のユーザにシェアがされます。編集後、右クリックを押しsubmitすることで他のユーザに変更がシェアされます。
もし差分を見たい場合は、右クリックでDiff Against Have Revision
を押すとp4vdiff
が起動し差分を見ることが可能です。
p4
のセットアップ
p4v
だとマウス操作が多いため、やはりCLIでどうにかしたいところです。そのためp4
をインストールします。リンクはこちら。
次にパスなどの情報をセットします。
私は.zshrc
をGithub上で管理しているため、secretな情報を隠すために別ファイルを用意しました。
# ~/.secret.sh export P4PATH=<server-url> export P4USER=<user-name> export P4CLIENT=<workspace-name> p4 login
# ~/.zshrc if [ -f $HOME/.secret.sh ]; then source $HOME/.secret.sh fi
上記の.secret.sh
を.zshrc
で読み込み、terminal起動時にp4
にログインをしています。これで準備は完了です。
編集したいときは、p4 edit
を使用することでチェックアウトが可能になります。また他にもいくつかコマンドがあるので試してください。
まとめ
私はgit
ばかり触っているゆとりですが、少しずつ慣れていこうかなと思っています。
次回はp4
+ vim
あたりについて書こうと思います。
リンク
Ruby on Railsのセットアップ
はじめに
Rubyデビューしたので初心に戻ってその勉強記録を記して行きたいと思います。
手順は
- rbenv (バージョン管理システム)
- nokogiriのインストール
- Railsのインストール
- アプリの起動
です。
rbenvのインストール
rbenvは最も有名なバージョン管理ツールらしいので、流行りに乗ってインストールしたいと思います。
…のつもりが、長いことmacを使っていると自ずとrbenvがインストールされていることもあるようで、gem installが通らなかったりしたのでuninstallから始めます。
手順はこちらです。
uninstall? · Issue #148 · rbenv/rbenv · GitHub
$ rm -rf ~/.rbenv $ brew uninstall rbenv
と、.zshrc
からrbenv
なる記述を削除しました。
それでは気を取り直してインストールします。
$ brew install rbenv
後は.zshrc
に追記します。
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc $ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.zshrc
Rubyのセットアップ
次にrubyをインストールします。
$ rbenv install 2.4.1
最新版をインストールしたい方はrbenv install -l
で最新版をチェックしてください。
rbenv rehash rbenv global 2.4.1
以上で完了です。
nokogiriのインストール
ここで少しハマったのがmacではnokogiriのインストールをmacのインストレーションに沿ってやらないといけないようなので、別途インストールします。
$ gem update --system $ xcode-select --install $ gem install nokogiri
これはmac OS Xというところに記載されています。
Railsのインストール
$ gem install rails
コマンドはこちらだけですね、とても簡単。
アプリの起動
適当なディレクトリに行き、以下のコマンドを叩きます。 チュートリアルに沿って、
$ rails new hello_app
$ cd hello_app
$ rails server
これでhttp://localhost:3000/にアクセスすると以下の画面が表示されます。
まとめ
とても簡単にRailsサーバが立ち上がりました。
Yay! I'm on Rails!
はい。せっかくなので何かサーバ建てたいですね。
リンク
classの継承についての考察
概要
ES6の継承が遅すぎると話題なので検証・最適化をしました。
どれくらい遅いのか
こちらのベンチマークによると、少しずつ改善は見られるものの、2.3-17倍程度遅い様子です。
実際に検証しました。
- Node v6.10.1
- benchmark.js v2.1.4
継承
// classes function ParentES5(value) { this._value = value; } function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); class ParentES6 { constructor(value) { this._value = value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } } // functions const funcs = { 'es5': function() { return new ES5(1); }, 'es6': function() { return new ES6(1); } };
**** benchmark.js **** [1] "es5" 0.000029ms [1.00] [2] "es6" 0.000045ms [1.56]
単純にclassを使うだけでも1.5倍遅いようです。
super classメソッドの呼び出し
function ParentES5(value) { this._value = value; } ParentES5.prototype.get = function() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return ParentES5.prototype.get.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return super.get() * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es5" 0.000021ms [1.00] [2] "es6" 0.000078ms [3.79]
super classメソッドの呼び出しは非常に遅いので避けたいところです。
考察
super classメソッドの呼び出し
function ParentES5(value) { this._value = value; } ParentES5.prototype.get = function() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return ParentES5.prototype.get.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return ParentES6.prototype.get.call(this) * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es6" 0.000020ms [1.00] [2] "es5" 0.000020ms [1.02]
super.get()
の代わりにsuper classメソッドをcall
することでes5と同等のパフォーマンスが得られました。これは使えそうです。
こちらのパターンも同じような結果が得られます。
function ParentES5(value) { this._value = value; } function getES5() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return getES5.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } } function getES6() { return this._value; }; class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return getES6.call(this) * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es5" 0.000023ms [1.00] [2] "es6" 0.000023ms [1.00]
こちらもなかなか使いどころがありそうです。
継承
ParentES6.prototype.constructor
へのアクセスは禁止されているため、classをextendsしsuperを呼び出す必要があります。
class ParentES6 { constructor(value) { this._value = value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } } // エラー class ES6 { constructor(value) { ParentES6.prototype.constructor.call(this, value); } } // TypeError: Class constructor ParentES6 cannot be invoked without 'new'
今のところたどり着いた最速の継承はこちらです。
function ParentES5(value) { this._value = value; } function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } function ES6(value) { this._value = value; } ES6.prototype = ParentES6.prototype; const es5 = new ES5(1); const es6 = new ES6(1); console.log(es6.get()); // 1 console.log(es6 instanceof ParentES6); // true // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
[1] "es6" 0.000020ms [1.00] [2] "es5" 0.000026ms [1.33]
es5の継承よりも高速な継承が可能となりました。もう少し検証が必要ですが、instanceofがtrueとみなされるのでこちらは意外と使えるかもしれません。 こちらの案はthamminさんがベースを提供してくれました。
まとめ
superクラスの呼び出しを避ける考察でした。ns,μsレベルのチューニングなので普通にsuperを呼んでいただければと思います。
JavaScriptっておもしろい。
リンク
Powerlineの導入
概要
Powerlineとはshell・vimにちょっとした情報をカッコよく表示してくれるツールです。何よりカッコいい。導入手順はこちらです。
Powerline for Shell
まずはshellからカッコよくしていきます。はじめにpowerline-shell
をクローンします。
$ cd ~
$ git clone https://github.com/milkbikis/powerline-shell
このディレクトリは削除できないのでお好みなところにクローンしてきてください。
次にインストールします。設定を変えたい場合はconfig.py
を変更することでお好みの設定に変更できます。設定変更後は再度install.sh
を実行する必要があります。
$ ./install.sh
これでpowerline-shell.pyが生成されるので、Symlinkを貼ります。
$ ln -s ~/powerline-shell/powerline-shell.py ~/powerline-shell.py
私はzshを使用しているので、zshrc
に設定を以下のように追加します。bash, fishの設定はこちらを参照してください。
# power-shell function powerline_precmd() { PS1="$(~/powerline-shell.py --cwd-max-depth 1 $? --shell zsh 2> /dev/null)" } function install_powerline_precmd() { for s in "${precmd_functions[@]}"; do if [ "$s" = "powerline_precmd" ]; then return fi done precmd_functions+=(powerline_precmd) } if [ "$TERM" != "linux" ]; then install_powerline_precmd fi
次にフォントのインストールをします。こちらはREADMEのスクリプトをそのまま実行するだけです。
# clone git clone https://github.com/powerline/fonts.git # install cd fonts ./install.sh # clean-up a bit cd .. rm -rf fonts
こんな感じになりました、カッコいいだけでなく意外に便利です。
Powerline for Vim
次にvimのpowerlineの設定をしていきます。はじめにお使いのvimの環境を確認してください。こちらから条件を確認できます。
私はvim-plugを使っているので.vimrc
に以下のように追加します。
Plug 'powerline/powerline', {'rtp': 'powerline/bindings/vim/'}
フォントもインストール済みなので、以上で設定は終了です。 別のプラグインを使っている方はこちらから確認してみてください。
こんな感じになります。
まとめ
デメリットはshellが少し重くなりますが、カッコいいだけでなく意外にも便利だったので導入してもいいかなと思いました。
リンク
- GitHub - powerline/powerline: Powerline is a statusline plugin for vim, and provides statuslines and prompts for several other applications, including zsh, bash, tmux, IPython, Awesome and Qtile.
- GitHub - banga/powerline-shell: A beautiful and useful prompt for your shell
- GitHub - powerline/fonts: Patched fonts for Powerline users.