You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Performance gap between GCS Python Client and boto3 for object downloads #1458
When comparing download performance between Google Cloud Storage Python Client and boto3 (AWS SDK), we observed that GCS client is significantly slower (about 50% slower) than boto3 for downloading the same objects stored in a GCS bucket.
Thank you for the detailed report. To better understand this, our team will set up benchmarks in a similar environment to the one you've described to try and reproduce your findings. This will help us understand why you're seeing this so we can investigate further.
In the meantime, could you try using Transfer Manager that utilizes multiple workers, in threads or processes, to maximize throughput. You can find more information in our docs and blog post.
Below is a code sample on how to use transfer_manager.download_many()
Additionally, could you also try enabling client-side tracing? This will provide us with more detailed insight that can help us pinpoint any potential bottlenecks on the client side. Here are the instructions on how to enable tracing in the python storage client: https://cloud.google.com/storage/docs/traces#python
I tried download_many, although it differs a bit from my original plan since I was benchmarking downloads directly to memory. download_many appears to only support downloads to disk.
To minimize the impact of disk performance, I used a RAM disk as the destination. With 16 thread workers, I observed a throughput of around 2.7 Gbps, which didn’t show improvement compared to download_as_bytes() or blob.open() then read().
As for process workers, I ran into issues with daemonic processes are not allowed to have children on my current script. Since my focus is on comparing in downloading to memory and multi-threaded setups, I decided to defer testing with process workers for now.
Another question is, looks like blob.open() then read() ultimately calls download_as_bytes() internally (source), but it consistently performs about 30% faster. I’m curious why there’s such a noticeable difference despite the shared code?
I’ll try client-side tracing later, thanks for your suggestion. (Update: I tried to enable Cloud Trace but ran into the permission error: Error while writing to Cloud Trace: 403 The caller does not have permission. I have granted the Cloud Trace Agent role to my service account.. will keep investigating)
FYI, running the same download test with gcloud storage cp can get 3.5 GiB/s (28 Gbps) throughput. However, gcloud test isn't a direct apples-to-apples comparison, since I can't choose threads/processes to configure the concurrency the same as my benchmark. But I hope this baseline is a helpful diagnostic step.
I also did benchmarks across various concurrency settings show that the google-cloud-storage Python client currently falls behind boto3 across different multithreading and multiprocessing levels. Please find the image below:
Hi @dreamtalen I am trying to replicate the issue, using your scripts.
We were able to replicate the issue, currently investigating the root cause and possible fixes.
Hi @dreamtalen, We were able to find out the root cause of the issue. The primary reason for the performance disparity is found as the default data transfer strategy,
While Boto3 Python SDK by default don’t do checksum matching here, as shown in the customer script
We will be introducing the single shot read/download soon. While we recommend to keep the checksum matching enabled, since it is important for the data integrity. But soon, we will give an option to disable the checksum matching through a boolean, to give a better control.
Hi @dreamtalen I am trying to replicate the issue, using your scripts.
We were able to replicate the issue, currently investigating the root cause and possible fixes.
Hi @dreamtalen, We were able to find out the root cause of the issue. The primary reason for the performance disparity is found as the default data transfer strategy,
While Boto3 Python SDK by default don’t do checksum matching here, as shown in the customer script
We will be introducing the single shot read/download soon. While we recommend to keep the checksum matching enabled, since it is important for the data integrity. But soon, we will give an option to disable the checksum matching through a boolean, to give a better control.
Hi @shubham-up-47, that makes sense, and thanks for the detailed explanation!. Looking forward to trying it out once the single-shot version is out.
Reacted by shubham-up-47, Chandra Shekhar Sirimala and Joshua Sleeper
You can pass checksum=None in it to disable checksumming, you will get additional speed by this (blob.open is faster because checksum=None is passed in it by default)
You can pass checksum=None in it to disable checksumming, you will get additional speed by this (blob.open is faster because checksum=None is passed in it by default)
Just a caution, if checksum verification is disabled, corrupted data may go undetected.
@dreamtalen I see you've done benchmarking for google-cloud-storage version: 2.19.0 & 3.1.0
There's a difference between both of these versions as far as the checksum verification is concerned. A snippet from official documentation.
In Python Storage 3.0, uploads and downloads now have a default of “auto” where applicable. “Auto” will use crc32c checksums, except for unusual cases where the fast (C extension) crc32c implementation is not available, in which case it will use md5 instead. Before Python Storage 3.0, the default was md5 for most downloads and None for most uploads
Thanks! I’ve verified that setting single_shot_download=True delivers similar performance to boto3.
Just curious - what exactly does raw_download=True do? I tried toggling it on and off, but didn’t notice any significant difference. Is it necessary in this case?
Reacted by shubham-up-47 and Chandra Shekhar Sirimala
Glad to hear that setting single_shot_download=True worked!
You didn't see a difference because raw_download=True only matters in case of compressed objects. Imagine you have a file named my_data.txt.gz in your GCS bucket and it's stored in GCS with Content-Encoding: gzip.
raw_download=False (default): Using the default download_as_bytes(), the SDK will automatically decompress the my_data.txt.gz data for you, so you will receive the decompressed content of the file my_data.txt.
raw_download=True: When you use download_as_bytes(raw_download=True), you'll get the raw bytes of the my_data.txt.gz file, which are the compressed bytes. You'll need to use gzip.decompress to get the original content.
Activity
cojenco commentedon Apr 11, 2025
Thank you for the detailed report. To better understand this, our team will set up benchmarks in a similar environment to the one you've described to try and reproduce your findings. This will help us understand why you're seeing this so we can investigate further.
In the meantime, could you try using
Transfer Managerthat utilizes multiple workers, in threads or processes, to maximize throughput. You can find more information in our docs and blog post.Below is a code sample on how to use
transfer_manager.download_many()python-storage/samples/snippets/storage_transfer_manager_download_many.py
Lines 16 to 60 in 63c1139
Additionally, could you also try enabling client-side tracing? This will provide us with more detailed insight that can help us pinpoint any potential bottlenecks on the client side. Here are the instructions on how to enable tracing in the python storage client: https://cloud.google.com/storage/docs/traces#python
dreamtalen commentedon Apr 11, 2025
Thanks for your reply, @cojenco !
I tried
download_many, although it differs a bit from my original plan since I was benchmarking downloads directly to memory.download_manyappears to only support downloads to disk.To minimize the impact of disk performance, I used a RAM disk as the destination. With 16 thread workers, I observed a throughput of around 2.7 Gbps, which didn’t show improvement compared to
download_as_bytes()orblob.open()thenread().As for process workers, I ran into issues with
daemonic processes are not allowed to have childrenon my current script. Since my focus is on comparing in downloading to memory and multi-threaded setups, I decided to defer testing with process workers for now.Another question is, looks like
blob.open()thenread()ultimately callsdownload_as_bytes()internally (source), but it consistently performs about 30% faster. I’m curious why there’s such a noticeable difference despite the shared code?I’ll try client-side tracing later, thanks for your suggestion. (Update: I tried to enable Cloud Trace but ran into the permission error:
Error while writing to Cloud Trace: 403 The caller does not have permission.I have granted the Cloud Trace Agent role to my service account.. will keep investigating)dreamtalen commentedon May 9, 2025
Hi @cojenco, just checking in to see if there are any updates or if you need any help reproducing my results. I’ve uploaded the benchmarking script here:https://github.com/dreamtalen/boto3-benchmark/tree/main/google-cloud-storage. Thanks!
dreamtalen commentedon May 23, 2025
FYI, running the same download test with
gcloud storage cpcan get 3.5 GiB/s (28 Gbps) throughput. However, gcloud test isn't a direct apples-to-apples comparison, since I can't choose threads/processes to configure the concurrency the same as my benchmark. But I hope this baseline is a helpful diagnostic step.I also did benchmarks across various concurrency settings show that the google-cloud-storage Python client currently falls behind boto3 across different multithreading and multiprocessing levels. Please find the image below:

