Skip to content

Commit 0cdcd4f

Browse files
committed
uploading files to S3 in parallel by using threads
1 parent fce83b5 commit 0cdcd4f

File tree

1 file changed

+66
-42
lines changed
  • lib/octopress-deploy

1 file changed

+66
-42
lines changed

lib/octopress-deploy/s3.rb

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def initialize(options)
2525
@headers = options[:headers] || []
2626
@remote_path = @remote_path.sub(/^\//,'') # remove leading slash
2727
@pull_dir = options[:dir]
28+
@bust_cache_files = []
29+
@thread_pool = []
2830
connect
2931
end
3032

@@ -78,64 +80,61 @@ def connect
7880
# Write site files to the selected bucket
7981
#
8082
def write_files
81-
puts "Writing #{pluralize('file', site_files.size)}:" if @verbose
82-
files_to_invalidate = []
83+
puts "Writing #{pluralize('file', site_files.size)}#{" (sequential mode)" unless parallel_upload?}:" if @verbose
84+
@bust_cache_files = []
85+
8386
site_files.each do |file|
84-
s3_filename = remote_path(file)
85-
o = @bucket.objects[s3_filename]
86-
file_with_options = get_file_with_metadata(file, s3_filename);
87-
88-
begin
89-
s3sum = o.etag.tr('"','') if o.exists?
90-
rescue AWS::S3::Errors::NoSuchKey
91-
s3sum = ""
87+
if parallel_upload?
88+
threaded { write_file file }
89+
else
90+
write_file file
9291
end
92+
end
9393

94-
if @incremental && (s3sum == Digest::MD5.file(file).hexdigest)
95-
if @verbose
96-
puts "= #{remote_path(file)}"
97-
else
98-
progress('=')
99-
end
100-
else
101-
o.write(file_with_options)
102-
files_to_invalidate.push(file)
103-
if @verbose
104-
puts "+ #{remote_path(file)}"
105-
else
106-
progress('+')
107-
end
108-
end
94+
@thread_pool.each(&:join)
95+
bust_cloudfront_cache
96+
end
97+
98+
def write_file file
99+
if write_file? file
100+
s3_upload_file file
101+
@bust_cache_files << file
102+
@verbose ? puts("+ #{remote_path(file)}") : progress('+')
103+
else
104+
@verbose ? puts("= #{remote_path(file)}") : progress('=')
109105
end
106+
end
110107

111-
invalidate_cache(files_to_invalidate) unless @distro_id.nil?
108+
def s3_upload_file file
109+
s3_object(file).write File.open(file), s3_object_options(file)
112110
end
113111

114-
def invalidate_cache(files)
112+
def bust_cloudfront_cache
113+
return if @distro_id.nil?
114+
115115
puts "Invalidating cache for #{pluralize('file', site_files.size)}" if @verbose
116116
@cloudfront.create_invalidation(
117117
distribution_id: @distro_id,
118118
invalidation_batch:{
119119
paths:{
120-
quantity: files.size,
121-
items: files.map{|file| "/" + remote_path(file)}
122-
},
120+
quantity: @bust_cache_files.size,
121+
items: @bust_cache_files.map{|file| "/" + remote_path(file)}
122+
},
123123
# String of 8 random chars to uniquely id this invalidation
124124
caller_reference: (0...8).map { ('a'..'z').to_a[rand(26)] }.join
125125
}
126-
) unless files.empty?
126+
) unless @bust_cache_files.empty?
127+
@bust_cache_files = []
127128
end
128129

129-
def get_file_with_metadata(file, s3_filename)
130-
file_with_options = {
131-
:file => file,
132-
:acl => :public_read
133-
}
130+
def s3_object_options(file)
131+
s3_filename = remote_path file
132+
s3_options = { :acl => :public_read }
134133

135134
@headers.each do |conf|
136135
if conf.has_key? 'filename' and s3_filename.match(conf['filename'])
137136
if @verbose
138-
puts "+ #{remote_path(file)} matched pattern #{conf['filename']}"
137+
puts "+ #{s3_filename} matched pattern #{conf['filename']}"
139138
end
140139

141140
if conf.has_key? 'expires'
@@ -151,24 +150,24 @@ def get_file_with_metadata(file, s3_filename)
151150
expireDate = (Time.now + (60 * 60 * 24 * relative_days[1].to_i)).httpdate
152151
end
153152

154-
file_with_options[:expires] = expireDate
153+
s3_options[:expires] = expireDate
155154
end
156155

157156
if conf.has_key? 'content_type'
158-
file_with_options[:content_type] = conf['content_type']
157+
s3_options[:content_type] = conf['content_type']
159158
end
160159

161160
if conf.has_key? 'cache_control'
162-
file_with_options[:cache_control] = conf['cache_control']
161+
s3_options[:cache_control] = conf['cache_control']
163162
end
164163

165164
if conf.has_key? 'content_encoding'
166-
file_with_options[:content_encoding] = conf['content_encoding']
165+
s3_options[:content_encoding] = conf['content_encoding']
167166
end
168167
end
169168
end
170169

171-
return file_with_options
170+
s3_options
172171
end
173172

174173
# Delete files from the bucket, to ensure a 1:1 match with site files
@@ -273,9 +272,34 @@ def self.default_config(options={})
273272
#{"verbose: #{options[:verbose] || 'false'}".ljust(40)} # Print out all file operations.
274273
#{"incremental: #{options[:incremental] || 'false'}".ljust(40)} # Only upload new/changed files
275274
#{"delete: #{options[:delete] || 'false'}".ljust(40)} # Remove files from destination which do not match source files.
275+
#{"parallel: #{options[:parallel] || 'true'}".ljust(40)} # Speed up deployment by uploading files in parallel.
276276
CONFIG
277277
end
278278

279+
protected
280+
281+
def write_file? file
282+
file_digest = Digest::MD5.file(file).hexdigest
283+
o = s3_object file
284+
s3sum = o.etag.tr('"','') if o.exists?
285+
@incremental == false || s3sum.to_s != file_digest
286+
end
287+
288+
def s3_object file
289+
s3_filename = remote_path file
290+
@bucket.objects[s3_filename]
291+
end
292+
293+
def parallel_upload?
294+
@options[:parallel]
295+
end
296+
297+
def threaded &blk
298+
@thread_pool << Thread.new(blk) do |operation|
299+
operation.call
300+
end
301+
end
302+
279303
end
280304
end
281305
end

0 commit comments

Comments
 (0)