Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase default buffer sizes for storage uploads and downloads. #2657

Closed
coryan opened this issue May 14, 2019 · 4 comments · Fixed by #2945
Closed

Increase default buffer sizes for storage uploads and downloads. #2657

coryan opened this issue May 14, 2019 · 4 comments · Fixed by #2945
Assignees
Labels
api: storage Issues related to the Cloud Storage API. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.

Comments

@coryan
Copy link
Contributor

coryan commented May 14, 2019

The sizes are too small for good performance, they are currently set to 256KiB, and I get better performance from GCE with 1MiB or bigger. My benchmarks show that for my workstation at work something in the 32MiB range is better.

I think we should increase these values. As to what is the ideal number, there probably isn't one, see #726. But until then, we should pick something that performs about optimally for GCE against a regional bucket (in the same region as the GCE instance).

We should also write a benchmark similar to gsutil perfdiag, so we can compare the two.

@coryan coryan added api: storage Issues related to the Cloud Storage API. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design. labels May 14, 2019
@coryan
Copy link
Contributor Author

coryan commented May 27, 2019

I am starting to get data around this. It seems that the buffer size is not that important for downloads after about 1-2MiB, but it remains important for uploads even up to 4MiB:

image

The buffer size is very important for the CPU overhead for uploads, but not so much for downloads:

image

Also the downloads are constrained by CPU, while the uploads are constrained by the roundtrip type to upload each chunk (recall that we are using resumable uploads):

image

A larger analysis is here:

throughput-vs-cpu-analysis.pdf

These are preliminary results, but I wanted to save them somewhere.

@coryan
Copy link
Contributor Author

coryan commented Jun 19, 2019

@dopiera I think this would be a good thing for you to tackle next. It is obvious we need larger buffer sizes (see the graphs above), but there are a couple of questions to answer:

  • How much larger do they need to be?
  • What is the size at which you start getting diminishing returns? (this is related to the previous question).
  • Is that size the same for regional buckets in the same region as the GCE instance vs. buckets in a different region?

For "ideal throughput" (large objects > 32 MiB) all these questions can be answered (I think) by running the throughput_vs_cpu benchmark with the right parameters, capturing the results and then printing the pretty graphs.

For "ideal latency" (small objects < 4MiB, maybe up to 32MiB) we need a new benchmark.

I think I can upload the Python code to generate the graphs in to this bug.

@coryan coryan added this to High priority in Customer Issues Jul 17, 2019
@dopiera
Copy link
Contributor

dopiera commented Aug 7, 2019

TL;DR; the buffer size should be set to 8MiB for uploads and 1.5MiB for downloads.

Methodology:
I've modified the throughput_vs_cpu benchmark such that:

The tests were run on a n1-standard-4 machine. The benchmark was using 2 threads to make sure that there is enough CPU for every thread and that hyperthreading doesn't kick in (if there actually is any - cpuinfo doesn't suggest it).

I've tun the test on a regional (europe-west3) and a remote (europe-west3 -> us-east1) bucket to collect a couple of thousand samples each.

I made the results available via pushing to my repo: dopiera@9c1161c . There is also a helper script for plotting them. For every observation, I'm leaving a command showing the data.

Observations:

  • CPU is not a bottleneck neither for uploads nor downloads.
    This shows that even for largest chunk and files sizes, bandwidth is approximately the same whether MD5 or CRC are on or not:
$ for crc in True False ; do for md5 in True False ; do \
    echo crc=$crc md5=$md5; \
    python plot2.py \
        regional.csv \
        "op==\"DOWNLOAD\" and crc==$crc and md5==$md5  and file_sz > 256 and chunk_sz > 16" \
        chunk_sz,file_sz,bw ; \
    done ; done
crc=True md5=True
Samples=221 EX=353.15, SD=46.17
gnuplot> ^D
crc=True md5=False
Samples=239 EX=353.64, SD=42.39
gnuplot> ^D
crc=False md5=True
Samples=210 EX=355.64, SD=41.25
gnuplot> ^D
crc=False md5=False
Samples=264 EX=352.39, SD=45.10
gnuplot> ^D

The cpu use actually never reaches 1.0 with both CRC and MD5 on:

$ python plot2.py \
  regional.csv \
  "op==\"DOWNLOAD\" and crc==True and md5==True" \
  chunk_sz,file_sz,cpu_use

cpu_use

A far-fetched conclusion is that we don't need to make downloading asynchronous to get better performance.

  • file size has an effect on the download bandwidth, but anything larger than 400 is mostly the same; it also doesn't make much sense to analyze chunk sizes larger than 16MiB for downloads (you'd have to run gnuplot and rotate the graph to actually see it):
$ python plot2.py \
    regional.csv \
    "op==\"DOWNLOAD\" and crc==True and md5==True" \
    chunk_sz,file_sz,bw

large_chunk_dl

  • for file sizes larger than 400M, the chunk sizes between 1.5MiB and 2MiB yield similar average bandwidth to chunk sizes larger than 16MiB; the average between 1MiB and 1.5MiB is singificantly slower, so 1.5Mib is a good value; the look at the plot seems to confirm it:
$ python plot2.py \
    regional.csv \
    op==\"DOWNLOAD\" and crc==True and md5==True and file_sz > 400 and chunk_sz > 1 and chunk_sz < 1.5"  \
    chunk_sz,bw
