rubyライクなcrystalでamethystを使ってAPIサーバを作る

# 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というファイルが作られ，バージョンが固定される．

必要なパッケージは以下の通り．

```Projectfile
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
```

```rb
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に似ていて書きやすいです．

返却値はこんな感じになります．

```json
{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を直接書き換えても動作するようです．

```rb
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内で行っています．

```conf
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](https://github.com/8398a7/api-abilitysheet)上にも挙げているので参考にどうぞ．