LoginSignup
18
18

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-08-05

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上にも挙げているので参考にどうぞ.

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18