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.
Support parallel operations for copying objects #36
Reacted by Chris Hiestand, Tim Sweña (Swast), Chris Roat, Hadrien Jean, Nate McCurdy, João Carabetta, Miguel Moncada Isla, Peter Whidden, Daniel B, Josh Lipschultz and 14 more
Extra note, for context. The -m flag in gsutil will spawn multiple processes, and multiple threads per process to perform the individual copies in parallel. The solution @dhermes mentioned above is roughly equivalent, assuming that either each blob's upload uses a separate HTTP transport object, or that if they share one, it's thread-safe. (Gsutil uses httplib2 for its transport, which aren't thread-safe, thus we create individual http objects for each thread to use.)
@ayserarmiti none yet. I have a meeting with some of the other Python client maintainers next week to determine priority / design of useful features such as this.
PR googleapis/google-cloud-python#8483 attempted to resolve this feature request. This is a feature request and it's not within scope of what should be worked on at this time. We have a priority on fixing reliability bugs in our clients before feature requests.
Additionally, this feature in particular requires a design review that has not yet been prioritized.
Thank you for your patience. We will revisit this feature request in the future.
Using workaround similar to the above, we're getting a bunch of these errors: urllib3.connectionpool(WARNING): Connection pool is full, discarding connection: storage.googleapis.com
even with fairly modest concurrency:
pool=futures.ThreadPoolExecutor(max_workers=5)
(with default, which is 5x # of cores, we were getting even more of those errors).
Have other folks seen that? Since the calls to urllib3 are being made in the library, is there any hook to increase the connection pool size or otherwise resolve this issue?
Given that gsutil has an option for this, I would echo that it would be very convenient to have this implemented within the library itself.
Reacted by Rodrigo Caus, Rüdiger Busche and Alex Logatski
Code like the following (based pretty closely on the example above, only for downloading files) was resulting in missing files a good amount of the time (with a single thread, works reliably, and fails more of the time as num threads increases):
fromgoogle.cloudimportstoragedefget_templates(download_folder):
# filter warnings about using default credentialswithwarnings.catch_warnings():
warnings.filterwarnings("ignore")
storage_client=storage.Client()
# The "folder" where the files you want to download arebucket=storage_client.get_bucket('somebucket')
blobs=bucket.list_blobs()
pool=futures.ThreadPoolExecutor(max_workers=5)
downloads= []
# Iterating through for loop one by one using API callforblobinblobs:
download=pool.submit(_download_files, download_folder, bucket, blob)
downloads.append(download)
futures.wait(downloads)
def_download_files(download_folder, bucket, blob):
# Have tried pre-creating these as wellname=blob.nameifnotname.endswith("/"):
subdir=os.path.dirname(name)
subdir_path='{}/{}'.format(download_folder, subdir)
ifnotos.path.exists(subdir_path):
os.makedirs(subdir_path)
# The name includes the sub-directory pathfile_path='{}/{}'.format(download_folder, name)
blob.download_to_filename(file_path)
Even after following some suggestions from a helpful person on GCP community slack, and using futures.as_completed(), it still takes me about 11 seconds to copy a directory structure with 41 relatively small Jinja templates from GCS.
The same thing with, e.g., gsutil -m cp:
- [41/41 files][ 24.5 KiB/ 24.5 KiB] 100% Done
Operation completed over 41 objects/24.5 KiB.
gsutil -m cp -r gs://xxx /tmp 2.55s user 1.06s system 131% cpu 2.736 total
@wyardley I'm only starting to get the urllib3 warning when the thread pool size is above 10 workers.
Even then, I think it's only a warning and the same request will be retried: https://stackoverflow.com/a/53765527
Yes, in the end, the urllib3 warnings were a red herring (tho pretty sure I was able to get them with fewer than 10 threads). A solutions eng on the GCP Slack gave me an example that fixes the missing files issue.
Either way, I do think this is functionality that should be builtin to the module.
It seemed to have to do with using futures.wait(). I adapted a variation that @domZippilli kindly threw together and that’s worked relatively well, though it’s not quite as simple as the examples above. Maybe he’d be willing to gist it here.
Of course! Here's the gist. Usual disclaimer that this is offered as-is, no guarantees that it will work or even not hurt you, use at your own risk.
This was about 30 days ago so my memory is a bit foggy but ISTR the key here is that one collect the futures (in this case the downloads list) and then check them using the as_completed function. This ensures that the program waits on every Future returning, as it must block on as_completed until they're all done and of course future.result() for each. My intuition with the first implementation is that some of the futures were not completing before the program exited, causing the missing files.
Then again, I cannot tell you why that would be happening based on the pydoc for futures.wait(). That is basically an abstraction for what I implemented here. So there's probably something I'm missing, but I've always found this works better.
I had also tried some simpler suggestions for fixing the above (including, if memory serves, returning something from _download_files(), and they didn't seem to work either.
Another workaround would be to use sh and do something like
importshsh.gsutil("-m cp data gs://mybucket/data".split())
Reacted by Bryan Yu, Jonathan Jin, Jayce Slesar, Nauman Mustafa, Kenzyme L., Aaron Mangum, Judah Rand, Adeel Khan, Cameron Arshadi, Chaskin Saroff and 5 more
@hector-sab Shelling out to gsutil has significant limitations:
Uploads don't create the empty "placeholder" directory-like entries that are needed to make gcsfuse run at a reasonable speed. It's a 10x speed factor.
Parallel downloads will put all the files into the same directory except when using -r to recursive download a directory. So I can't give it a list of files from multiple directories to put into multiple directories.
Error handling and recovery: Can one reliably parse its stdout to tell which transfers succeeded?
Reacted by William C Grisaitis, Kenzyme L., parnell, Adeel Khan, Neil Halelamien, Cameron Arshadi, Álvaro Martínez Morales, Cavin Dsouza and Sergey Serebryakov
Activity
dhermes commentedon Jan 3, 2018
@bencaine1 You can do this as follows:
I am going to close this since the feature is "supported" in this way. I'm happy to continue discussing / reconsider if you'd like.
houglum commentedon Jan 3, 2018
Thanks, @dhermes.
Extra note, for context. The
-mflag in gsutil will spawn multiple processes, and multiple threads per process to perform the individual copies in parallel. The solution @dhermes mentioned above is roughly equivalent, assuming that either eachblob's upload uses a separate HTTP transport object, or that if they share one, it's thread-safe. (Gsutil uses httplib2 for its transport, which aren't thread-safe, thus we create individual http objects for each thread to use.)theacodes commentedon Jan 3, 2018
We use requests so it shares a thread-safe connection pool. :)
tswast commentedon Aug 28, 2018
Can we reopen this as a feature request? It would be useful to have helpers for recursive copies similar to gsutil.
tseaver commentedon Aug 28, 2018
@frankyn I'll let you prioritize this one among other GCS feature requests.
frankyn commentedon Sep 6, 2018
Acking
ayserarmiti commentedon Apr 10, 2019
@tswast is there any update regarding this feature request. Thanks
tswast commentedon Apr 12, 2019
@ayserarmiti none yet. I have a meeting with some of the other Python client maintainers next week to determine priority / design of useful features such as this.
frankyn commentedon Aug 12, 2019
Hi folks,
PR googleapis/google-cloud-python#8483 attempted to resolve this feature request. This is a feature request and it's not within scope of what should be worked on at this time. We have a priority on fixing reliability bugs in our clients before feature requests.
Additionally, this feature in particular requires a design review that has not yet been prioritized.
Thank you for your patience. We will revisit this feature request in the future.
benbro commentedon Sep 1, 2019
Related:
https://issuetracker.google.com/issues/111828021
tseaver commentedon Nov 13, 2019
@tswast, @frankyn Perhaps we could resolve this by adding a more robust sample to the docs, based on the one @dhermes outlined above. E.g.:
5 remaining items
wyardley commentedon May 13, 2020
Using workaround similar to the above, we're getting a bunch of these errors:
urllib3.connectionpool(WARNING): Connection pool is full, discarding connection: storage.googleapis.comeven with fairly modest concurrency:
(with default, which is 5x # of cores, we were getting even more of those errors).
Have other folks seen that? Since the calls to urllib3 are being made in the library, is there any hook to increase the connection pool size or otherwise resolve this issue?
Given that
gsutilhas an option for this, I would echo that it would be very convenient to have this implemented within the library itself.wyardley commentedon May 21, 2020
Hi --
Code like the following (based pretty closely on the example above, only for downloading files) was resulting in missing files a good amount of the time (with a single thread, works reliably, and fails more of the time as num threads increases):
Even after following some suggestions from a helpful person on GCP community slack, and using
futures.as_completed(), it still takes me about 11 seconds to copy a directory structure with 41 relatively small Jinja templates from GCS.The same thing with, e.g.,
gsutil -m cp:benbro commentedon Jun 19, 2020
@wyardley I'm only starting to get the urllib3 warning when the thread pool size is above 10 workers.
Even then, I think it's only a warning and the same request will be retried: https://stackoverflow.com/a/53765527
wyardley commentedon Jun 19, 2020
Yes, in the end, the urllib3 warnings were a red herring (tho pretty sure I was able to get them with fewer than 10 threads). A solutions eng on the GCP Slack gave me an example that fixes the missing files issue.
Either way, I do think this is functionality that should be builtin to the module.
benbro commentedon Jun 19, 2020
I agree that it should be part of the library.
What fixed the missing files issue?
wyardley commentedon Jun 19, 2020
It seemed to have to do with using
futures.wait(). I adapted a variation that @domZippilli kindly threw together and that’s worked relatively well, though it’s not quite as simple as the examples above. Maybe he’d be willing to gist it here.domZippilli commentedon Jun 19, 2020
Of course! Here's the gist. Usual disclaimer that this is offered as-is, no guarantees that it will work or even not hurt you, use at your own risk.
This was about 30 days ago so my memory is a bit foggy but ISTR the key here is that one collect the futures (in this case the
downloadslist) and then check them using theas_completedfunction. This ensures that the program waits on every Future returning, as it must block onas_completeduntil they're all done and of coursefuture.result()for each. My intuition with the first implementation is that some of the futures were not completing before the program exited, causing the missing files.Then again, I cannot tell you why that would be happening based on the pydoc for
futures.wait(). That is basically an abstraction for what I implemented here. So there's probably something I'm missing, but I've always found this works better.wyardley commentedon Jun 19, 2020
I had also tried some simpler suggestions for fixing the above (including, if memory serves, returning something from
_download_files(), and they didn't seem to work either.benbro commentedon Jun 19, 2020
I think that the following also guarantees that all task complete.
domZippilli commentedon Jun 19, 2020
It should! Executor's
mapfunction rules. And I believe the iterator yields the result of each future, so that is a succinct way of doing the same.[-]Storage: Parallel operations for copying objects[/-][+]Support parallel operations for copying objects[/+]hector-sab commentedon Oct 7, 2020
Another workaround would be to use sh and do something like
1fish2 commentedon Mar 16, 2021
@hector-sab Shelling out to
gsutilhas significant limitations:gcsfuserun at a reasonable speed. It's a 10x speed factor.-rto recursive download a directory. So I can't give it a list of files from multiple directories to put into multiple directories.