AWS_S3にアップロードしている画像を圧縮してみた


こんにちは。デザイン・システム室の御立田です。

現在、弊社ECサイト「旬をすぐに」の開発に携わっており、2020年7月リリース後より、サイトがどんどんどんどん重くなっていき、パフォーマンス改善の必要に迫られておりました。

商品一覧画面に100枚近くの画像を掲載しており、枚数が多かったのと、サイトの仕様変更もあり、画像の表示領域以上のサイズのものがあったため、これを機にアップロード済みの画像を一気にリサイズ・圧縮してしまおうという事で着手しました。

「旬をすぐに」のサービスでは、画像ストレージにAWSのS3を使用しており、私自身、普段、S3を操作するコードを書くことがないので、復習兼ねて纏めていこうと思います。( 画像の圧縮には、MiniMagickを使用しました。

まずは、画像圧縮用にgemでmini_magickをインストールします。

gem "mini_magick"

その後、新規ファイルにImageCompressorクラスを作成します。

① AWS SDK for Ruby V3をrequireし、画像がアップロードされているAWS S3 クライアント(s3_client)を作成します。

② 作成したクライアントに対して、圧縮したい画像のkeyを元に、対象のオブジェクトを取得します。※ 編集したい画像のキーは、AWSマネジメントコンソールにて確認するか、RailsでActiveStrageを使用している際は、オブジェクトに紐づく画像のblobオブジェクトより取得すれば良いかと思います。

require 'aws-sdk-s3'

class ImageCompressor
  # key_num = "12345678901234567890"

  def self.optimize(key_num)
    # ① clientの作成
    s3_client = Aws::S3::Client.new(
                                    :region => "region",
                                    :access_key_id =>  "access_key_id",
                                    :secret_access_key => "secret_access_key"
                                   )
    # ② オブジェクトの取得
    file = s3_client.get_object(:bucket => "bucket_name", :key => key_num)
  end
end

次に、画像圧縮するロジックを実装します。

③ 対象ファイルが画像かどうか検証。(※ content_typeは、S3オブジェクトのオプションのメタデータで、画像データであれば必ず付いているものではないため注意が必要です。)

④ mini_magickを使用して、画像のリサイズ・圧縮を実施。

  def self.optimize(key_num, height = 400, width = 400, , resolution = 80)
    # ① clientの作成
    s3_client = Aws::S3::Client.new(
                                 :region => "region",
                                 :access_key_id =>  "access_key_id",
                                 :secret_access_key => "secret_access_key"
                                )
    # ② オブジェクトの取得
    file = s3_client.get_object(:bucket => "bucket_name", :key => key_num)

    # ③ AWS_S3中のファイルでcontent_typeがimageファイルに該当するか判断
    if file.content_type.match?(/image/)
      image_file = file.body.read
      # ④ 画像のリサイズ・圧縮
      image = MiniMagick::Image.read(image_file)
      image.resize("#{width}x#{height}")
      image.quality(resolution)
    end
  end

圧縮前の画像と圧縮後の画像を差し替える。

⑤ 圧縮後にそのまま画像をアップロードすると、そのS3オブジェクトのcontent_typeが空欄になるため、ここでcontent_typeを取得し、⑥でアップロード時に付与してます。

⑥ 既存のS3オブジェクトのkey(key_num)に紐づく画像をcompressed_imageに差し替えて、S3にアップロード。

最終的に以下の様になります。

require 'aws-sdk-s3'
class ImageCompressor
	def self.optimize(key_num, height = 400, width = 400, , resolution = 80)
    # ① clientの作成
    s3_client = Aws::S3::Client.new(
                                 :region => "region",
                                 :access_key_id =>  "access_key_id",
                                 :secret_access_key => "secret_access_key"
                                )
    # ② オブジェクトの取得
    file = s3_client.get_object(:bucket => "bucket_name", :key => key_num)

    # ③ AWS_S3中のファイルでcontent_typeがimageファイルに該当するか判断
    if file.content_type.match?(/image/)
      image_file = file.body.read
      # ④ 画像のリサイズ・圧縮
      image = MiniMagick::Image.read(image_file)
      image.resize("#{width}x#{height}") # 400* 400[px]
      image.quality(resolution)          # 解像度: 80%
      
      # 加工後の画像
      compressed_image = image
      # ⑤ content-typeを取得
      type = file.content_type
      
      # ⑥ 既存のS3オブジェクトの画像をcompressed_imageに差し替える。
      s3 = Aws::S3::Resource.new(region: "region")
      bucket = s3.bucket("bucket")
      bucket.object(key_num).put(:body => File.open(compressed_image.path), :content_type => type)
    end
  end
end

以上で、AWS S3上の画像を圧縮するロジックの実装が出来ました。

あとは、optimizeメソッドの変数key_numに圧縮したい画像のkeyを順に与えてあげればと思います。

結果として、画像容量を1枚あたり250KBほどあったものを60KBほどに落とすことが出来、パフォーマンスの向上に繋がりました。

以下の画像では、圧縮後のサイトパフォーマンスになります。Compress Imagesが「A」になりました!

( 本記事を書く3ヶ月前位に改修を行ったので、圧縮前のパフォーマンスのスクショが手元になく掲載出来ませんでした、、 )

今後、次世代画像フォーマット(webPなど)の採用や画像処理以外にも、まだまだパフォーマンス改善のテーマがありますので、引き続き着手していきます。

メンバー募集

ファンデリー デザイン・システム室では現在メンバーを募集しています。 興味を持たれた方は、是非下記のフォームよりご応募ください。

募集要項およびエントリーフォーム >