Web API用の負荷テストツールで遊んでみる

これはWeb API Advent Calendar 2014の16日目の記事です。

はじめに

Web API Advent Calendar 2014 8日目ではkazuchikaさんよりApigee製のAPI負荷ツールapibの紹介がありました。

この記事ではapibに加えてBoomというGolang製の類似ツールを簡単に紹介し、様々なマイクロWebフレームワークに対して両者を使ってみます。

Boom

rakyll / boom

インストール

Golang環境がセットアップされているものとします。

go get github.com/rakyll/boom

実行

boom -n 回数 -c 並列度 [URL]

その他オプションはboomコマンドで閲覧できます。

apib

apigee / apib

  • C言語
  • Apigeeエンジニアが開発
  • リクエスト回数ではなく計測時間を指定する
  • Warm upの時間を指定できる
  • リモートサーバにデーモンを仕込んだ測定もできる

インストール

brew install apib

実行

apib -d 計測時間 -c 並列度 [URL]

その他オプションはapibコマンドで閲覧できます。

実行結果

複数プログラミング言語のマイクロフレームワークを用いて単純なWebAPIを作成して負荷テストツールを実行してみます。条件は以下です。

  • boomの場合、並列度100で1万リクエスト
  • apibの場合、並列度100で10秒

Node.js - Express

Node.jsのスタンダードなWAFです。

var express = require('express')
var app = express()
var http = require('http');
http.globalAgent.maxSockets = 50;

app.get('/', function (req, res) {
  res.send('Hello')
})

var server = app.listen(3000, function () {
})

f:id:slowquery:20141216221017p:plain

f:id:slowquery:20141216221101p:plain

Node.js - Restify

Expressに似ていますが、Web APIに特化しています。

var restify = require('restify');
http = require('http');
http.globalAgent.maxSockets = 50;

function respond(req, res, next) {
    res.send('hello');
    next();
}

var server = restify.createServer();
server.get('/', respond);

server.listen(3000, function(){});

f:id:slowquery:20141216221128p:plain

f:id:slowquery:20141216221138p:plain

Node.js - Hapi

Walmart Lab製のWAFです。自由度の高いExpressや類似フレームワークに対して、ある程度統制を効かせてコードをクリーンに保とうという意識を感じます。WebAPIのルーティングも可読性が高いです。日本ではあまり知られていないように思いますが、小売系の他npm、Mozillaなどtech系企業のユースケースも目立ちます。

var Hapi = require('hapi');
var http = require('http');
http.globalAgent.maxSockets = 50;

var server = new Hapi.Server();
server.connection({ port: 3000 });

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
        reply('Hello');
    }
});

server.start(function () {});

f:id:slowquery:20141216221156p:plain

f:id:slowquery:20141216221209p:plain

Ruby - Sinatra

言わずと知れたマイクロフレームワークです。フォロワーのWAFが大量に出たのがわかるミニマルな記法です。WEBRickは遅かったためThinに変更しています。

require 'sinatra'

set :server, 'thin'
set :logging, nil

get '/' do
  'Hello'
end

f:id:slowquery:20141216221231p:plain

f:id:slowquery:20141216221244p:plain

Ruby - Espresso

速度が売りのマイクロフレームワークです。こちらもSinatraに合わせてThinに変更しています。期待してなかったのですが検討しています。

require 'e'

class App < E
  map '/'

  def index
    "Hello"
  end
end

App.run :server => :Thin

f:id:slowquery:20141216221257p:plain

f:id:slowquery:20141216221305p:plain

Python - bottle

Bottle: Python Web Framework

恐らくPythonで最もミニマルなWAFで個人的にもよく使っています。WSGIに対応していてアプリケーションサーバFacebook製のTornadoに変更しました。

from bottle import route, run


@route('/', methods=['GET'])
def index():
    return "Hello"

run(server='tornado', host="0.0.0.0", port=3000, debug=False)

f:id:slowquery:20141216221318p:plain

Haskell - Scotty

Sinatraに影響を受けたHaskellのマイクロWebフレームワークです。Haskellの中では最も親しみやすい印象でした。この記法でこの性能はいい感じです。

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

