Ruby on RailsでMySQLに画像を格納する
訳あって、Ruby on RailsでMySQLに画像を格納し使う実験をした。
migrationでの指定
画像を格納するカラムは migration では binary を指定します。ただし、そのままでは 64Kbyteまでしか格納出来ない blob型になってしまいます。これを mediumblob型にするには、 migrationファイルで :limit => (16*1024*1024 - 1) を指定します。
def self.up create_table :table_name do |t| t.binary :column_name, :limit => (16*1024*1024 - 1) t.timestamps end end
ネットを検索すると、mediumblob指定の為に execute でMySQLのSQL文を書くような記事が見つかりますが、そんな面倒はいりません。
max_allowed_packet を変更
大きめな画像を扱うには MySQLサーバー (mysqld)の max_allowed_packet を大きくする必要があります。私の使った環境では max_allowed_packetの初期値は 1Mbyteでした。画像はSQL文の中で1byte を16進数2文字のデータで送受信しているので、扱う画像の2倍以上のbyte数を指定します。
ちなみに、MySQL4.1のマニュアル に「メモリは必要な場合にのみ割り当てられるため、この変数を増やしても安全です。」と書いてあるので大きくしても問題は無いようです。
性能は?
画像をMySQLに格納した場合と、apacheが同じ画像ファイルを配信する場合の性能を比較しました。環境は少し古めです ^^);
- サーバー さくらVPS
- OS CentOS 5.5
- apache 2.2.3
- MySQL 4.1.22
- Ruby 1.8.7
- Ruby on Rails 2.3.8
- Passenger 2.2.15
Ruby on Railsのコードは下のような metal を使ったコードなので Railsのコントロラーを使った場合に比べ軽いと思います。
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) class MysqlImages def self.call(env) if env["PATH_INFO"] =~ /^\/mysql_images\/(.*)/ image = MysqlImage.find($1) [200, {"Content-Type" => image.mime_type, "Content-Length" => image.data.size.to_s}, [image.data]] else [404, {"Content-Type" => "text/html"}, ["Not Found"]] end ensure ActiveRecord::Base.clear_active_connections! end end
ab -n 100 でアクセスしたときの 処理能力(Requests per second) です。
画像サイズ | 3.5Kbye | 104Kbyte | 536Kbyte | 2.0Mbyte |
---|---|---|---|---|
Apache | 50.0 | 48.3 | 48.3 | 48.4 |
Ruby on Rails | 33.7 | 9.5 | 3.9 | 1.2 |
apacheの画像配信が画像サイズによらずほぼ一定なのに対し、Railsは画像サイズに比例して遅くなっていますね。しかしこの環境でも小さい画像であれば、極端に遅いということはないようです。
性能はサーバーの環境(メモリーサイズ、MySQLのバージョン、設定・・・)によるのであくまで目安として下さい。