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 }}
Repository navigation
STORAGE: Bucket.delete_blobs() should use Batch #727
Ugh! While conceptually this is a relatively simple change, it is a really messy thing to try to test (batching means kicking down a layer below api_request, which makes all the affected tests barf). I've discarded the branch I started.
Batch.finish calls _make_request on its connection, rather than api_request. The existing stub connection doesn't have that, and its contract is different enough that I backed off of trying to add it. It was also going to be tricky to write assertions about the multipart sub-requests (I started factoring out helper methods from TestBatch, but dropped that as well).
No reason it couldn't be done: I just felt like I was down the rabbit hole, and bailed.
raises error KeyError: "Cannot get('nextPageToken', default=None) on a future" because the iterator returned by list_blobs is making calls to get the next page in the context of a batch.
Maybe this is a separate errors? Seems like the above code should work without encasing blobs in list(blobs)
It's certainly separate from this issue, but good to know. I recommend against passing an iterator like that to delete_blobs() but it is a bug we can try to take a stab at.
In my mind delete_blobs should take an iterator. A Blob object's state is not intrinsically tied to the state of a blob in the cloud, therefor deleting a blob isn't (or shouldn't be) a mutation of the state of the blob itself. The blob is just a wrapper for information about how to make the RPC call.
Again, how is this a mutation? delete_blob does do any modification of the Blob object. You can't infer "No mutation of any state at all" from that PEP, it's mutation of state associated with objects in the iterator?
Came here while trying to open a new issue concerning this. My usecase is deleting or moving millions of blobs. Currently I use one main thread that lists all blobs in a bucket and puts the ones to be deleted on a queue. A few dozen worker threads then individually delete each blob. I'm averaging about 60 deletes/sec, adding more threads doesn't help. And that's a bit slow for millions of blobs (takes days to run).
I'm trying to use the Batch class for this, but it is not entirely clear to me how to correctly use it and if it even supports deletes, am getting a header parsing error when I try to batch deletes.
It would be great if the bucket.delete_blobs() method (and other related methods for that matter) would use a batch by default. The current code is:
defdelete_blobs(self, blobs, on_error=None, client=None):
"""Deletes a list of blobs from the current bucket. Uses :meth:`delete_blob` to delete each individual blob. If :attr:`user_project` is set, bills the API request to that project. :type blobs: list :param blobs: A list of :class:`~google.cloud.storage.blob.Blob`-s or blob names to delete. :type on_error: callable :param on_error: (Optional) Takes single argument: ``blob``. Called called once for each blob raising :class:`~google.cloud.exceptions.NotFound`; otherwise, the exception is propagated. :type client: :class:`~google.cloud.storage.client.Client` :param client: (Optional) The client to use. If not passed, falls back to the ``client`` stored on the current bucket. :raises: :class:`~google.cloud.exceptions.NotFound` (if `on_error` is not passed). """forblobinblobs:
try:
blob_name=blobifnotisinstance(blob_name, six.string_types):
blob_name=blob.nameself.delete_blob(blob_name, client=client)
exceptNotFound:
ifon_errorisnotNone:
on_error(blob)
else:
raise
The batch version might look something like (just a draft, not tested):
defdelete_blobs(self, blobs, on_error=None, client=None):
withself.client.batch() asbatch:
forblobinblobs:
try:
blob_name=blobifnotisinstance(blob_name, six.string_types):
blob_name=blob.nameself.delete_blob(blob_name, client=client)
# Send batch off if fulliflen(batch._requests) >batch._MAX_BATCH_SIZE-1:
batch.finish()
Activity
tseaver commentedon Mar 19, 2015
Ugh! While conceptually this is a relatively simple change, it is a really messy thing to try to test (batching means kicking down a layer below
api_request, which makes all the affected tests barf). I've discarded the branch I started.dhermes commentedon Mar 19, 2015
Good to know. How do you mean "kicking down"?
tseaver commentedon Mar 19, 2015
Batch.finishcalls_make_requeston its connection, rather thanapi_request. The existing stub connection doesn't have that, and its contract is different enough that I backed off of trying to add it. It was also going to be tricky to write assertions about the multipart sub-requests (I started factoring out helper methods fromTestBatch, but dropped that as well).No reason it couldn't be done: I just felt like I was down the rabbit hole, and bailed.
dhermes commentedon Mar 19, 2015
Cool. Seems like we could improve the code based on those observations. I'll ping you if I pick this up at some point.
elibixby commentedon Aug 18, 2016
Should this have label: bug?
The following code:
raises error
KeyError: "Cannot get('nextPageToken', default=None) on a future"because the iterator returned bylist_blobsis making calls to get the next page in the context of a batch.Maybe this is a separate errors? Seems like the above code should work without encasing
blobsinlist(blobs)dhermes commentedon Aug 18, 2016
It's certainly separate from this issue, but good to know. I recommend against passing an iterator like that to
delete_blobs()but it is a bug we can try to take a stab at.elibixby commentedon Aug 18, 2016
Slightly connected to this bug, since that pattern would be unnecessary if
delete_blobsused a batch. But I agree it isseperateseparate.tseaver commentedon Aug 18, 2016
Iterating over a collection while mutating it is always problematic, even without adding RPC calls to the mix: see PEP 3106.
I would say the
list(blobs)fix is the Right Thing(TM).elibixby commentedon Aug 18, 2016
In my mind
delete_blobsshould take an iterator. ABlobobject's state is not intrinsically tied to the state of a blob in the cloud, therefor deleting a blob isn't (or shouldn't be) a mutation of the state of the blob itself. The blob is just a wrapper for information about how to make the RPC call.dhermes commentedon Aug 18, 2016
It does. We're just recommending to you that mutating a Python iterator while using it should be avoided when possible.
elibixby commentedon Aug 18, 2016
Again, how is this a mutation?
delete_blobdoes do any modification of theBlobobject. You can't infer "No mutation of any state at all" from that PEP, it's mutation of state associated with objects in the iterator?6 remaining items
The-Fonz commentedon Oct 16, 2019
Came here while trying to open a new issue concerning this. My usecase is deleting or moving millions of blobs. Currently I use one main thread that lists all blobs in a bucket and puts the ones to be deleted on a queue. A few dozen worker threads then individually delete each blob. I'm averaging about 60 deletes/sec, adding more threads doesn't help. And that's a bit slow for millions of blobs (takes days to run).
I'm trying to use the
Batchclass for this, but it is not entirely clear to me how to correctly use it and if it even supports deletes, am getting a header parsing error when I try to batch deletes.It would be great if the
bucket.delete_blobs()method (and other related methods for that matter) would use a batch by default. The current code is:The batch version might look something like (just a draft, not tested):
feat: add support for proto3 optional tag (#727)
feat: add support for proto3 optional tag (#727)
chore(deps): update dependency google-cloud-monitoring to v2.14.1 (#727)
chore: Update gapic-generator-python to v1.8.5 (#727)
chore: release 1.10.4 (#727)
chore: Update gapic-generator-python to v1.8.5 (#727)
feat: add reauth feature to user credentials (#727)
chore: librarian update image pull request: 20260213T152756Z (#727)
chore(main): release 2.13.1 (#727)
chore(python): Block pushing non-cloud libraries to Cloud RAD (#727)
fix: retry client side requests timeout (#727)
chore(python): auto approve template changes (#727)