🔨

S3にある大量のファイルのメタデータを書き換える

2020/09/28に公開

背景

レシピけんさく では画像をS3に置いてCloudflare (CDN) でキャッシュしているのだが, Cache-Controlmax-age を1ヶ月に設定してしまった. CDN側でもっとキャッシュして転送量 (料) を節約するべく, max-age を1年に設定したい気持ち.

S3では既存のメタデータを書き換えることが出来ないので, copy-object で上書きコピーして, その際に新たなメタデータを設定する. ごく少数ならコンソールや aws コマンドを使っても良いが, 大量にあると切りが無いので, AWSのAPIを直接叩くことにした. (aws コマンド呼び出しだと, パフォーマンスが出なかった) 以下は, *.webp, *.jpg, *.png の Cache-Control を書き換える例.

64並列で実行して, 約215万個のファイルを5500秒 (秒間約400件) で処理することができた. ここまでやっても一切エラーを起こさなかったAWSはさすが. ちなみに, COPY リクエストは1000リクエストで0.0047USDが課金されるので, 10USDちょいかかっている.

にゃーん

Rubyスクリプト

aws-sdk-s3, parallel, dotenv が必要なので, gem で入れるか Bundler 使うかする.

#! /usr/bin/env ruby

require "dotenv"
require "aws-sdk-s3"
require "parallel"

Dotenv.load

pp ENV["ASSETS_HOST"]

list_file = ARGV.shift
fh = File.open(list_file)

puts Time.now.iso8601(6)

Parallel.each_with_index(fh.each_line, in_threads: 64) do |line, index|
  next unless line =~ /\.(?:webp|jpeg|png)/
  path = line.chomp.sub(/.* /, "")
  ext = path.sub(/.*\./, "")
  s3 = Aws::S3::Client.new

  begin
    s3.copy_object(
      bucket: ENV["ASSETS_HOST"],
      copy_source: "/#{ENV['ASSETS_HOST']}/#{path}",
      key: path,
      content_type: "image/#{ext}",
      cache_control: "public, max-age=31536000",
      metadata_directive: "REPLACE"
    )
  rescue Aws::Errors::ServiceError => error
    pp [path, index, error]
  end

  pp [path, index, Time.now.iso8601(6)] if index%10000 == 0
end

puts Time.now.iso8601(6)

使い方

  1. 環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, ASSETS_HOST を設定するか, .env に書き込む
  2. aws s3 ls の結果をファイルに落とす. (APIで取得して順次処理しても良いのだが, 途中でコケたときに困るので.)
    aws s3 ls --recuesive s3://$ASSET_HOST > s3.list
    
  3. スクリプトにファイル一覧を食わせる.
    ruby update-max-age.rb s3.list
    

Discussion