crystal
- rubyライクに書ける
- 静的型付け
- golangの有力対抗馬(だと思ってる)
- コンパイルしてバイナリ実行
作るAPIはDBから引っ張ってきた情報を適当にJSONで返すものです.
今回はユーザの人数を返すだけのAPIを作ります.
golang
今回crystalで作った理由として,
普段Rubyを使ってる身としては,GolangはシンプルといえどもCっぽい感じが…
最初はgolangで書いていたけど,crystalで書きなおしてみると書きやすい!と感じたのでこちらで書いてみた.
crystalはかなりrubyっぽいので学習コストがとても低い.
あと,golangと違ってコア数を増やすことはできないらしい(将来対応する予定だとか).
amethyst
フルスタックフレームワーク,railsっぽいもの.
今回はAPIサーバなのでこれを使う理由はなかったが,ルーティング周りをさくっと書きたかったので使うことに.
大体使わない場合と比べて処理速度は2/3程度になる.
パッケージのインストール
さくっと作っていきます.
$ mkdir api_sample
$ cd api_sample
$ touch Projectfile
Projectfileがrubyでいうgemfile
インストールを開始すると.deps.lockというファイルが作られ,バージョンが固定される.
必要なパッケージは以下の通り.
deps do
github "will/crystal-pg"
github "Codcore/amethyst"
github "spalger/crystal-mime"
end
crystal deps
でインストールされ,コード中のrequireによって読み込みを行います.
続いて,API部分を作っていきます.
今回は簡単な形で作っていますが,
コントローラなどを分けて,Railsっぽくも書けるようなので詳細は公式リポジトリのexampleを参照して下さい.
$ mkdir src
$ vim src/app.cr
require "amethyst"
require "pg"
DB = PG.connect("postgres://user@host:port/db_name")
class UsersController < Base::Controller
actions :index
def index
result = DB.exec({Int32}, "SELECT count(id) FROM users")
hash = Hash(String, Int32).new
hash["users"] = result.rows[0].to_a.first
json hash.to_json
end
end
class ApiApp < Base::App
routes.draw do
get "/api/v1/users", "users#index"
register UsersController
end
end
app = ApiApp.new
app.serve
たったこれだけです.
users#indexの最後の行でformatをjsonにしてhashをto_jsonにして返すだけ.
非常にrubyに似ていて書きやすいです.
返却値はこんな感じになります.
{users:100}
あとは本番用にビルドして使うだけですね.
$ crystal build --release src/app.cr
$ ./app
本番用(ubuntu 14.04)で起きた問題
crystal-pgがエラーを吐いてしまう不具合がありました.
どうも過去にissueも立っているけど直っていないっぽい?
前のissueを読んでいると,postgresを9.4系にすれば動く模様.
ということでpostgresを9.4にした所,正常に動作するようになりました.
追記
このgemはpg_configのパラメタからlinkを決めているようなのですが,
色々調査をしていると,postgres9.3系と9.4系では以下の様な違いが有りました.
# postgres 9.3.9
INCLUDEDIR = /usr/include/postgresql
LIBDIR = /usr/lib
# postgres 9.4.4
INCLUDEDIR = /usr/include/postgresql
LIBDIR = /usr/lib/x86_64-linux-gnu
LIBDIRの場所が違っており,これを修正するだけでも動くのでは?と試してみたところ,
正常に動作するのでcrystal-pgを直接書き換えても動作するようです.
module PG
@[Link(ldflags: "-lpq -I`pg_config --includedir` -L /usr/lib/x86_64-linux-gnu")]
lib LibPQ
ファイルとしては,root/.deps/will-crystal-pg/src/pg/libpq.cr
にあります.
nginxの設定
簡単にこんな感じにしています.
crystalの中でAccess-Control-Allow-Originを設定する方法がわからなかったので,
今回はnginx内で行っています.
upstream crystal {
server localhost:8080;
keepalive 300;
}
server {
location /api {
access_log off;
error_log /dev/null crit;
add_header Access-Control-Allow-Origin *;
proxy_pass http://crystal;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
golangとのベンチマーク
golangではginを使って同じものを作り,ベンチマークをしてみました.
ab -k -c 10 -n 10000 http://127.0.0.1/api/v1/users
lang | request/sec |
---|---|
crystal(amethyst) | 2272.13 |
golang(gin) | 1278.75 |
結構crystalが圧勝気味です.
ただし,全コア数を使うとgolangのほうが上回るかと思います.
上記はnginxを通していますが,通さず直にやってcrystalが2700req/sec程度の速度でした.
ちなみにrailsでやった場合は,200-300req/sec行かない(worker: 2-8)の速度です.
お手軽にこれだけ高速化できるなら,publicなAPIはcrystalで書き直すのも良いかもしれません.
一応リポジトリのサンプルとして少し形が違いますが,github上にも挙げているので参考にどうぞ.