Ruby on Rails Advent Calendar 13日目です
注意
-
FFIを初めて使ってみたネタレベルなので本番でやってはいけません。
threadの処理がかなりまずい気がします - helixやruruは使っていません。使ってみたいです。
- .trb形式でのinline rustも使ってないです。
モチベーション
今年の9月から新しい会社に転職し、 railsを使い始めました。
その前はjava書いていたので、嬉しさ反面、並列処理まわりで少し不満もあります。
rubyを書き出してGiant VM Lockという言葉を初めて知ったのですが、rubyのthreadって実際はIO系の処理しか並列で動かせないので、なんとも...
たとえばcontrollerでなにか処理をしたあと、httpレスポンスを先に返して、バックグラウンド処理でメールやslackに処理を飛ばす処理は簡単には書けません。
やるとしたらdelayed jobやsidekiq等を使うことになると思います。
現状それが現実解だと思いますが、sidekiqなどを使うなら、
- Gemfileに追加
- bundle install
- capistrano等デプロイ機構でsidekiqプロセスを動かくように設定
- sidekiqプロセスの監視機構を用意
といった作業が必要です。cron等で定時バッチでやるコードを書くという手段はもっとだるいです。
それぐらいやれよというのは、そのとおりなのですが、
ようは軽い気持ちでバックグラウンド処理やりたいですよね!
ということでcontrollerで処理を飛ばしたあと、(軽い気持ちで??)rustコードを書いてslack通知をやってみたいとおもいます。
rust側
railsのルートディレクトリ直下にに rust/
というディレクトリを切ってその中で
$ cargo new afterslack
しました。これで rust/afterslack/ というディレクトリができます。
afetslackというのがrust側のプロジェクト名です。
(ネーミングおかしいのは見逃してください...)
rust側でslackに通知してみます。
slack-hook というクレート(rustのライブラリの呼び方)を使いました。
確認しやすいように3秒まつコードにしました。
extern crate slack_hook;
use slack_hook::{Slack, PayloadBuilder};
use std::time::Duration;
use std::thread;
#[no_mangle]
pub extern fn send_msg_ffi() {
thread::spawn(move || {
send_msg();
});
}
fn send_msg() {
println!("rust-slack-start");
let webhook_url = "<自分のslackのwebhook url>";
let slack = Slack::new(webhook_url).unwrap();
let p = PayloadBuilder::new()
.text("rustrustrust")
.channel("<#ではじまるチャンネル名 or @ではじまる宛先ユーザー名>")
.username("rust-slack-notify")
.build()
.unwrap();
let res = slack.send(&p);
match res {
Ok(()) => println!("ok"),
Err(e) => println!("ERR: {:?}", e)
}
// あえて3秒まってみる
thread::sleep(Duration::from_millis(3000));
println!("rust-slack-end");
}
Cargo.tomlはこんなかんじです。
[package]
name = "afterslack"
version = "0.1.0"
authors = ["なまえ <めあど>"]
[lib]
name = "afterslackffi"
crate-type = ["dylib"]
[dependencies]
slack-hook = "0.2"
でbuildします。
$ cargo build --release
成果物はrailsのルートディレクトリからみると
rust/afterslack/target/release/libafterslackffi.dylib
というところにできあがります。
rails側
rails new
はもうやっている前提なので
controller作りましょう
class PostsController < ActionController::Base
# POST /posts
def create
render text: ':create' # ここDEPRECATION WARNINGなの見逃してください
AfterSlack.send_msg_ffi
end
end
AfterSlack.send_msg_ffiってというのがafterslackを呼び出しているruby側コードです。これも自分で書きました。次の通りです。
module AfterSlack
extend FFI::Library
ffi_lib Rails.root.join 'rust/afterslack/target/release/libafterslackffi.dylib'
attach_function :send_msg_ffi, [], :void, blocking: false
end
リクエストなげてみる
まずはthread使わないでやると
...
#[no_mangle]
pub extern fn send_msg_ffi() {
// ためしにさっきのコードをスレッド使わずにやる
// thread::spawn(move || {
send_msg();
// });
}
...
結果
curl -XPOST localhost:3000/posts
をやってみるとrailsサーバーのログにこんな結果がでました。
Completed 200 OK in 3347ms (Views: 6.2ms | ActiveRecord: 0.0ms)
Threadの中でやってみる
threadを使うようにして、再度、cargo build --release
してcurlを叩いてみます。
結果
Completed 200 OK in 25ms (Views: 5.4ms | ActiveRecord: 0.0ms)
うぇーい!! 3347 ms -> 25 ms
slackにもちゃんととんでいました。
#再度注意
すいません。これうぇーいとよろこんでいますが
joinしていないthread処理をFFI経由で実行した場合、後処理はどうするんだ?
たぶん、いけないものが残る....気が..
まとめ
FFIやrustのthread処理については詳しく把握する必要がありますが、
ノウハウがたまれば、sidekiq等を用意するよりも気軽にバックグラウンド処理ができます。
本当に必要なビジネスロジックだけ実行したら、さっさとユーザーにレスポンスを返して、他の処理は後でやりたいです!
明日は
あしたはjoker1007さんです。個人的な面識などはありませんが、常日頃名前はお見かけします。
投稿タイトルも気になります。期待しています。