最近 ぼくが携わっているサービスで paperclipのURL生成におけるパフォーマンス改善を行ったので、知見を一般化して共有します。
paperclipのパフォーマンスにおける問題
paperclipのURL生成は難読化のために:hash
を用いている状況下でとても遅くなります。これは、Paperclip::Interpolations::interpolate
(paperclip/interpolations.rb)における計算量が大きくなることが原因のようでした。
Paperclip::Interpolations は URLパターンの一部である:boo
のような表現箇所を適切な値に置換する責務を持つモジュールであり、paperclipにおけるデフォルトのinterpolatorとして定義(paperclip/attachment.rb)されています。
「デフォルトのinterpolator」と述べた通り、これはオプションで変更可能です。ということで、Interpolator を自作することで高速化を試み、ベンチマークをとってみます。
作ったもの
- ロジックは異なりますが
Paperclip::Interpolations::interpolate
でやっていることとほぼ同じです。
# lib/paperclip/custom_interpolator.rb
# see: https://github.com/mizoR/paperclip-benchmark-app/blob/master/lib/paperclip/custom_interpolator.rb
module Paperclip
class CustomInterpolator
include Interpolations
def interpolate pattern, *args
pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol
pattern.gsub(/:([a-z][a-z0-9_]+)/) do |match|
method_name = $1
respond_to?(method_name) ? send(method_name, *args) : match
end
end
def plural_cache
Interpolations.plural_cache
end
end
end
使い方
has_attached_file
にオプションとして interpolator: CustomInterpolator.new
を渡す。
# app/models/user.rb
class User < ActiveRecord::Base
has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100>" },
default_url: "/images/:style/missing.png",
url: "/system/:rails_env/:class/:attachment/:id/:hash.:extension",
hash_secret: "785AFEE8C45E71086D262DB0B0A31442",
interpolator: Paperclip::CustomInterpolator.new # <-- こんな感じ
validates_attachment_file_name :avatar,
matches: [/png\Z/, /jpe?g\Z/]
end
ベンチマーク
- paperclip-benchmark-app/performance.rake at master · mizoR/paperclip-benchmark-app
- ちょうど1.6倍くらいの高速化に成功した。
Calculating -------------------------------------
Paperclip::Interpolations
9.000 i/100ms
Paperclip::CustomInterpolator
16.000 i/100ms
-------------------------------------------------
Paperclip::Interpolations
100.019 (± 3.0%) i/s - 999.000
Paperclip::CustomInterpolator
162.002 (± 3.1%) i/s - 1.632k
問題
作成した Paperclip::CustomInterpolator
は、オリジナルの Paperclip::Interpolations
と比べると以下の挙動に違いが生まれています。たとえば、
url: '/files/:idwhat.:extension'
のようなURLパターンでは、
# Paperclip::Interpolations の場合 `:id`部分が置換される
/files/1234what.png
# Paperclip::CustomInterpolator の場合 (`#idwhat` というメソッドが定義されていないため)置換されない
/files/:idwhat.png
となってしまいます。ので、このようなパターンを利用しているサービスの場合はもうすこし工夫が必要になります。
まとめ
- Paperclip::Interpolations::interpolate は結構遅い
- Interpolator を自作することでパフォーマンスを改善できるかも