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.
Optional pool metrics
pip install 'django-pg-real-pool[telemetry]'
DJANGO_PG_REAL_POOL_TELEMETRY = {"ENABLED": True}
Exports Prometheus gauges for used/idle connections, current size and maximum capacity,
plus checkout/error/timeout counters (and native pool queue depth). Metric names and labels
are configurable. server_address identifies the configured endpoint, including the full
multi-host list. See telemetry configuration and multiprocess setup.
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
Metadata
Release files for django-pg-real-pool 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| django_pg_real_pool-1.1.0.tar.gz | 15.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_pg_real_pool-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.6 kB
Release files / django_pg_real_pool-1.1.0.tar.gz
| Download URL | django_pg_real_pool-1.1.0.tar.gz |
|---|---|
| Size | 15.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
31acc321e185620cee9d340526023b40e7e0e99c635b17c7674b5174d474faaa
|
|
BLAKE2b-256 checksum How to use checksums |
137a40dd3906d6eba0293817d0a9fab5e013f4144149822b2e68ed905330e77e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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}
|
Release files / django_pg_real_pool-1.1.0-py3-none-any.whl
| Download URL | django_pg_real_pool-1.1.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e0862c745fc1f2ba9544fea33cdb8898f1ea14e9712259ed4481a824d076bb27
|
|
BLAKE2b-256 checksum How to use checksums |
58973802cc33df155e12019e42950e8feb43bc1d6a1ab7176b0ca788b3e8dfef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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}
|
Security for django-pg-real-pool 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.