code: def chunked_qs_reverse(qs, chunk_size=10000):
      
      if not qs:
          return
  
      qs = qs.order_by('-id')
  
      # Can't use .only() here in case the query used select_related
      max_id = qs.first().id
      while True:
          chunk = qs.filter(id__lte=max_id)  # upper bound of this chunk
  
          rows = chunk[:chunk_size]
  
          if len(rows) < 1:
              break
  
          yield rows
  
          # update the maximum ID for next iteration
          max_id = max_id - chunk_size
docs: Generator to iterate over the given QuerySet in reverse chunk_size rows at a time
  
      Usage:
  
          >>> qs = FailureLine.objects.filter(action='test_result')
          >>> for qs in chunked_qs_reverse(qs, chunk_size=100):
          ...     for line in qs:
          ...         print(line.message)
  
      Note: This method is just different enough that it seemed easier to keep
      this function separate to chunked_qs.