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
from dataclasses import dataclass from typing import Optional from sqlalchemy import select, func
Building a pagination helper with SQLAlchemy
pagination
dataclass
computed-properties
Intermediate
8 steps
php
final class UserActivityStream { public function __construct( private readonly \PDO $db,
Streaming user activity with PHP generators
generators
pagination
lazy-evaluation
Intermediate
8 steps
javascript
export function buildPaginationUrl(baseUrl, { page, perPage, filters = {}, sort } = {}) { const url = new URL(baseUrl); const params = url.searchParams;
Building and parsing pagination URLs
url-parsing
query-string
pagination
Intermediate
8 steps
python
import re from email_validator import validate_email, EmailNotValidError _DISPOSABLE_DOMAINS = {
Normalizing signup emails in Python
validation
normalization
data-cleaning
Intermediate
8 steps
python
import time from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path
Parallel file downloads with a thread pool
concurrency
thread-pool
streaming-io
Intermediate
8 steps
python
from datetime import datetime from zoneinfo import ZoneInfo
Timezone-safe datetime handling in Python
timezones
datetime
normalization
Intermediate
4 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.