main = scotty 3000 $ do
    get "/" $ do
        text "Hello"

f:id:slowquery:20141216221342p:plain

f:id:slowquery:20141216221350p:plain

Go - Martini

DIの仕組みをもったGoのマイクロフレームワークです。通常の設定だと標準出力にログが出て速度がやや遅くなったのでzishe/app.goを参考にログを消しています。Golangの人気がうかがえる速度です。

package main

import (
    "log"
    "github.com/go-martini/martini"
)

func init(){
    martini.Env = martini.Prod
}

type myClassic struct {
    *martini.Martini
    martini.Router
}

func withoutLogging() *myClassic {
    r := martini.NewRouter()
    m := martini.New()
    m.Use(martini.Recovery())
    m.MapTo(r, (*martini.Routes)(nil))
    m.Action(r.Handle)
    return &myClassic{m, r}
}

func main() {
    m := withoutLogging()
    m.Get("/", func(lg *log.Logger) string {
        return "Hello"
    })
    m.Run()
}

f:id:slowquery:20141216221416p:plain

f:id:slowquery:20141216221424p:plain

Go - beego

MartiniよりリッチなGoのフレームワークです。マイクロフレームワークかと思っていたらフルスタックとのこと。フルスタックかつ高性能と謳っています。

package main

import (
    "github.com/astaxie/beego"
)

type MainController struct {
    beego.Controller
}

func (this *MainController) Get() {
    this.Ctx.WriteString("Hello")
}

func main() {
    beego.Router("/", &MainController{})
    beego.Run()
}

f:id:slowquery:20141216221442p:plain

f:id:slowquery:20141216221450p:plain

注意

この記事はフレームワークの性能を表すものではありません。以下のようにフレームワーク周辺領域での選択やアプリやミドルウェアの構成による影響が大きく、何をもって"フレームワークの性能"と定めるか容易には決められないからです。

  • ディスクやネットワークのIO。その性能もORMやコネクタに左右されます。
  • シリアライズやバリデーションなど入れ替え可能な関連ライブラリ
  • PythonWSGIRubyのRackのようにWebサーバとWebアプリケーションフレームワーク間の統一インタフェースが提供されている場合、Webサーバの選択が性能に大きく影響します。
  • ルーティングを辞書でなく配列に格納してマッチングするWAFの場合、探索の計算量がO(N)となるため登録されているルーティングが多くなると性能が劣化してしまいます。

※サンプルコードはテキストでHelloという文字列を返すだけでWeb APIとしては不完全です。

感想

WebAPIの負荷テストツールを使うことで新たなフレームワークを試すのが楽しくなりました。この記事以外にも様々なフレームワークを試した結果、記法が美しいものから魅力的なレスポンスタイムを持つものまで様々なフレームワークに出会えましたが、その中で共通して感じたのはWeb APIを実装する敷居が低くなったということです。

昨今、サービスがWeb APIを公開するのはもちろんのこと、Web APIが組み込まれたソフトウェアも目立つようになりました。ソフトウェアのWebAPIがこの記事で使ったようなマイクロフレームワークで実装されていることもあります。

このような流れが加速してサービスもソフトウェアもWeb APIを持つのが当たり前になり、誰もがコードから簡単に操作できる自由な世界が訪れるといいですね。

Enjoy!

OSXとHomebrew-caskでHaskellの開発環境を構築

この記事ではHomebrewではなくHomebrew-caskを使ってHaskellの環境を構築します。

インストールには、以下のOSX用のHaskellディストリビューションを使います。コンパイル済みのバイナリとcabalが入っていて早くてお手軽です。

GHC

  1. インストール

     brew cask install ghc
    
  2. GHCのパスを通す

    1. F4を押してlaunchpadを起動します。
    2. ghcと入力してghc-7.8.3を起動します。
    3. 画面左上にあるAppend to ~/.bash_profileボタンをクリックします。

      f:id:slowquery:20140727115203p:plain

      ※ 表示されているスクリプト.bash_profileに追加されます。

      zshの場合は表示されているスクリプト.zshrcなどに自分で追加します。

  3. GHC起動

     ghc     
    

