Django PostgreSQL backend that releases pooled connections As Soon As Possible
Project description
django-pg-real-pool
A Django PostgreSQL database backend that releases pooled connections As Soon As Possible.
TL;DR
A plain connection pool hands a connection to a thread and keeps it checked out for the whole request/context — even if a single query was made early and the rest of the work never touches the database. Under load, threads fight over a small set of connections that mostly sit idle while still "in use".
django-pg-real-pool returns the connection to the pool right after each query when you
are not inside a transaction, and holds it only for the duration of an atomic() block (or
a manually controlled transaction). The result: far fewer connections are needed to serve the
same concurrency.
# settings.py
DATABASES = {
"default": {
"ENGINE": "django_pg_real_pool", # native psycopg pool (Django 5.1+)
"NAME": "mydb", "USER": "...", "PASSWORD": "...", "HOST": "...", "PORT": "5432",
"CONN_MAX_AGE": 0, # pooling forbids persistent connections
"OPTIONS": {"pool": {"min_size": 2, "max_size": 10}},
}
}
That's it. Your code uses the ORM exactly as before; connections are just held for less time.
Motivation
When you run a multi-threaded Django app (Gunicorn threads, dramatiq workers, Celery
prefork+threads, ASGI, …) every thread normally keeps its own DB connection for the lifetime
of the request. With a pool in front, the pool still won't reclaim a connection until the
caller releases it — which Django does only at the end of the request. So a request that does
one quick SELECT and then spends 200ms calling an external API keeps a database connection
pinned that entire time.
django-pg-real-pool changes the release point: the connection goes back to the pool after
each cursor finishes, except inside transactions where it must stay (correctness first). The
connection is transparently re-acquired the next time the ORM needs it.
How it works
The package is one small mixin (ConnectionReleaseMixin) layered on top of a pooled
PostgreSQL DatabaseWrapper:
- After a cursor created outside an
atomic()block is closed, the connection is returned to the pool (AutoConnectionReleaseCursor). commit()/rollback()return the connection to the pool when the transaction ends.- Inside an
atomic()block the connection is held until the block exits — so transactions, savepoints, server-side cursors andon_commithooks all behave exactly as Django expects.
Because both Django's native pool and the third-party pool return the connection to the pool on
close(), the same mixin works on top of either.
Pool backends
| Engine | Pool | Requires | Install |
|---|---|---|---|
django_pg_real_pool (default) |
Django native (psycopg_pool) |
Django ≥ 5.1, psycopg 3 | pip install django-pg-real-pool |
django_pg_real_pool.dj_db_conn_pool |
django-db-connection-pool (SQLAlchemy QueuePool) |
any supported Django | pip install 'django-pg-real-pool[dj-db-conn-pool]' |
Native pool (recommended, default)
Uses Django's built-in connection pool (added in Django 5.1), which is backed by
psycopg_pool.ConnectionPool. The OPTIONS["pool"] dict is passed straight through as
ConnectionPool keyword arguments:
"OPTIONS": {
"pool": {
"min_size": 2, # connections kept warm
"max_size": 10, # hard ceiling
"timeout": 10, # seconds to wait for a free connection
"max_lifetime": 3600, # recycle age
"max_idle": 600, # close idle connections above min_size
},
}
Requirements / rules:
- psycopg 3 with the pool extra:
pip install 'psycopg[pool]'(a dependency of this package; you may preferpsycopg[binary,pool]). psycopg2 does not support the native pool. CONN_MAX_AGEmust be0— pooling and Django persistent connections are mutually exclusive (the pool manages connection lifetime viamax_lifetime/max_idle).- Use
"pool": Trueto accept allpsycopg_pooldefaults.
Third-party pool (optional)
If you cannot use psycopg 3 / Django 5.1, or you already standardised on
django-db-connection-pool, install the extra and point ENGINE at the sub-backend:
"ENGINE": "django_pg_real_pool.dj_db_conn_pool",
"POOL_OPTIONS": {"POOL_SIZE": 3, "MAX_OVERFLOW": 7, "RECYCLE": 86400},
POOL_OPTIONS keys (uppercase): POOL_SIZE, MAX_OVERFLOW, RECYCLE, TIMEOUT,
PRE_PING, ECHO. Effective defaults when omitted: pool_size=10, max_overflow=10,
recycle=900, timeout=30, pre_ping=True.
When (not) to use it
Good fit:
- Multi-threaded apps where DB work is bursty and interleaved with non-DB work.
- High concurrency against a database (or PgBouncer) with a limited connection budget.
Think twice:
- If most of your request time is spent in the database, the win is small.
- The trade-off is more
getconn/putconnchurn and re-acquisition between queries. For the native pool this is cheap; measure if you are latency-sensitive.
Compatibility
- Python: 3.10 – 3.14
- Django: 5.1, 5.2 LTS, 6.0 (native pool). The third-party backend also works on older Django.
- PostgreSQL: any supported by your Django/psycopg version.
Note: Django 5.1 is EOL upstream; it is supported here because it introduced the native pool. Prefer 5.2 LTS or newer for production.
Install
pip install django-pg-real-pool
# or, for the third-party SQLAlchemy-based pool:
pip install 'django-pg-real-pool[dj-db-conn-pool]'
Usage
Set the ENGINE in settings.py — no code changes are needed beyond that.
# Native pool (default, Django 5.1+):
DATABASES = {
"default": {
"ENGINE": "django_pg_real_pool",
"NAME": "mydb", "USER": "...", "PASSWORD": "...", "HOST": "...", "PORT": "5432",
"CONN_MAX_AGE": 0,
"OPTIONS": {"pool": {"min_size": 2, "max_size": 10}},
}
}
# Third-party pool (optional extra):
DATABASES = {
"default": {
"ENGINE": "django_pg_real_pool.dj_db_conn_pool",
"NAME": "mydb", "USER": "...", "PASSWORD": "...", "HOST": "...", "PORT": "5432",
"POOL_OPTIONS": {"POOL_SIZE": 3, "MAX_OVERFLOW": 7, "RECYCLE": 86400},
}
}
Runnable snippets live in examples/. Full documentation — pool options,
compatibility matrix, internals and FAQ — is in docs/
(rendered at https://spumer.github.io/django-pg-real-pool/).
Development
uv sync # create venv and install dev deps
docker compose up -d # start PostgreSQL on host port 5433
PGPORT=5433 uv run pytest # run the suite against the native pool
DJANGO_PG_REAL_POOL_BACKEND=dj_db_conn_pool PGPORT=5433 uv run pytest # third-party backend
uv run tox # full Django/Python matrix
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_pg_real_pool-1.0.0.tar.gz.
File metadata
- Download URL: django_pg_real_pool-1.0.0.tar.gz
- Upload date:
- Size: 9.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3badecc1a3a31593c9feaccc97924fba7687b4ed95404a52d58ff82d5b8f4b56
|
|
| MD5 |
d0ef480f3c69136c30a4b5d1b977b6bc
|
|
| BLAKE2b-256 |
d1384926ed5066036b786338f5c968967d7b1b7bb02d7801ddd6e3cfaa025e7b
|
File details
Details for the file django_pg_real_pool-1.0.0-py3-none-any.whl.
File metadata
- Download URL: django_pg_real_pool-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e846da4fd6b3992fb1c12fd3eb30627a51070b6780aeb5983f85ebb79756922e
|
|
| MD5 |
1fc35a35260962dc6afbf363c2f54a96
|
|
| BLAKE2b-256 |
46ed5a8dff81e13d5dbf2cac482e3feed8cc8a7a3a800ba0da1b86a08a529585
|