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.

Where startup time goesPython startinterpreterdjango.setup()apps, modelsModule importsheavy librariesImport-time workqueries, networkMost startup time is imports and work done at import; measure before guessing.
  1. Measure: python -X importtime -c "import django; django.setup()" 2> imports.log (with your settings), then sort by cumulative time.
  2. Move heavy imports (pandas, numpy, boto3 clients, PDF and ML libraries) inside the functions that use them.
  3. Never query the database or call external services at import time or in AppConfig.ready().
  4. Remove unused apps from INSTALLED_APPS and unused middleware.
  5. For Gunicorn, consider preload_app so the import cost is paid once in the master.
  6. 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.

startup time breakdown (illustrative)Python + Django core350 msproject apps and models600 mspandas imported by views1400 msboto3 clients at import900 msDB query in AppConfig.ready600 ms0 ms2000 ms4000 msA few heavy imports and import-time work often dominatedjango-deployment.com
Illustrative numbers; your importtime log shows the real ones.

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.

cutting startup timeimporttime measured with prod settingssort by cumulative timeHeavy libraries imported lazilyinside functionsNo queries or network at importnor in AppConfig.readyUnused apps and middleware removedproduction settingsBytecode compiled in the imagecompileall at buildStartup time matters most for serverless, autoscaling, and deploysdjango-deployment.com
Add a CI check that fails if startup time regresses past a threshold.

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_child recycling.

Next steps

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.

Keep going