Skip to content

Commit 6308d8f

Browse files
authored
fix: Improve upload performance for Cloud Storage (#13213)
The change in #11508 introduced a significant performance regression. It caused the HTTP client to read 8 MB chunks from the upload source and send each chunk in separate PUT requests with a String object instead of an IO object. This significantly slowed upload performance since the SSL socket sends 16K at a time and resizes the String buffer with each iteration. This caused CPU to skyrocket since it requires allocating a new buffer and copying existing data into that string. The garbage collector would have to work hard to keep up. To eliminate this unnecessary String resizing, wrap the read chunk in a `StringIO` object. This enables `OpenSSL::Buffering` to read and write a full 16K buffer. Relates to #13212
1 parent a272ab2 commit 6308d8f

File tree

2 files changed

+6
-2
lines changed

2 files changed

+6
-2
lines changed

google-apis-core/lib/google/apis/core/storage_upload.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require 'google/apis/core/http_command'
1616
require 'google/apis/core/api_command'
1717
require 'google/apis/errors'
18+
require 'stringio'
1819
require 'tempfile'
1920
require 'mini_mime'
2021

@@ -141,7 +142,7 @@ def send_upload_command(client)
141142
request_header = header.dup
142143
request_header[CONTENT_RANGE_HEADER] = get_content_range_header current_chunk_size
143144
request_header[CONTENT_LENGTH_HEADER] = current_chunk_size
144-
chunk_body = upload_io.read(current_chunk_size)
145+
chunk_body = StringIO.new(upload_io.read(current_chunk_size))
145146

146147
response = client.put(@upload_url, body: chunk_body, header: request_header, follow_redirect: true)
147148

google-apis-core/spec/google/apis/core/storage_upload_spec.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@
3939
end
4040

4141
it 'should send upload command' do
42+
allow(client).to receive(:put).and_call_original
43+
expect(client).to receive(:put).with(anything, hash_including(body: kind_of(StringIO)))
44+
4245
command.execute(client)
4346
expect(a_request(:post, 'https://www.googleapis.com/zoo/animals?uploadType=resumable')
4447
.with { |req| req.headers['Content-Length'].include?('11') }).to have_been_made
4548

4649
expect(a_request(:post, 'https://www.googleapis.com/zoo/animals?uploadType=resumable')
47-
.with { |req| req.headers['Content-Type'].include?('application/json') }).to have_been_made
50+
.with { |req| req.headers['Content-Type'].include?('application/json') }).to have_been_made
4851

4952
expect(a_request(:put, 'https://www.googleapis.com/zoo/animals')
5053
.with{ |req| req.headers['Content-Length'].include?('11')}).to have_been_made

0 commit comments

Comments
 (0)