skip to content

Before a Django launch, what do the defaults already do for CONN_MAX_AGE and the cached template loader, and when do you change them?

level: middleimportance: should knowfreq 35%

answer

  1. per-request overhead you can remove
  2. a connection per request by default
  3. loaders you did not specify
  4. explicit loaders lose the wrapper

basics

~20 s

CONN_MAX_AGE defaults to 0, opening a database connection per request; under WSGI a positive value with CONN_HEALTH_CHECKS reuses it. The cached template loader is already on unless OPTIONS['loaders'] is set, in which case you wrap the loaders yourself.

solid answer

~40 s

`CONN_MAX_AGE` defaults to `0`, so Django closes the database connection at the end of every request and the next request reconnects. Under WSGI, setting it to a positive number of seconds keeps each thread's connection for reuse, and `CONN_HEALTH_CHECKS = True` replaces connections the server has dropped; budget one connection per worker thread. Under ASGI the docs say to keep persistent connections off and use pooling, such as the PostgreSQL `pool` option from Django 5.1. The cached template loader needs no action in most projects: since Django 4.1 it wraps the default loaders whenever `OPTIONS['loaders']` is not specified, in development and production alike. If you do list loaders explicitly — for a custom loader, say — nothing is cached until you wrap them in `django.template.loaders.cached.Loader` yourself, and `APP_DIRS` must then be removed.

code

python · 27 lines
python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'OPTIONS': {
            'loaders': [
                (
                    'django.template.loaders.cached.Loader',
                    [
                        'myproject.loaders.TenantLoader',
                        'django.template.loaders.filesystem.Loader',
                        'django.template.loaders.app_directories.Loader',
                    ],
                ),
            ],
        },
    },
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'shop',
        'CONN_MAX_AGE': 60,
        'CONN_HEALTH_CHECKS': True,
    },
}

go deeper

for a junior

Know that CONN_MAX_AGE controls database connection reuse and that templates are cached by default in current Django.

for a middle

Explain CONN_MAX_AGE's default of 0, what a positive value and CONN_HEALTH_CHECKS do, and when the cached loader is and is not applied automatically.

for a senior

Choose connection reuse or pooling from the serving mode and the database's connection budget, and catch explicit loader lists that silently lost caching.

for a principal

Standardise connection strategy across services sharing a database, balancing per-request latency against connection slots and operational complexity.

## Two settings that cost something on every request Most of a pre-launch checklist is about safety. Two items are about per-request overhead: opening database connections and loading templates from disk. Django's defaults handle one well and the other conservatively, and neither is reported by `check --deploy`. ## CONN_MAX_AGE: connection reuse **`CONN_MAX_AGE`** is set per database alias in `DATABASES` and is the maximum lifetime of a connection in seconds. - **Default `0`:** the connection is closed at the end of each request. Every request that queries the database pays for a new connection, including authentication with the database server. - **Positive value:** the connection is kept and reused by later requests on the same thread until it reaches that age. - **`None`:** connections are kept indefinitely. When enabling reuse, also consider **`CONN_HEALTH_CHECKS = True`** (default `False`): Django checks a reused connection once per request before using it, so a connection closed by a database restart is replaced instead of failing a request. Two constraints come with it: 1. **Connections are per thread.** The database must accept at least as many connections as you run worker threads across all processes and instances, now held even while idle. 2. **ASGI is different.** Django's database docs say persistent connections should be disabled under ASGI; use the backend's pooling instead — for PostgreSQL the `pool` option in `OPTIONS`, added in Django 5.1, which refuses to run with a non-zero `CONN_MAX_AGE`. ## The cached template loader A **template loader** finds and compiles templates. The **cached loader** (`django.template.loaders.cached.Loader`) wraps other loaders and keeps each compiled template in memory after the first use, avoiding disk reads and recompilation on every render. | Configuration | Is caching on? | |---|---| | `APP_DIRS = True`, no `OPTIONS['loaders']` | yes: Django wraps the filesystem and app-directories loaders in the cached loader | | no `OPTIONS['loaders']` and `APP_DIRS` false | yes: the filesystem loader is wrapped | | explicit `OPTIONS['loaders']` without the cached wrapper | **no**: every render goes back to the listed loaders | | explicit `OPTIONS['loaders']` wrapped in the cached loader | yes | Since **Django 4.1** this default applies **in development too**; during `runserver` the autoreloader resets the cache when templates change, so edits still show up. Before 4.1, the cached loader was only enabled automatically when `DEBUG` was false, which is why older checklists tell you to switch it on for production. When you list loaders explicitly, `APP_DIRS` must not also be set — Django raises `ImproperlyConfigured` — so include the app-directories loader in the list yourself. ## Deciding at launch - WSGI deployment with a nearby database and a connection budget to spare: set `CONN_MAX_AGE` to a modest number of seconds and enable `CONN_HEALTH_CHECKS`. - Database far away or expensive to connect to, but connection slots tight: prefer a pool or an external pooler over many idle persistent connections. - ASGI deployment: keep `CONN_MAX_AGE = 0` and configure pooling. - Templates: confirm whether `OPTIONS['loaders']` is set anywhere; if it is, wrap the list in the cached loader. ## What interviewers listen for The default of `0` and what it costs, the per-thread connection budget, the ASGI exception, and the fact that the cached loader is automatic unless someone has overridden `loaders`.

  • A team adds a custom template loader and page render times rise noticeably. What likely happened?
    Listing `OPTIONS['loaders']` explicitly replaces Django's default, which wrapped the loaders in the cached loader. Without the wrapper every render re-reads and recompiles templates. Wrap the list in `('django.template.loaders.cached.Loader', [...])`, and include the app-directories loader in it since `APP_DIRS` cannot be combined with explicit loaders.
  • Why not set CONN_MAX_AGE = None everywhere to save the most connection overhead?
    Each worker thread would then hold a connection for its whole life, idle or not, so the database needs a slot for every thread across all instances. Connections killed by the server or a network device also linger until a query fails unless `CONN_HEALTH_CHECKS` is on. Under ASGI the docs advise against persistent connections entirely in favour of pooling.

saying these in an interview costs you the question

  • CONN_MAX_AGE defaults to persistent connections of several minutes
  • The cached template loader must always be enabled by hand for production
  • Listing custom loaders keeps Django's automatic caching wrapper
  • Persistent connections are the recommended setup for ASGI deployments
  • check --deploy warns when CONN_MAX_AGE is 0