shubham-up-47 commentedon May 26, 2025
Hi @dreamtalen I am trying to replicate the issue, using your scripts.
shubham-up-47 commentedon May 28, 2025
We were able to replicate the issue, currently investigating the root cause and possible fixes.
shubham-up-47 commentedon Jun 18, 2025
Hi @dreamtalen, We were able to find out the root cause of the issue. The primary reason for the performance disparity is found as the default data transfer strategy,
By default, GCS Python SDK iterates over small chunks (default size as 8 KB) of the response object, which introduces significant overhead, generating lower speed
By default, Boto3 Python SDK utilizes a single-shot read (response.raw.read()) of the response object, which downloads the entire 64 MB object at once causing better speed (while boto3 too supports chunk download, but it is not their default behavior)
Apart from this, we found some little overhead due to the checksum matching logic,
By default, GCS Python SDK does checksum matching
While Boto3 Python SDK by default don’t do checksum matching here, as shown in the customer script
We will be introducing the single shot read/download soon. While we recommend to keep the checksum matching enabled, since it is important for the data integrity. But soon, we will give an option to disable the checksum matching through a boolean, to give a better control.
dreamtalen commentedon Jun 18, 2025
Hi @shubham-up-47, that makes sense, and thanks for the detailed explanation!. Looking forward to trying it out once the single-shot version is out.
shubham-up-47 commentedon Jun 24, 2025
Hi @dreamtalen, Some updates over the issue,
chandra-siri commentedon Jun 24, 2025
Just a caution, if checksum verification is disabled, corrupted data may go undetected.
@dreamtalen I see you've done benchmarking for
google-cloud-storageversion: 2.19.0 & 3.1.0There's a difference between both of these versions as far as the checksum verification is concerned. A snippet from official documentation.
In Python Storage 3.0, uploads and downloads now have a default of “auto” where applicable. “Auto” will use crc32c checksums, except for unusual cases where the fast (C extension) crc32c implementation is not available, in which case it will use md5 instead. Before Python Storage 3.0, the default was md5 for most downloads and None for most uploads
you can know more by going through Checksum Defaults
shubham-up-47 commentedon Jul 7, 2025
Hi @dreamtalen,
Now, you should be able to get high speed by enabling flags
single_shot_download=Trueandraw_download=Truein the API call download_as_bytespython-storage/google/cloud/storage/blob.py
Line 1393 in 7e0412a
checksum=Noneif you want to disable the checksumming and make it little more faster).Let us know, whether this fix works well or not. Feel free to close the issue if its giving the expected speed.
dreamtalen commentedon Jul 7, 2025
Hi @shubham-up-47,
Thanks! I’ve verified that setting
single_shot_download=Truedelivers similar performance to boto3.Just curious - what exactly does
raw_download=Truedo? I tried toggling it on and off, but didn’t notice any significant difference. Is it necessary in this case?shubham-up-47 commentedon Jul 8, 2025
Glad to hear that setting
single_shot_download=Trueworked!You didn't see a difference because
raw_download=Trueonly matters in case of compressed objects. Imagine you have a file namedmy_data.txt.gzin your GCS bucket and it's stored in GCS withContent-Encoding: gzip.my_data.txt.my_data.txt.gzfile, which are the compressed bytes. You'll need to use gzip.decompress to get the original content.