【Ruby vs Python】RailsとFlaskをベンチマーク比較

Pythonの軽量WebフレームワークFlask導入にあたり、DBアクセスとテンプレート継承有りでRailsと比較しました。外部の[Web Framework Benchmarks](https://www.techempower.com/benchmarks/)と異なった**Railsの方が早い**という結果がでたので原因を特定して解消する記事です。

# ベンチマーク結果
個人PJでの利用を想定しているので、用途に特化したベンチマークを書いて比較しました。結果**Rails** は**Flask** より**1.493倍高速に動作** することが判明しました。Railsすんごいはやい。

![スクリーンショット 2015-12-14 18.44.24.png](https://qiita-image-store.s3.amazonaws.com/0/65312/61a1de50-cebc-c1c1-ed2e-f395e7f39f8e.png)

## Web Framework Benchmarksと異なる結果になった
[Web Framework Benchmarks](https://www.techempower.com/benchmarks/)と異なる結果になりました。Flaskが遅いのはコード側に問題がありそうなので検証していきます。

![スクリーンショット 2015-12-15 8.10.35.png](https://qiita-image-store.s3.amazonaws.com/0/65312/7685d417-b43d-ea7a-2e0b-9d86649b03d9.png)



## ベンチマーク条件
用途に特化したベンチマーク試験となっています。

 - DBアクセスして1レコード取得し結果をテンプレート表示
 - DBアクセスは必ずO/Rマッパーから行う
 - テンプレートはレイアウトファイル(Pythonではextends)を利用してテンプレート継承を必ず1度行う
 - ベンチマークはApache Benchで取得。並列度10で1000回アクセスしたときの平均応答速度を5回取得
 - DBはmysql
 - セッション機能は利用しないので無効
 - 環境はすべてローカルで構築

```shell-session:サーバ起動コマンド
# どちらも1スレッドのみデバッグコマンドで起動した

# Rails
rails server

# Flask
python manage.py runserver

```


## プログラム毎のバージョン
|分類  |言語ver  |Webフレームワークver  |
|---|---|---|
|Flask  |Python 3.5.0   |0.10.1  |
|Rails  |ruby 2.0.0p451   |Rails 4.2.5  |

## Apache Benchでベンチマークを取得
Apacheで標準に付いているWEBサーバの性能を計測するためのコマンドで**ab** コマンド（Apache Benchの略）で実行できます。
`ab.exe -n <Total発行リクエスト数> -c <同時接続数> <URL>`

```shell-session:abコマンド
#並列度10で1000回アクセス
ab -n 1000 -c 10 http://127.0.0.1:3000/
```

 - 引用: [Apache Benchでサクッと性能テスト](http://qiita.com/flexfirm/items/ac5a2f53cfa933a37192)

# 俺のFlaskがこんなに遅いはずがない
[前回のFlaskの記事](http://qiita.com/haminiku/items/c5b7fc0bd37a9ee1fafe)ではDBアクセスとテンプレート継承なしで**平均2ms** で応答していました。テンプレート継承はキャッシュしてコストゼロになるはずだし、DBアクセスに7msも必要なわけがありません。多く見積もってもDBアクセスに1-2msになるはずではないでしょうか？何か理由があるはずです。

その後printデバッグして調査した結果sql alchemy(O/R Mapper) でDBのconnection poolingが無効になっていることを発見して修正しました。

```py3:db.py
# 修正前
engine = create_engine(db_path, encoding='utf-8')
# 修正後
engine = create_engine(db_path, encoding='utf-8', pool_size=5)

# ※ベンチではThreadLocalStorageにほうりこんでコネクションを再利用するコード書いた 
```


# ベンチマーク再取得結果
FlaskのDB connection poolingを有効にしてベンチマークを再取得した結果、Flask が平均リクエスト速度で最速という結果を得られました。使い慣れていないWebフレームワークを検証するとき外部のベンチマーク結果と手元の結果を比較検証することは有意義であると感じます。

![スクリーンショット 2015-12-14 17.10.02.png](https://qiita-image-store.s3.amazonaws.com/0/65312/005377c3-861d-0766-c9f3-289127620fab.png)

![スクリーンショット 2015-12-14 17.25.19.png](https://qiita-image-store.s3.amazonaws.com/0/65312/1817ecb6-4781-714e-2591-b02754229c74.png)


|分類  |言語ver  |Webフレームワークver  |O/Rマッパー|
|---|---|---|---|
|Django  |Python 2.7.5   |Django==1.6.4  |Django独自実装|
|Flask  |Python 3.5.0   |0.10.1  |SQL Alchemy|
|Rails  |ruby 2.0.0p451   |Rails 4.2.5  |Active Record|


## サラリーマンエンジニアとしてFlaskに思うこと
お仕事でPythonを書く自分にとって、Djangoと比較して実行速度ベースで2倍しか高速化しないFlaskにあまり魅力を感じません。チームで働いている人であればあるほど、既存のテスト済み資産を捨て、みんなで学習コストを払ってまで移るメリットを感じられないのではないでしょうか？ただ個人PJではサーバ費用を1円でも圧縮して爪に火をともしたいので積極的に利用していこうと思います。お金大事。


# まとめ
- TechEmpower社の[Web Framework Benchmarks](https://www.techempower.com/benchmarks/)有能
- Railsは十分早い
- Flask,Pyramidユーザは[SQL AlchemyのDB Connection Pool](http://docs.sqlalchemy.org/en/latest/core/pooling.html) を有効にしているか確認しよう
- Django使ってるユーザは[DB Connection Pool](https://docs.djangoproject.com/en/1.9/ref/settings/#conn-max-age) を有効にしているか確認しよう
- Railsで出来ることはFlaskでも出来る。逆もまた真なり


## 俺俺ベンチとWeb Framework Benchmarksの比較
比較グラフを生成してみました。Djangoがこんなに早いはずがないという点については、気が向いたら調査しようと思います。


![スクリーンショット 2015-12-14 16.26.29.png](https://qiita-image-store.s3.amazonaws.com/0/65312/dd122413-b453-05a2-eb20-ebaf0bcb4413.png)

# 上には上がいるWebフレームワークの世界
go言語の**revel** は**Flask** より**2.41** 倍早い

![スクリーンショット 2015-12-14 15.33.41.png](https://qiita-image-store.s3.amazonaws.com/0/65312/ccdb1072-c66b-e743-ff97-23004bb7063f.png)


- [「最速」フルスタックWebフレームワーク「revel」の紹介](http://qiita.com/2KB/items/7a743033d8d1a87f1b72)


# ベンチマークコード
各チュートリアルやるのは結構楽しい。Railsの`rails generate` コマンドでなんでも生成しちゃうのは、内部がどうなっているか隠蔽されてしまい理解しにくかったです。

## Rails

```rb:book_controller.rb
class BookController < ApplicationController
  def index
    @msg = 'てすとめっせーじ';
    # select
    book = Book.where("id = 1").all
    @book = book[0];
  end
end

```

```rb:index.html.erb
<p>
    <%= @msg %><br/>
    <%= @book.id %><br />
    <%= @book.name %><br />
    <%= @book.description %><br />
</p>
```


## Flask

```html:index.html
{% extends "master.html" %}

{% block title %}index{% endblock %}

{% block body %}
<h1>this is index page</h1><br />
{{ book.id }}
{{ book.title }}
{{ book.publish }}
{% endblock %}

```

```py3:root.py
# -*- coding: utf-8 -*-
from flask import render_template, Blueprint

# 第一引数の名称が、テンプレのurl_for内で呼び出すときの名称と紐づく
from utils.db import get_db_session
from module.book import Book

app = Blueprint("index",
                __name__,
                url_prefix='/<user_url_slug>')


# テンプレート内で呼び出すときは url_for('index.index')
@app.route("/")
def index():
    book = get_db_session().query(Book).filter(Book.id=1).all()
    return render_template('root/index.html', book=book[0])
```


## django
```html:index.html
{% extends "master.html" %}

{% block content %}
{{ book.id }}
{{ book.name }}
{{ book.description }}
{% endblock %}
```

```py2:views.py
class TestView(TemplateView):
    """
    test
    """
    template_name = 'index.html'
    def get(self, request, *args, **kwargs):
        book = Book.objects.filter(id=1).get()
        response = self.render_to_response({
            'book': book,
        })
        return response
```


# 初めてのRails
Railsを初めて利用しました至れり尽くせり感が素晴らしかったです。Railsのエコシステムがここまで居心地いいとは思いませんでした。特に本番と開発用の2系統出力される設定内容が、よく考えられていて枯れた設計になっていて安心しました。

- 設定全然迷わない。最初から適切な`production.rb` と `development.rb` が生成された。
- コマンド1つでViewとそれに伴うテストコードを生成された
- アセットパイプラインでcssもjsも自動ビルドしてくれる
- 軽く触った感じだとActiveRecordは今となっては普通のO/Rマッパーだった
- Rakeコマンドが良い感じに難しい部分を1タスクにまとめていて便利
- ぐぐったら日本語ドキュメントが大量に出てくる
- 以上の要因が重なりたった3時間でイチから構築してベンチマークが取得できた。
- 高い学習コスト、それだけの価値


# RailsのDB connection pooling事情
`config/database.yml` にpool値を設定できるようです。ただし0にするとエラーが出て動かなくなる。RailsではDB connection poolingは必ず有効にしなければならないみたいです。

## Railsでconnection poolingが無効にできないのは問題あり
DB connection pooling を有効にすると間違いなく高速化しますが副作用があります。DBサーバの負荷です。AWSで一番高いDBインスタンスを借りても5万コネクションを張るとCPUが100%に張り付いて負荷で飛びます。DBコネクションはサーバのスレッド毎に生成されます、1サーバあたり8CPUとすると8-48スレッドのHTTP子プロセスを立ち上げるのが一般的なのでpool=1に設定しても、タイムアウト含めて1000台サーバあるとDBが負荷で飛ぶことになります。

## 例の会社の中の人が解決してた
例のレシピサイトの中の人が解決してました。ぐぐったら大抵の問題が解決するのは、利用者が多くコミュニティが強いWebフレームワークの大きなメリットの1つだと思います。

 - [Rails(ActiveRecord)でデータベースへのコネクションプーリングをさせなくする](http://d.hatena.ne.jp/hxmasaki/20100812/1281588958)

おまえの次のセリフは『シナトラ』・・・( ･ㅂ･)و