пул: keepalive, проверка живости, min=2 max=5, reconnect, v1.22

This commit is contained in:
2026-06-17 18:50:33 +04:00
parent 5c0fb98955
commit 6585598fca
3 changed files with 40 additions and 32 deletions
+4 -2
View File
@@ -152,6 +152,7 @@ class ContractsApp:
mime = mimeutil.guess_mime(filename) or "application/octet-stream"
cid = data.get("cid")
conn = None
if cid:
contract_id = cid
else:
@@ -165,10 +166,11 @@ class ContractsApp:
)
contract_id = cur.fetchone()[0]
conn.commit()
doc_id = _save_file_to_db(filename, file_bytes, mime, str(contract_id), conn=conn)
if conn:
cur.close()
db.put_conn(conn)
doc_id = _save_file_to_db(filename, file_bytes, mime, str(contract_id))
return jsonify({"contract_id": str(contract_id), "doc_id": str(doc_id) if doc_id else None})
# ── Чанковая загрузка ──────────────────────────────────────
+35 -29
View File
@@ -1,39 +1,41 @@
"""
db.py — Транспортный слой к базе данных (с пулом соединений).
Пул psycopg2.pool.ThreadedConnectionPool (min=1, max=3).
Все query/execute забирают соединение из пула и возвращают обратно.
db.py — Пул соединений с keepalive, проверкой живости, переподключением.
"""
import os
import logging
from psycopg2 import pool as _pgpool
from psycopg2 import OperationalError, InterfaceError
import psycopg2
logger = logging.getLogger(__name__)
_pool = None
def _get_pool():
"""Ленивая инициализация пула."""
global _pool
if _pool is None:
_pool = _pgpool.ThreadedConnectionPool(
minconn=1,
maxconn=3,
minconn=2,
maxconn=5,
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT", 5432),
dbname=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASS"),
sslmode=os.getenv("DB_SSLMODE", "disable"),
connect_timeout=10,
keepalives=1,
keepalives_idle=30,
keepalives_interval=10,
keepalives_count=3,
)
return _pool
def _pg_connect(dbname):
"""
Сырое подключение к ЛЮБОЙ базе (для /test createdb).
НЕ из пула — для создания БД нужна отдельная сессия.
"""
"""Сырое подключение к ЛЮБОЙ базе (для /test createdb)."""
return psycopg2.connect(
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT", 5432),
@@ -45,14 +47,28 @@ def _pg_connect(dbname):
def connect():
"""
Подключение к ЦЕЛЕВОЙ базе из пула.
Возвращает (connection, None) или (None, error).
"""
try:
return _get_pool().getconn(), None
except Exception as e:
return None, str(e)
"""Подключение из пула с проверкой живости."""
pool = _get_pool()
for attempt in range(3):
try:
conn = pool.getconn()
# Проверить живость
cur = conn.cursor()
cur.execute("SELECT 1")
cur.close()
return conn, None
except (OperationalError, InterfaceError) as e:
if conn:
try: pool.putconn(conn, close=True)
except: pass
if attempt == 2:
return None, str(e)
except Exception as e:
if conn:
try: pool.putconn(conn)
except: pass
return None, str(e)
return None, "pool exhausted"
def put_conn(conn):
@@ -64,10 +80,6 @@ def put_conn(conn):
def query(sql_text, params=None):
"""
SELECT → (result, None) или (None, error).
result = {"columns": [...], "rows": [[...], ...]}
"""
conn, err = connect()
if err:
return None, f"connect: {err}"
@@ -85,9 +97,6 @@ def query(sql_text, params=None):
def execute(sql_text, params=None):
"""
INSERT/UPDATE/DELETE → (rowcount, None) или (None, error).
"""
conn, err = connect()
if err:
return None, f"connect: {err}"
@@ -107,9 +116,6 @@ def execute(sql_text, params=None):
def query_one(sql_text, params=None):
"""
SELECT одной строки → (row_dict, None) или (None, error).
"""
result, err = query(sql_text, params)
if err:
return None, err
+1 -1
View File
@@ -59,7 +59,7 @@
<body>
<div class="topbar">
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.21</span></span>
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.22</span></span>
</div>
<div class="content">