python
41 lines · 7 steps
A batched session-cleanup command in Django
A custom management command deletes expired session rows in safe, bounded batches with a configurable grace period.
Explained by
highlit
1from datetime import timedelta
2
3from django.contrib.sessions.models import Session
4from django.core.management.base import BaseCommand
5from django.utils import timezone
6
7
8class Command(BaseCommand):
9 help = "Delete session records that have already expired."
10
11 def add_arguments(self, parser):
12 parser.add_argument(
13 "--grace-hours",
14 type=int,
15 default=0,
16 help="Keep sessions that expired within the last N hours.",
17 )
18 parser.add_argument(
19 "--batch-size",
20 type=int,
21 default=5000,
22 help="Number of rows to delete per query.",
23 )
24
25 def handle(self, *args, **options):
26 cutoff = timezone.now() - timedelta(hours=options["grace_hours"])
27 batch_size = options["batch_size"]
28 expired = Session.objects.filter(expire_date__lt=cutoff)
29
30 total = 0
31 while True:
32 pks = list(expired.values_list("pk", flat=True)[:batch_size])
33 if not pks:
34 break
35 deleted, _ = Session.objects.filter(pk__in=pks).delete()
36 total += deleted
37 self.stdout.write(f"Purged {deleted} expired sessions...")
38
39 self.stdout.write(
40 self.style.SUCCESS(f"Done. Removed {total} expired sessions.")
41 )
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deleting in bounded batches keeps a single cleanup query from locking or overwhelming the database.
- 2Exposing tunables like grace period and batch size as command arguments makes an operational task safe to run in different environments.
- 3Collecting primary keys first and then deleting by pk__in avoids surprises from a moving filter during iteration.
Related explainers
python
import hashlib from collections import defaultdict from pathlib import Path
Finding duplicate files by size then hash
hashing
file-io
deduplication
Intermediate
7 steps
python
from flask import Flask, request, g, jsonify from flask_babel import Babel, gettext as _, format_datetime from datetime import datetime
Per-request localization in Flask with Babel
i18n
content-negotiation
request-lifecycle
Intermediate
8 steps
go
package handlers import ( "encoding/base64"
Cursor pagination in a Gin handler
pagination
cursor
rest-api
Intermediate
8 steps
python
from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key from django.db.models.signals import post_save, post_delete from django.dispatch import receiver
Busting template fragment caches in Django
caching
signals
cache-invalidation
Intermediate
4 steps
python
from datetime import datetime, timezone _INTERVALS = ( ("year", 60 * 60 * 24 * 365),
Building a human-friendly time_ago helper
datetime
timezones
formatting
Intermediate
5 steps
python
import time import threading from flask import Flask, request, jsonify, g
A token-bucket rate limiter in Flask
rate-limiting
token-bucket
middleware
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-batched-session-cleanup-command-in-django-explained-python-da22/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.