A simple for loop over Order.objects.all() works fine until it meets 5 million rows. Then you get memory spikes, timeouts, a slow database, and no idea where the job stopped.
Here is the pattern that turns that into a restartable pipeline.
The core idea
Don't run one big job. Run thousands of small, independent Celery tasks, each handling one ID range.
CHUNK_SIZE = 2000
@shared_task
def dispatch_order_recalculation():
bounds = Order.objects.aggregate(lo=Min("id"), hi=Max("id"))
if bounds["lo"] is None:
return 0
start = bounds["lo"]
while start <= bounds["hi"]:
end = start + CHUNK_SIZE - 1
process_order_chunk.delay(start, end)
start = end + 1
@shared_task(bind=True, max_retries=3, acks_late=True)
def process_order_chunk(self, start_id, end_id):
try:
orders = list(
Order.objects.filter(id__gte=start_id, id__lte=end_id)
.only("id", "subtotal", "tax_rate", "discount")
)
for order in orders:
order.total = calculate_total(order)
Order.objects.bulk_update(orders, ["total"], batch_size=500)
except Exception as exc:
raise self.retry(exc=exc, countdown=30 * (self.request.retries + 1))
Checklist
- Keyset pagination, not OFFSET. Range queries on an indexed column stay fast across the whole table.
-
.only()+bulk_update(). Fewer bytes read, far fewer queries. It skipssave()and signals, so setupdated_atyourself if needed. - Idempotent tasks. Compute from source data, use upserts, and keep processed flags for emails or payments.
-
Celery settings.
acks_late=True,prefetch_multiplier=1, time limits, and a dedicatedbulkqueue. - Protect Postgres. Limit concurrency, keep one chunk per transaction, index filter columns, and consider PgBouncer.
-
Track progress. Use a
BulkJobmodel withF()counters so failed ranges can be requeued alone.
BulkJob.objects.filter(pk=job_id).update(completed_chunks=F("completed_chunks") + 1)
Chunk size
There's no universal number. Start with 1,000 to 5,000 records, benchmark 500 / 2,000 / 5,000 on your real data, and aim for tasks that take a few seconds to a minute.
Full guide
The complete version, with Celery settings, common mistakes, a production checklist, and an FAQ, is on my blog:
👉 https://blog.reiteachaigean.com/blog/process-5-million-records-django-celery
What's the largest dataset you've processed with Celery? Share your setup below.
Top comments (0)