Queryset to filter exactly N objects from a table

Viewed 40

I am trying to work out how to allocate exactly N available objects, in a way which is safe against concurrent activity. Is there a way to write a queryset that will fail if it cannot return at least N objects?

This is probably an excess of caution for my particular application because the chances of a race are infinitesimal, but I'm curious

What I know won't work is

available = Foo.objects.filter( status=Foo.AVAILABLE).count()
if available < N:
    #let the user know there aren't enough left

# but now something concurrent may grab them! 

foo_qs = Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N]
with transaction.atomic():
    for foo in foo_qs:
        ... # quite a bit. In RL I will  have locked related objects as well.
        foo.status=Foo.RESERVED
        foo.save()

because slicing a queryset guarantees only no more than N objects.

Removing the slice might lock a large number of rows that I don't need to lock. Is this inefficient? The whole Foo table won't be locked for long because I update only N objects and then exit the transaction.

Is the only answer to grab the objects one at a time inside the transaction, or to get all of them as a list and re-check its length?

with transaction.atomic():
    foos = list( foo_qs)
    if len(foos) < N:
        raise ...    # fail transaction
    for foo in foos:
        ... # as before
1 Answers

Maybe something like:

with transaction.atomic():
   # Locks before checking the count
   available = Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N]

   if available.count() >= N:
      available.update(status=Foo.RESERVED)
   else:
      raise ...    # fail transaction

Or using list:

with transaction.atomic():
   # Locks before checking the length
   available = list(Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N])

   if len(available) >= N:
      for foo in available:
         foo.status=Foo.RESERVED
         foo.save()
   else:
      raise ...    # fail transaction
Related