Cabal

Haskellのパッケージ管理ツールcabalは、初めからインストールされているのでアップデートするだけです。

  • cabal自体のアップグレード

      cabal install cabal-install
    
  • パッケージアップデート

      cabal update
    

OSX 10.10 Yosemiteでhomebrewによるgccインストールのエラーを回避する方法

OSX 10.10 YosemiteのDeveloper Preview版で普通にbrew install gccgccをインストールしようとするとエラーになると思います。ここではパッチを当ててgcc4.9.1をインストールしてみます。

前提

  • OSX 10.10 Developer Preview 4
  • gcc 4.9.1

参考

https://github.com/Homebrew/homebrew/issues/29845

方法

  1. homebrewをアップデートしてgccのformulaを最新版にします。

     brew update
    
  2. gccのformulaの編集モードに入ります。

     brew edit gcc
    
  3. 2行目に以下の4行を追加します。

     patch do
       url "https://gcc.gnu.org/bugzilla/attachment.cgi?id=33180"
       sha1 "def0cb036a255175db86f106e2bb9dd66d19b702"
     end
    

     class GCC < Formula
    
       # Patch for OSX 10.10 Developer Preview 4
       # ref. https://github.com/Homebrew/homebrew/issues/29845
       patch do
         url "https://gcc.gnu.org/bugzilla/attachment.cgi?id=33180"
         sha1 "def0cb036a255175db86f106e2bb9dd66d19b702"
       end
    
       def arch
         if Hordware::CPU.type +== : intel
         略
    
  4. インストール

     brew install gcc
    

以上

OSXでssh-copy-idを使ってリモートサーバにパスワードなしでログインする

OSXssh-copy-idを使う方法です。

何ができる?

  1. sshでサーバにログインする際のパスワード入力が不要になる。
  2. そのために必要なssh公開鍵の配布を簡単に行うことができる。

インストール

beautifulcode/ssh-copy-id-for-OSX - GitHubを使います。

  1. ダウンロード

     curl https://raw.githubusercontent.com/beautifulcode/ssh-copy-id-for-OSX/master/ssh-copy-id.sh -o /usr/local/bin/ssh-copy-id
    
  2. 実行権限を付与

     chmod +x /usr/local/bin/ssh-copy-id
    

使い方

  1. 公開鍵を生成します。~/.ssh/id_rsa.pubが存在する場合は不要です。

     ssh-keygen  
    

    以下が生成されます。

     ~/.ssh/id_rsa.pub
    
  2. 公開鍵を配布します。

    通常

     ssh-copy-id [ユーザID]@[リモートサーバ]
     ssh-id-copy root@192.168.100.10 #例
    

    ~/.ssh/id_rsa.pub以外の公開鍵を使う場合

     ssh-copy-id -i 公開鍵のフルパス [ユーザID]@[リモートサーバ]
     ssh-id-copy ~/.ssh/myidentity.pub root@192.168.100.10 #例
    

    ※ ログインパスワードを聞かれるので入力します。

  3. sshでリモートのサーバにログインします。

     ssh [ユーザID]@[リモートサーバのアドレス]
     ssh root@192.168.100.10 #例
    

    ※ ログインパスワードは聞かれません。

npmに自作のCLIモジュールを公開する

npmのアカウントを作成

https://www.npmjs.orgで登録します。

  • ユーザ名
  • パスワード
  • Emailアドレス

npmコマンドとnpmアカウントの紐付け

先ほど作成したユーザ情報を対話的に入力します。

npm adduser

公開準備 : package.json生成

npm init

package.jsonを対話的に生成します。

"private"trueの場合はfalseに修正します。

"private": false,

公開準備 : CLIモジュール用の設定

npm install -gに対応

"preferGlobal": true,

コマンド名と起動コマンドの登録

"bin": {
  "コマンド名": "起動スクリプト名"
},

公開

npm publish

確認

npm info モジュール名

バージョンアップ

package.jsonのversionを新たなバージョンに変更してnpm publishします。