Intach-Pune / database.py
Meshyboi's picture
Update database.py
b87252a verified
Raw History Blame Contribute Delete
2.67 kB
import time
from typing import Iterator
import psycopg2
import psycopg2.extras
from psycopg2.pool import PoolError, ThreadedConnectionPool
from config import settings
# Without this, uuid/uuid[] columns (e.g. chat_messages.referenced_site_ids)
# come back from fetches as raw strings like '{}' instead of Python lists.
psycopg2.extras.register_uuid()
# minconn=0 previously meant psycopg2's SimpleConnectionPool.putconn()
# always closed the returned connection instead of keeping it for reuse
# (its internal "len(pool) < minconn" check was 0 < 0, always false) - so
# every single request paid a full fresh-connection handshake (~1.5s to
# the remote DB), regardless of any query-level caching. minconn=2 lets
# putconn() actually retain idle connections.
pool = ThreadedConnectionPool(
minconn=2,
maxconn=24,
dsn=settings.database_url,
connect_timeout=10,
keepalives=1,
keepalives_idle=30,
keepalives_interval=10,
keepalives_count=3,
)
_ACQUIRE_TIMEOUT = 5.0 # seconds a request will wait for a free connection
_ACQUIRE_INTERVAL = 0.05
def _getconn_blocking() -> psycopg2.extensions.connection:
"""ThreadedConnectionPool.getconn() raises immediately when maxconn is
reached. A dashboard load fires ~6 requests at once and cold Neon holds
each connection for seconds, briefly draining the pool - a short wait
lets that burst queue through instead of 500ing. A genuine connection
leak still surfaces as PoolError once the timeout elapses."""
deadline = time.monotonic() + _ACQUIRE_TIMEOUT
while True:
try:
return pool.getconn()
except PoolError:
if time.monotonic() >= deadline:
raise
time.sleep(_ACQUIRE_INTERVAL)
def get_db() -> Iterator[psycopg2.extensions.connection]:
conn = _getconn_blocking()
try:
yield conn
conn.commit()
except (psycopg2.OperationalError, psycopg2.InterfaceError):
# The connection itself is dead - e.g. Neon's pooled endpoint
# silently drops idle connections after some minutes, and
# SimpleConnectionPool has no health check on getconn(). Closing
# it here (instead of falling through to putconn() below) stops
# the same broken connection from being recycled back into the
# pool and handed to the next request, which otherwise turns one
# dropped connection into every subsequent request 500ing until
# the whole process is restarted.
conn.close()
raise
except Exception:
conn.rollback()
raise
finally:
if not conn.closed:
pool.putconn(conn)