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

  • 45
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

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

ベンチマーク結果

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

スクリーンショット 2015-12-14 18.44.24.png

Web Framework Benchmarksと異なる結果になった

Web Framework Benchmarksと異なる結果になりました。Flaskが遅いのはコード側に問題がありそうなので検証していきます。

スクリーンショット 2015-12-15 8.10.35.png

ベンチマーク条件

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

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

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

俺のFlaskがこんなに遅いはずがない

前回のFlaskの記事ではDBアクセスとテンプレート継承なしで平均2ms で応答していました。テンプレート継承はキャッシュしてコストゼロになるはずだし、DBアクセスに7msも必要なわけがありません。多く見積もってもDBアクセスに1-2msになるはずではないでしょうか?何か理由があるはずです。

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

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

スクリーンショット 2015-12-14 17.25.19.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円でも圧縮して爪に火をともしたいので積極的に利用していこうと思います。お金大事。

まとめ

俺俺ベンチとWeb Framework Benchmarksの比較

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

スクリーンショット 2015-12-14 16.26.29.png

上には上がいるWebフレームワークの世界

go言語のrevelFlask より2.41 倍早い

スクリーンショット 2015-12-14 15.33.41.png

ベンチマークコード

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

Rails

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

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

Flask

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 %}

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

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

{% block content %}
{{ book.id }}
{{ book.name }}
{{ book.description }}
{% endblock %}
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.rbdevelopment.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つだと思います。

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