Benchmarking different ways of creating presigned S3 URLs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A local domain ActiveRecord, Asset#file is a shrine attachment | |
asset = Asset.last | |
# load outside of benchmarking | |
asset.file | |
s3_object = asset.file.storage.object(asset.file.id) | |
ITERATIONS = (ENV['ITERATIONS'] || "1200").to_i | |
$stderr.puts "#{ITERATIONS} iterations\n\n" | |
def naive_public_url(shrine_file) | |
"https://#{["#{shrine_file.storage.bucket.name}.s3.amazonaws.com", *shrine_file.storage.prefix, shrine_file.id].join('/')}" | |
end | |
if naive_public_url(asset.file) != asset.file.url(public: true) | |
raise "naive_public_url implementation does not pass:\n\n#{naive_public_url(asset.file)}\n#{asset.file.url(public: true)}" | |
end | |
# URI.escape may do exactly what we need without requiring splitting on "/", but | |
# it's sadly deprecated when it really ought not to be | |
def naive_with_uri_escape_escaping(shrine_file) | |
# because URI.escape does NOT escape `/`, we don't need to split it, which is what | |
# actually saves us the time. | |
path = URI.escape(shrine_file.id) | |
"https://#{["#{shrine_file.storage.bucket.name}.s3.amazonaws.com", *shrine_file.storage.prefix, shrine_file.id].join('/')}" | |
end | |
if naive_with_uri_escape_escaping(asset.file) != asset.file.url(public: true) | |
raise "naive_with_uri_escape_escaping implementation does not pass:\n\n#{naive_with_uri_escape_escaping(asset.file)}\n#{asset.file.url(public: true)}" | |
end | |
# Re-use a Presigner to see if it gets us any speed -- hope it's thread-safe? | |
AWS_CLIENT = asset.file.storage.client | |
S3_PRESIGNER = Aws::S3::Presigner.new(client: AWS_CLIENT) | |
def reused_sdk_presigner(shrine_file) | |
bucket_name = shrine_file.storage.bucket.name | |
s3_key = [*shrine_file.storage.prefix, shrine_file.id].join("/") | |
S3_PRESIGNER.presigned_url( | |
:get_object, | |
bucket: bucket_name, | |
key: s3_key | |
) | |
end | |
if reused_sdk_presigner(asset.file) != asset.file.url(public: false) | |
raise "reused_sdk_presigner implementation does not pass:\n\n#{reused_sdk_presigner(asset.file)}\n#{asset.file.url(public: false)}" | |
end | |
AWS_SIG4_SIGNER = Aws::Sigv4::Signer.new( | |
service: 's3', | |
region: AWS_CLIENT.config.region, | |
credentials_provider: AWS_CLIENT.config.credentials, | |
unsigned_headers: Aws::S3::Presigner::BLACKLISTED_HEADERS, | |
uri_escape_path: false | |
) | |
def direct_aws_sig4_signer(url) | |
AWS_SIG4_SIGNER.presign_url( | |
http_method: "GET", | |
url: url, | |
headers: {}, | |
body_digest: 'UNSIGNED-PAYLOAD', | |
expires_in: 900, # seconds | |
time: nil | |
).to_s | |
end | |
if direct_aws_sig4_signer(naive_public_url(asset.file)) != asset.file.url(public: false) | |
raise "direct_aws_sig4_signer implementation does not pass:\n\n#{direct_aws_sig4_signer(asset.file)}\n#{asset.file.url(public: false)}" | |
end | |
def inline_direct_aws_sig4_signer(url) | |
signer = Aws::Sigv4::Signer.new( | |
service: 's3', | |
region: AWS_CLIENT.config.region, | |
credentials_provider: AWS_CLIENT.config.credentials, | |
unsigned_headers: Aws::S3::Presigner::BLACKLISTED_HEADERS, | |
uri_escape_path: false | |
) | |
signer.presign_url( | |
http_method: "GET", | |
url: url, | |
headers: {}, | |
body_digest: 'UNSIGNED-PAYLOAD', | |
expires_in: 900, # seconds | |
time: nil | |
).to_s | |
end | |
Benchmark.bmbm do |x| | |
x.report("sdk public_url") do | |
ITERATIONS.times do | |
s3_object.public_url | |
end | |
end | |
x.report("naive S3 public url") do | |
# May not always properly escape chars that need to be escaped in uris? Not sure, may be fine. | |
ITERATIONS.times do | |
naive_public_url(asset.file) | |
end | |
end | |
x.report("naive S3 public url with URI.escape") do | |
ITERATIONS.times do | |
naive_with_uri_escape_escaping(asset.file) | |
end | |
end | |
x.report("sdk presigned_url") do | |
ITERATIONS.times do | |
s3_object.presigned_url(:get) | |
end | |
end | |
x.report("re-use instantiated SDK Presigner") do | |
ITERATIONS.times do | |
reused_sdk_presigner(asset.file) | |
end | |
end | |
x.report("use inline instantiated Aws::Sigv4::Signer directly for presigned url (with escaping)") do | |
ITERATIONS.times do | |
inline_direct_aws_sig4_signer(naive_with_uri_escape_escaping(asset.file)) | |
end | |
end | |
x.report("Re-use Aws::Sigv4::Signer for presigned url (with escaping)") do | |
ITERATIONS.times do | |
direct_aws_sig4_signer(naive_with_uri_escape_escaping(asset.file)) | |
end | |
end | |
x.report("Re-use Aws::Sigv4::Signer for presigned url (without escaping)") do | |
ITERATIONS.times do | |
direct_aws_sig4_signer(naive_public_url(asset.file)) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment