-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix prefetch_related updates. #4553
Conversation
Although this does fix the bug where people update an object and forget to reload it, it can have a big performance impact when unneeded. I just noticed this change because many PUT operation on my objects suddenly got bumped from 9 to 14 requests, from 8 to 12, etc. That's quite a performance hit. The point being: the object is loaded with multiple I would believe that code modifying data is reponsible for reloading it. Plus, that's consistent with Django form views. But at least, is it possible to opt out of this mechanism without making my own (on a more sentimental issue, I'm feeling after carefully managing my reloads, I'm taking the performance hit because some other users were too lazy to do it themselves) |
I'm not exactly clear what "carefully managing my reloads" means here - can you give an example of how you're managing this in your codebase so that this hasn't been an issue for you. We do need to prioritize not breaking user code more highly than making user code more performant, but happy to consider tweaks if a good case can be made. |
Well, for every
By the way, Django's generic form views do not reload the object upon update. It's unlikely to bite often because it sends a redirect, but if your `get_absolute_url()` does use some of the prefetched objects to build the url, user code is responsible for reloading the object, either in his `MyView.form_valid()` method or his `MyForm.save()` method. Otherwise, it will still see the old values. The fact that prefetched data is not updated on saves is basic Django knowledge that any Django developer should know. instance = Parent.objects.prefetch_related('children').get(pk=42)
Child.objects.filter(parent__pk=42).delete()
instance.children.all() # still shows all children despite having deleted them all And tickets on that subject on Django tracker are systematically closed as Invalid with a core developer saying it is by design (example) I'm not sure DRF working around this is really helpful in the long run. Rather, I would believe making it inconsistent with Django just adds confusion… and useless requests in all cases where reloads are not needed. |
The change here resolves an easily reproducible bug, and is only applied to The ticket linked is closed as invalid because there's no desire to attempt to resolve this at the ORM level. That doesn't mean that we can't or shouldn't resolve it at the generic view level - it's not the same consideration. |
Perhaps the specific ticket was not the best example, I just took the first that showed up in google. The point being: prefetched data is not refreshed when saving an instance. This is expected behavior for all Django APIs, be it ORM, forms or views, and I would have expected DRF to behave the same. It seems odd that if I do It seems odd that adding a It seems odd also that saving the object might succeed - potentially generating side-effects such as logging an audit entry, then raise a But alright, mixin it will be for sure. I don't want to have to track the exact queryset methods invoked to guess whether DRF will reload the object or not. Explicit is better than implicit. __ # This viewset works as expected:
class DraftViewSet(viewsets.ModelViewSet):
queryset = Document.objects.filter(status=Document.DRAFT)
serializer_class = DocumentSerializer
# This viewset will *always* return a 404 when changing the document status
# Actual database outcome depends on Django settings:
# -> if ATOMIC_REQUESTS is enabled, the model update is cancelled
# -> if ATOMIC_REQUESTS is disabled (default), the model remains updated despite the 404
class DraftViewSet(viewsets.ModelViewSet):
queryset = Document.objects.filter(status=Document.DRAFT).prefetch_related('attachments')
serializer_class = DocumentSerializer Only difference comes from adding the |
I've currently no reason to believe we have a behavioral bug there.
I'm not aware of any public API for doing so (other than ad-hoc assignments to the instance or queryset, which we shouldn't support), or have any reason to believe we have a bug there.
So don't perform |
Sure, but it's better than returning incorrect results.
If you think that we've behavior here that can be improved, then sure, let's discuss that. Note that there are already other similar cases, eg. application level validation might pass but database level validation fail in some race conditions.
Grand, so let's get together a failing test case for that and then use (Even better, consider if we can alter the code here so that we only invalidate the |
Interested to know if #4668 addresses your issue sufficiently or not. Certainly a better approach. |
Although it does leave open the case where prefetched data is not updated and is used in the representation, it does fix the odd I will still use a mixin, because I manually keep the prefetch cache up-to-date when I update a full set of objects: instance._prefetched_objects_cache[instance.pictures.prefetch_cache_name] = pictures But with the new approach:
That gives me an idea, posting on the other issue in 5 minutes. |
Closes #2442.