bash script で並列度を制限しながら並列処理する

masm11 です。

bash script で、時間がかかる仕事を複数の CPU core を有効活用して処理したくなったので、今回はその方法を考えてみました。

要件は以下の通りです。

  • bash script で書く。
  • 時間のかかる仕事やあまりかからない仕事がたくさんある。
  • 各仕事は CPU core を1つしか使わない。
  • CPU core は複数あるので、複数の仕事を並列に処理したい。
  • 各仕事は bash script 内で関数で定義されている。

man bash してたどり着いた結論は、jobs %?ce を使う、ということです。 マニュアルには ce としか書いてありませんが、何でも構いません。 例えば jobs %?foo と実行すると、jobs の実行結果のうち foo を含むものだけを出力してくれます。 foo を含むものがあれば $? が 0 に、なければ 1 になります。

この機能を使うことにしました。 コードは以下のようになります。

#!/bin/sh

job_body () {
    id=$1
    # やりたい仕事。ここでは仮に引数に渡された時間だけ sleep することにする。
    echo "$(date +%H:%M:%S) $id enter."
    sleep $2
    echo "$(date +%H:%M:%S) $id leave."
}

process_pool () {
    JOBIDS=([0]=JOBID_a [1]=JOBID_b)
    while :; do
        for id in ${JOBIDS[*]}; do
            if jobs %?${id} > /dev/null 2>&1; then
                # このジョブはまだ実行中。
                :
            else
                if read args; then
                    # 実行が終了している。新たなジョブを投入。
                    eval "job_body $id $args &"
                else
                    # 新たなジョブもない。
                    if jobs %?JOBID_ > /dev/null 2>&1; then
                        # 何らかのジョブがまだ実行中。
                        :
                    else
                        # 新たなジョブもないし、実行中のジョブもない。終了。
                        return
                    fi
                fi
            fi
        done
        sleep 1
    done
}

# process_pool にジョブを渡す
cat <<EOT | process_pool
10
20
30
40
EOT

ジョブは2つまで並列実行可能として、JOBID_a, JOBID_b とします。 最初にその ID を配列で定義しておいて、あとは以下を繰り返しています。

  1. ジョブがまだ実行中なら何もしない。
  2. ジョブが終了しているなら、新たなジョブがあれば起動する。
  3. 新たなジョブがなければ、実行中のジョブがあるか確認して、あれば待機。
  4. それもなければ、何もないので終了。

関数名が process_pool になっていますが、process pool でもなんでもなく、 単に終了したら新たに起動しているだけです。 id が不要であれば、jobs の個数を数えるだけでも良いかもしれません。

結果は以下の通り。

% bash ./test.sh
22:53:29 JOBID_a enter.
22:53:29 JOBID_b enter.
22:53:39 JOBID_a leave.
22:53:40 JOBID_a enter.
22:53:49 JOBID_b leave.
22:53:50 JOBID_b enter.
22:54:10 JOBID_a leave.
22:54:30 JOBID_b leave.
% 

JOBID_aJOBID_b が並列に動作しているのがわかります。うまくいってますね。

上記のコードで細かいことを言えば、個人的には eval を使っているのが気に入っていません。このままでは たまたま $argsJOBID_a が含まれていた場合に誤動作してしまいます。 ここは今後の課題です。

ボツ案として、実は以下のようなものも考えました。

worker () {
    id=$1
    while read a; do
        job_body $id $a
    done
}

process_pool () {
    worker a &
    worker b &
    wait
}

しかし、read が一行だけ read() してくれる可能性が低いので、諦めました。 こちらの方が process pool っぽいのですが。

エンジニア募集しています!

永田です。

今日はCTOとして記事を書きます。

エンジニア募集しています!(切実)

募集内容は 、

中途採用|株式会社インゲージ 採用情報

に載せています。

  • Webアプリケーションエンジニア(サーバーサイドエンジニア)
  • フロントエンジニア
  • インフラエンジニア
  • iOSエンジニア

です。

大阪にある会社で、Ruby (Rails) で仕事ができて、自社サービスで、というのはなかなかないと思います。

どんなサービスを開発しているかは、

最新のメール管理システム|問合せ管理の新基準 Re:lation(リレーション)

を見ていただければわかりますが、今のところサイトがあまりいけてないので(!)、わからないことがあれば説明します。

ただ弊社としては、今、求める人材としては上記ページにも書いていますが、まだまだ小さい会社なので、自己管理ができて、自分から行動してくれる人となります。

もし興味がありましたら、Twitter (@kizashi1122) にメンションください。

Sidekiq を使ったメール送信時に uninitialized constant Mail::* が発生した

永田です。

弊社では Rails をつかって Relationというサービスを構築しています。 サービス内部では処理の効率化のために、非同期処理を様々な機能で使っています。 Rails 4から使えるようになった ActiveJob を Sidekiq で使っています。キューの管理は Redis を使っています。

ActiveJob ではメールの送信もおこなっています。

ActiveJob でメールの送信をおこなうようになってから1年以上は経ちますが最近、奇妙なエラーが発生しました。うちで発生したのは以下の2パターンでした。

uninitialized constant Mail::SMTPConnection

uninitialized constant Mail::AddressList::Address

これには悩まされましたが、mail gem の issue にてすでに議論されていました。

NameError: uninitialized constant Mail::Parsers::ContentTypeParser · Issue #912 · mikel/mail · GitHub

mail gem がスレッドセーフではなかったことが原因とのこと。対応策としては、この issue にも、Sidekiq の Wiki にも載っていますが、

Problems and Troubleshooting · mperham/sidekiq Wiki · GitHub

initializer 内のファイルに

Mail.eager_autoload!

とすることになります。

github に登録したリリースノートを集める

永田です。弊社では、リリース毎に github にタグを打って、リリースノートをつけています。

年末の振り返り時に、リリースノートを一覧で見たくなったのですが、github 上では見れないため、API を使って取得することにしました。

gistd36acf57c330bcd11b9f1bf508d26617

これで、カレントディレクトリに release-v0.0.0 というファイル名で、テキストで(中身はマークダウン形式)出力されます。

あとは、ファイルをぐるぐる回して、必要な情報をとればいいということになります。

弊社では以下のようにフォーマットを決めているので、

### Release Date
- 2017/12/XX
 
### New Feature
- blah blah blah
  
### Improvement
- blah blah blah
  
### Bugfix
- blah blah blah

### Other
- blah blah blah
  

gist77ee798b9bd5650ebfa904ab6d58bc78

こんな感じでパースすると、

    release-v.0.0.0    {
        Bugfix           [
            [0] "blah ...",
            [1] "...",
            [2] "..."
        ],
        Improvement      [
            [0] "blah"
        ],
        'New Feature'    [
            [0] "blah "
        ],
        Other            [
            [0] "blah"
        ],
        'Release Date'   [
            [0] "2017/12/XX"
        ]
    },

という感じでハッシュにデータをいれることができるので、取り出して、CSVにするなり何なりとすればよいということになります。

あけましておめでとうございます

インゲージの永田(id:kizashi1122)です。

Twitter でははるか昔にこんな風につぶやいていましたが、ようやく記事を載せようかなと思います。 コーポレートページやサービスページはまだアレなところはありますが、チームメンバーともども、技術的な知見などを載せていくことができればと思います。

今年もよろしくお願い致します。