django-cacheops: cache wrongly set to empty list for get query (when accessing a foreignkey field)
I am observing a random get(..) query returning DoesNotExist for some objects (old objects that always existed in the DB). This is happening when I try to access a field that is a ForeignKey to a model (Vehicle in code below).
On digging deep, I found that Django essentially runs a filter query on the parent, then a count() on it to do a get when I try to access the foreign key field.
So I tried to simply call the filter query from shell and found a surprising result (where 409 is the ID of the object on which I am facing this issue):
In [752]: Vehicle.objects.filter(id=409)
Out[752]: <QuerySet []>
In [753]: Vehicle.objects.filter(id=409).nocache()
Out[753]: <QuerySet [<Vehicle: Vehicle object (409)>]>
On further analysis, I found that Django sets upper limit of 21 to filter query in both get and __repr__ (This is why both the field access and the above filter query are returning same empty list). The query cached by cacheops is thus : Vehicle.objects.filter(id=409)[:21] . Indeed, if I check the cache value for this query, I see:
In [748]: qs1c = Vehicle.objects.filter(id=409)
In [749]: qs1c.query.set_limits(high=21)
In [750]: rr.get(qs1c._cache_key())
Out[750]: b'\x80\x04]\x94.'
(The output value is an empty set)
So my problem is: why and when is the cache set to []?
I found that the cache is set in _fetch_all() function in cacheops/query
self._result_cache = list(self._iterable_class(self))
self._cache_results(cache_key, self._result_cache)
The only possibility seems to be list(self._iterable_class(self)) returning empty. How can that ever happen though?
I still don’t know the condition under which this happens - it’s random, but it’s only happening for that one foreign field access. I haven’t observed any other errors with any other model so far.
Can anyone guide how can I further pin-point when this is happening?
My setup: django-cacheops 6.2, Django 3.2, Python 3.11
About this issue
- Original URL
- State: closed
- Created 3 months ago
- Comments: 15 (8 by maintainers)
You are welcome. Glad this is resolved.
Thanks for the pointers @Suor , here’s my thoughts on them:
Clash by error - say you make the same query against different databaseall queries are on single DBA situation where you receive wrong response to database query occasionallyok, this is a possibility, however, outside of this model we have not seen any issue anywhere so far. Also, I have always seen exceptions raised whenever there’s an issue with a pool connection. Django ORM never returns wrong results - at least that is my experience using it for 10+ years now. I have never encountered unreliability. Pools and connections are managed by Django ORM, we are not doing anything special or custom at all.A database exception slurped on the middle, causing empty response, or database cursor read before Django had a chance at it, Django getting 0 rows.Any pointers on this? AFAIK database exception would be re-thrown and never return an empty response. I will search for Django returning 0 rows, but like I said above, I have never ever experienced or heard about Django ORM giving wrong results. I am keen to debug this option though.slave lagged responsewe are not using any slave. Also, these are old rows are in the DB, so they were 100% there when the queries updated the cache.To further debug the issue: I am thinking of including the
cacheopscode in our codebase & logging a warning whenever empty query is response is received. This way, we will know when[]is being written to the cache.Do you think this makes sense? Any further thoughts? (Really really appreciate your time!). Thanks!