Reduce Django Startup and Import Time
Make Django processes start faster: measure import time with python -X importtime, defer heavy imports, avoid database queries at import, trim INSTALLED_APPS, and why startup matters for serverless, autoscaling, deploys, and management commands.
Problem statement
A Django process that takes eight seconds to start hurts in several places: serverless cold starts on Cloud Run or Lambda, autoscaling that adds capacity too slowly during a spike, rolling deploys and health checks that time out, Celery workers restarting after max_tasks_per_child, and every management command and test run. The time goes into importing Python modules: Django itself, every app, and libraries such as pandas, boto3, or ML frameworks pulled in at module level. Measuring and deferring the heavy imports usually cuts startup substantially.
- Measure:
python -X importtime -c "import django; django.setup()" 2> imports.log(with your settings), then sort by cumulative time. - Move heavy imports (pandas, numpy, boto3 clients, PDF and ML libraries) inside the functions that use them.
- Never query the database or call external services at import time or in
AppConfig.ready(). - Remove unused apps from
INSTALLED_APPSand unused middleware. - For Gunicorn, consider
preload_appso the import cost is paid once in the master. - Re-measure after each change; track startup time in CI if cold starts matter.
Step-by-step solution
1. Measure import time
DJANGO_SETTINGS_MODULE=config.settings.production SECRET_KEY=x \
python -X importtime -c "import django; django.setup(); import config.urls" 2> importtime.log
sort -t'|' -k2 -n importtime.log | tail -25
The second column is cumulative microseconds for each module including its imports. Importing config.urls triggers view imports, which is where many heavy libraries sneak in. tuna importtime.log shows the same data as an interactive tree.
2. Defer heavy imports
# Before: reports/views.py imported on startup
import pandas as pd
def export(request):
df = pd.DataFrame(...)
# After: imported only when the view runs
def export(request):
import pandas as pd
df = pd.DataFrame(...)
Python caches modules after the first import, so later calls pay nothing. Apply this to libraries used by a few views or tasks: data science stacks, PDF generation, image processing, cloud SDKs.
3. Remove import-time work
Code that runs at import or in ready() delays every start:
- Database queries (
Site.objects.get_current()in a module, populating caches) also fail when the database is unavailable during builds. - Creating cloud SDK clients (
boto3.client(...)) at module level loads large endpoint metadata; create them lazily. - Reading large files, compiling many regexes, or loading ML models.
Use lazy patterns:
from functools import lru_cache
@lru_cache(maxsize=1)
def s3():
import boto3
return boto3.client("s3")
4. Trim apps and middleware
Every app in INSTALLED_APPS is imported, its models registered, and its signals connected. Remove leftovers such as django_extensions or debug tools from production settings, and middleware that is not needed in production.
5. Use preloading where it fits
With Gunicorn preload_app = True, the master imports the app once and forks workers, so worker startup is nearly instant and memory is shared copy-on-write. It does not speed up the master itself, and it needs care with connections opened at import; see Use Gunicorn preload_app With Django.
6. Bytecode and images
Precompile bytecode in Docker images (python -m compileall -q /app and site-packages) so containers do not write .pyc files on first import, which matters with read-only filesystems and cold starts.
Verification check: time python -c "import django; django.setup(); import config.urls" drops noticeably, Cloud Run or Lambda cold-start durations in logs fall, and health checks during deploys pass sooner.
Explanation
Starting a Django process means importing the settings module, running django.setup() (which imports every installed app and its models), and, when the first request arrives, importing the URLconf and every view module it references. Python executes each module's top-level code on import, so heavy libraries and work done at import time add directly to startup. Deferring rarely used imports and removing import-time side effects keeps the critical path short, which pays off wherever processes start often.
Edge cases / notes
- Lazy imports hide errors until the code runs; keep tests that exercise those paths.
- Newer Python versions start the interpreter somewhat faster; upgrading helps a little, but imports dominate.
- Warm-up requests after deploy (hitting key URLs) move the URLconf import cost off real users.
- Celery workers benefit too: faster restarts after
max_tasks_per_childrecycling.
Next steps
- If the first request after deploy is slow, see Fix Django Slow First Request After Deploy.
- Share import cost across workers with Use Gunicorn preload_app With Django.
FAQ
Why does my Django app take so long to start?
Usually heavy module imports (data, cloud, PDF, ML libraries) and work done at import time such as database queries.
How do I measure Django import time?
Run python -X importtime with django.setup() and your URLconf, then sort by cumulative time.
Is it bad practice to import inside functions?
It is a reasonable optimisation for heavy, rarely used libraries. Keep common imports at module level.
Does startup time matter on a VPS?
Less than on serverless, but it affects deploys, worker restarts, and management commands.