Melchior

バンクーバーで働くエンジニアの備忘録

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, P4revertP4movetocl だけサポートしてるそうです。

f:id:suguru03:20171107092146p:plain

f:id:suguru03:20171107092629p:plain

f:id:suguru03:20171107092523p:plain

Vim上で手動でチェックアウトする必要があります。WebStormを使っていると自動チェックアウトができるそうです。 p4 deleteが一番の曲者なのでサポートして欲しいところではあります。

vimdiff

vimdiffのセットアップは一行足すだけでp4 diffvimに置き換わります。

$ export P4DIFF=vimdiff

f:id:suguru03:20171107102935p:plain

これはとても見やすく便利です。

まとめ

vim-perforceを改良したいところではありますが、いったんこの2つさえあれば作業は捗るかと思います。

もう少し効率よくするために、ローカル環境ではgitを用意して、gitの情報を元にPerforceのコマンドを自動化しています(謎)。 Perforceはあまりブランチを作成したり複数のことを同時にしたりすることが難しいので、gitでできることはgitに移行したいところです。

リンク

Perforceのセットアップ for Mac

はじめに

PerforceSubversionのようなバージョン管理システムでbinaryの管理に向いているそうです。今回はp4vというツールとp4というコマンドラインツールを導入したのでそのセットアップ方法について書きます。

p4vのセットアップ

こちらのリンクからmac用のリンクをダウンロードします。

設定はこちらのリンク に沿ってやるだけですが、管理者からServerのアドレスとuser名をいただき入力するだけです。

https://www.perforce.com/perforce/doc.current/manuals/p4v-gs/images/p4v-gs001.png

以上で設定は完了です。

p4vの使い方

ログイン後、始めに差分を取得するためルートディレクトリで右クリックを押しGet Latest Revisionを取得します。 全てのファイルはRead Onlyになっており編集権限がありません。もし編集したい場合はファイルを右クリックでCheck outする必要があります。チェックアウトをすると編集が可能になり、その情報は他のユーザにシェアがされます。編集後、右クリックを押しsubmitすることで他のユーザに変更がシェアされます。 もし差分を見たい場合は、右クリックでDiff Against Have Revisionを押すとp4vdiffが起動し差分を見ることが可能です。

p4のセットアップ

p4vだとマウス操作が多いため、やはりCLIでどうにかしたいところです。そのためp4をインストールします。リンクはこちら

次にパスなどの情報をセットします。 私は.zshrcGithub上で管理しているため、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のインストール

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/にアクセスすると以下の画面が表示されます。

f:id:suguru03:20170908081122p:plain

まとめ

とても簡単にRailsサーバが立ち上がりました。

Yay! I'm on Rails!

はい。せっかくなので何かサーバ建てたいですね。

リンク

classの継承についての考察

概要

ES6の継承が遅すぎると話題なので検証・最適化をしました。

どれくらい遅いのか

f:id:suguru03:20170428145927p:plain

こちらベンチマークによると、少しずつ改善は見られるものの、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

こんな感じになりました、カッコいいだけでなく意外に便利です。

f:id:suguru03:20170403130056p:plain

Powerline for Vim

次にvimのpowerlineの設定をしていきます。はじめにお使いのvimの環境を確認してください。こちらから条件を確認できます。

私はvim-plugを使っているので.vimrcに以下のように追加します。

Plug 'powerline/powerline', {'rtp': 'powerline/bindings/vim/'}

フォントもインストール済みなので、以上で設定は終了です。 別のプラグインを使っている方はこちらから確認してみてください。

こんな感じになります。

f:id:suguru03:20170403125852p:plain

まとめ

デメリットはshellが少し重くなりますが、カッコいいだけでなく意外にも便利だったので導入してもいいかなと思いました。

リンク