Ruby on RailsでMySQLに画像を格納する

訳あって、Ruby on RailsMySQLに画像を格納し使う実験をした。

http://www.mysql.com/common/logos/logo-mysql-110x57.png

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 でMySQLSQL文を書くような記事が見つかりますが、そんな面倒はいりません。

max_allowed_packet を変更

大きめな画像を扱うには MySQLサーバー (mysqld)の max_allowed_packet を大きくする必要があります。私の使った環境では max_allowed_packetの初期値は 1Mbyteでした。画像はSQL文の中で1byte を16進数2文字のデータで送受信しているので、扱う画像の2倍以上のbyte数を指定します。

ちなみに、MySQL4.1のマニュアル に「メモリは必要な場合にのみ割り当てられるため、この変数を増やしても安全です。」と書いてあるので大きくしても問題は無いようです。

性能は?

画像をMySQLに格納した場合と、apacheが同じ画像ファイルを配信する場合の性能を比較しました。環境は少し古めです ^^);

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のバージョン、設定・・・)によるのであくまで目安として下さい。