uploads: generate thumbnails in parallel.

Make uploads faster by generating and saving thumbnails in parallel.

We generate each thumbnail in parallel, then send each thumbnail to the
backend image servers in parallel.

Most images have 5 variants: 'preview' (150x150), 180x180, 360x360,
720x720, and 'sample' (850px width). Plus the original file, that's 6
files we have to save. In production we have 2 image servers, so we have
to save each file twice, to 2 remote servers. Doing all this in parallel
should make uploads significantly faster.
This commit is contained in:
evazion
2022-02-04 15:39:00 -06:00
parent fd25cd6868
commit e7744cb6e3
5 changed files with 10 additions and 11 deletions

View File

@@ -30,7 +30,7 @@ class StorageManager
# written, or an error is raised and the original file is left unchanged. The # written, or an error is raised and the original file is left unchanged. The
# file should never be in a partially written state. # file should never be in a partially written state.
# #
# @param io [IO] a file (or a readable IO object) # @param src_file [File] the file to store
# @param path [String] the remote path where the file should be stored # @param path [String] the remote path where the file should be stored
def store(io, path) def store(io, path)
raise NotImplementedError, "store not implemented" raise NotImplementedError, "store not implemented"

View File

@@ -12,13 +12,12 @@ class StorageManager::Local < StorageManager
super(**options) super(**options)
end end
def store(io, dest_path) def store(src_file, dest_path)
temp_path = full_path(dest_path) + "-" + SecureRandom.uuid + ".tmp" temp_path = full_path(dest_path) + "-" + SecureRandom.uuid + ".tmp"
FileUtils.mkdir_p(File.dirname(temp_path)) FileUtils.mkdir_p(File.dirname(temp_path))
io.rewind bytes_copied = IO.copy_stream(src_file.path, temp_path)
bytes_copied = IO.copy_stream(io, temp_path) raise Error, "store failed: #{bytes_copied}/#{src_file.size} bytes copied" if bytes_copied != src_file.size
raise Error, "store failed: #{bytes_copied}/#{io.size} bytes copied" if bytes_copied != io.size
FileUtils.chmod(DEFAULT_PERMISSIONS, temp_path) FileUtils.chmod(DEFAULT_PERMISSIONS, temp_path)
File.rename(temp_path, full_path(dest_path)) File.rename(temp_path, full_path(dest_path))

View File

@@ -9,14 +9,14 @@ class StorageManager::Mirror < StorageManager
super(**options) super(**options)
end end
def store(io, dest_path) def store(src_file, dest_path)
services.each do |service| Parallel.each(services, in_threads: Etc.nprocessors) do |service|
service.store(io, dest_path) service.store(src_file, dest_path)
end end
end end
def delete(path) def delete(path)
services.each do |service| Parallel.each(services, in_threads: Etc.nprocessors) do |service|
service.delete(path) service.delete(path)
end end
end end

View File

@@ -7,7 +7,7 @@ class StorageManager::Null < StorageManager
super(base_url: nil) super(base_url: nil)
end end
def store(io, path) def store(src_file, path)
# no-op # no-op
end end

View File

@@ -271,7 +271,7 @@ class MediaAsset < ApplicationRecord
end end
def distribute_files!(media_file) def distribute_files!(media_file)
variants.each do |variant| Parallel.each(variants, in_threads: Etc.nprocessors) do |variant|
variant.store_file!(media_file) variant.store_file!(media_file)
end end
end end