Samples=39 EX=326.42, SD=41.47
gnuplot> ^D
$ python plot2.py \
    regional.csv \ 
    "op==\"DOWNLOAD\" and crc==True and md5==True and file_sz > 400 and chunk_sz > 1.5 and 
    chunk_sz < 2" \
    chunk_sz,bw
Samples=37 EX=353.70, SD=44.13
gnuplot> ^D
$ python plot2.py \
    regional.csv \
    "op==\"DOWNLOAD\" and crc==True and md5==True and file_sz > 400 and chunk_sz > 16 "  \
    chunk_sz,bw
Samples=153 EX=358.02, SD=43.69
gnuplot> ^D
$ python plot2.py \
    regional.csv \
    "op==\"DOWNLOAD\" and crc==True and md5==True and file_sz > 400 and chunk_sz < 16" \
    chunk_sz,bw
Samples=463 EX=350.55, SD=44.80
gnuplot> 

upload_bw

  • for remote locations, the file size matters more for bandwidth and the peak is at lower chunk sizes:
$ python plot2.py \
    remote.csv \
    "op==\"DOWNLOAD\" and crc==True and md5==True " \
    chunk_sz,file_sz,bw 

remote_dl_bw

A closer look at large files and limited chunks size shows that 1.5MiB is a good choice for remote case too:

$ python plot2.py \
    remote.csv \
    "op==\"DOWNLOAD\" and crc==True and md5==True and file_sz > 400 and chunk_sz < 16" \
    chunk_sz,bw

remote_dl_bw_zoom

  • for uploads, CPU is not an issue either:
$ for crc in True False ; do for md5 in True False ; do \
    echo crc=$crc md5=$md5; \
    python plot2.py regional.csv "op==\"UPLOAD\" and crc==$crc and md5==$md5  and file_sz > 200" \
    chunk_sz,file_sz,bw; \
    done ; done
crc=True md5=True
Samples=1085 EX=48.31, SD=7.10
gnuplot> ^D
crc=True md5=False
Samples=1148 EX=48.34, SD=7.12
gnuplot> ^D
crc=False md5=True
Samples=1109 EX=48.17, SD=7.23
gnuplot> ^D
crc=False md5=False
Samples=1131 EX=48.32, SD=6.69
gnuplot> ^D
  • for uploads, file sizes don't play that big of a role and for sizes larger than 200MiB don't seem to play a role at all:
$ python plot2.py \
    regional.csv \
    "op==\"UPLOAD\" and crc==True and md5==True" \
    chunk_sz,file_sz,bw

uploads

  • the average upload BW for chunk sizes larger than 16MiB and file size larger than 200MiB is around 50MiB/s; this chunk and file size range is already the plateau:
$ python plot2.py \
    regional.csv \
    "op==\"UPLOAD\" and crc==True and md5==True and file_sz > 200 and chunk_sz >16"  \
    chunk_sz,file_sz,bw
Samples=261 EX=50.75, SD=3.12

upload_max

  • the link between the chunk size and bandwidth seems to have a local maximum every 2MiB with 8MiB being the first peek to reach 50MiB/s (the top which we can reach), so 8MiB is a good value:
$ python plot2.py \
    regional.csv
    "op==\"UPLOAD\" and crc==True and md5==True and file_sz > 200 and chunk_sz <16"  \
    chunk_sz,bw

upload_chunk_vs_bw

  • file sizes larger than 200MiB don't matter for uploads to a remote location either:
$ python plot2.py \
    remote.csv \
    "op==\"UPLOAD\" and crc==True and md5==True" \
    chunk_sz,file_sz,bw

uploads_remote

  • for remote uploads, the benefits of increasing the buffer go all the way up to 32MiB, but the 8Mib seems to achieve around 60% of the maximum possible, so 8MiB is still the way to go IMO:
$ python plot2.py \
    remote.csv "op==\"UPLOAD\" and crc==True and md5==True and file_sz > 200 " \
    chunk_sz,bw

upload_chunk_vs_bw_remote

@dopiera
Copy link
Contributor

dopiera commented Aug 7, 2019

Opening the stream within a region takes 55ms and closing 110ms (165ms total). To a us-east1 from europe-west3 these numbers are 257ms and 392ms (649ms total) respectively.

Assuming 50MiB/s and 30 MiB/s throughputs, these numbers mean, that during that extra time, 8.2 MiB or 19.5MiB could be sent respectively. This is the reason why I think files up to at least 20MiB (instead of current 5MiB) should be sent in a non-resumable fashion.

dopiera added a commit to dopiera/google-cloud-cpp that referenced this issue Aug 7, 2019
This fixes googleapis#2657.

Experiments, which back these numbers are described in googleapis#2657.
dopiera added a commit to dopiera/google-cloud-cpp that referenced this issue Aug 7, 2019
Customer Issues automation moved this from High priority to Closed Aug 7, 2019
coryan pushed a commit that referenced this issue Aug 7, 2019
coryan pushed a commit that referenced this issue Aug 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: storage Issues related to the Cloud Storage API. type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.
Projects
No open projects
Customer Issues
  
Closed
Development

Successfully merging a pull request may close this issue.

2 participants