Repository navigation
Consistency issue #15859
Description
- API: python-ndb
- AppEngine
- Python version: Python 3.8
- google-cloud-ndb version: 1.9.0
Steps to reproduce
On a _post_put_hook(), we have a deferred classmethod, via Cloud Tasks, which will do a few additional processing based on the updated or created entity. We pass self.key to the deferred method, and it will then start by retrieving the entity thru a simple entity = key.get().
It works most of the time, but it sometimes happens that key.get() will return None, although the entity definitely does exist. We tried adding use_cache=False, use_global_cache=False to the get(), but that did not help.
We could circumvent the issue by passing the entity itself (self) to the deferred method, but that is not good practice as it might have been modified before the deferred method runs. We could also add some code to sleep a while, then try again (and this actually does work). But I thought firebase in datastore mode now provided strong consistency, and I thought that meant I was certain to be able to read a freshly put entity.
The elapsed time between the put() and the deferred_post_put_hook() that will do the key.get() may vary, but in one example it was around 100ms, and yet the get() returned None.
Code example
class Thing(ndb.Model):
name = ndb.StringProperty()
def _post_put_hook(self, future):
Thing.deferred_post_put_hook(self.key)
@classmethod
@defer() # The defer() custom decorator handles calling the method via a Cloud Task. It is quite reliable, and is likely not guilty here.
def deferred_post_put_hook(cls, thing_key: ndb.Key):
thing: Thing = thing_key.get()
# Would process thing from here on, but on occasions thing is None
Activity
patb2 commentedon Dec 1, 2021
Just to make it clearer, the effect of this "deferred" thing is that the
thing_key.get()happens a few milliseconds after theput()and possibly on another GAE instance. This probably is a factor, because I have never witnessed akey.get()failing right after aput()on the same instance.anoteboom commentedon May 19, 2022
I have witnessed this problem as well in our software. We rely on this posthook method to keep an elasticsearch in sync.
the key just isn't set in the posthook. The key seems to be available to the rest of code following the put() statement.
anoteboom commentedon Feb 15, 2024
We found a cause in the end. When the put would fail, for instance when the entity is larger than 1Mb, the post_hook is still triggered, with a empty key.
so for us it was just a side effect of another error we could prevent easily.
patb2 commentedon Feb 16, 2024
Hi @anoteboom, and thanks for helping keep this issue alive. The case you encounter, with a failure of the put() is different from ours however, in our case, the put() definitely did occur, and we can later retrieve the entity.
Here is an experiment we did when trying to find a workaround:
What happens is we find all 3 traces in our logs, meaning that all 3 cases happen (together with a great majority of successes): (a) reading the entity with a simple get() fails but reading with no_cache succeeds, (b) reading with no_cache fails as well but trying again after 8 seconds succeeds, (c) reading after wait fails as well. We ended up leaving the wait in place since it recovers a number of errors. But it is not always sufficient.
patb2 commentedon Jul 31, 2024
Hi, Trying to keep alive this unresolved issue (classified as bug so far).
python-ndb APIs allow a
read_consistencyargument, that may bendb.EVENTUALorndb.STRONG. And it is quite explicit thatndb.EVENTUALwould lead to transient inconsistencies. However the default value isndb.STRONG, so we would expect consistency to be the default.Here are two possible ways to mitigate this (not yet tested in a production environment):
None of these is quite satisfactory however.
Also, please refer to this "Google Database Products" article, that says this:
The article is undated unfortunately. It would seem that a "lookup by key" (which I understand is a
key.get()), offers strong consistency.Nostr.send add use_cache=False to the tx that populates Object.json too
ATProto/Nostr.send: try read_consistency=STRONG instead of use_cache=…
chalmerlowe commentedon Mar 2, 2026
This issue was moved from the following repo as part of the monorepo migration: googleapis/python-ndb
daniel-sanche commentedon May 28, 2026
This issue is being bulk closed due to inactivity. Please re-open if you think this still requires attention, and we'll take another look. Thanks!