I’m exploring implementing top-to-bottom async views in our application. (Strictly speaking, I’m working on async logic to generate responses to websocket messages arriving via channels.) Just ran into the queryset limitation that the asynchronous iterator doesn’t support prefetching data.
After some thought and code reading, I overloaded QuerySet.aiterator() with this logic inspired by the synchronous iterator() method, and it seems to work reasonably well – data seems to be prefetched for one chunk at a time. Beyond the performance impact of crossing the sync border for every chunk of results, are there any other side effects that I should worry about?
The solution seemed so easy that I was surprised it wasn’t in 4.2 alpha build – so much so that I’m wondering what I must be overlooking.
(TLDR: The logic sets aside the list of prefetches, then uses the standard async iterator to assemble a chunk of results. The prefetches are then applied to that chunk, much like the standard iterator() method, and then the results are yielded one at a time.)
async def aiterator(self, chunk_size=2000):
"""
An asynchronous iterator over the results from applying this QuerySet
to the database.
"""
from asgiref.sync import sync_to_async
from django.db.models import prefetch_related_objects
if not self._prefetch_related_lookups:
async for result in super().aiterator(chunk_size=chunk_size):
yield result
if chunk_size <= 0:
raise ValueError("Chunk size must be strictly positive.")
prefetch_related_lookups = self._prefetch_related_lookups
self._prefetch_related_lookups = None
chunk = deque()
num_items = 0
async for result in super().aiterator(chunk_size=chunk_size):
chunk.append(result)
num_items += 1
if num_items == chunk_size:
await sync_to_async(prefetch_related_objects)(chunk, *prefetch_related_lookups)
for item in chunk:
yield item
chunk.clear()
num_items = 0
if num_items:
await sync_to_async(prefetch_related_objects)(chunk, *prefetch_related_lookups)
for item in chunk:
yield item
self._prefetch_related_lookups = prefetch_related_lookups