Рефакторинг API-слоя, операций и БД
This commit is contained in:
+46
-11
@@ -1,26 +1,41 @@
|
||||
"""
|
||||
Connection pool для PostgreSQL (psycopg2).
|
||||
Connection pool для PostgreSQL (psycopg2) — lazy-init в каждом gunicorn-воркере.
|
||||
|
||||
Lazy-init: пул создаётся при первом обращении к БД в каждом gunicorn-воркере.
|
||||
Почему lazy-init: gunicorn форкает воркеры, и если создать пул до fork,
|
||||
все воркеры будут делить ОДНО соединение → гонки и падения.
|
||||
Поэтому _pool создаётся при ПЕРВОМ обращении к БД в каждом воркере отдельно.
|
||||
|
||||
Переменные окружения (КАЖДАЯ отдельно):
|
||||
DB_HOST — хост
|
||||
DB_PORT — порт (5432)
|
||||
DB_NAME — имя БД
|
||||
Переменные окружения (КАЖДАЯ отдельно, не DSN-строкой):
|
||||
DB_HOST — хост PostgreSQL
|
||||
DB_PORT — порт (по умолчанию 5432)
|
||||
DB_NAME — имя базы данных
|
||||
DB_USER — пользователь
|
||||
DB_PASSWORD — пароль
|
||||
DB_SSLMODE — sslmode (require)
|
||||
DB_SSLMODE — sslmode (по умолчанию "require")
|
||||
|
||||
ThreadedConnectionPool(1, 5):
|
||||
- Минимум 1 соединение (всегда готово)
|
||||
- Максимум 5 одновременных соединений на воркер
|
||||
- При 2 воркерах gunicorn: макс. 10 соединений к БД всего
|
||||
"""
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2 import pool
|
||||
|
||||
# _pool — по одному на gunicorn-воркер (создаётся при первом get_conn)
|
||||
_pool = None
|
||||
# _initialized — схема уже применена в этом воркере
|
||||
_initialized = False
|
||||
|
||||
|
||||
def _dsn():
|
||||
"""Построить DSN-строку из отдельных переменных окружения.
|
||||
|
||||
DSN (Data Source Name) — это строка подключения для psycopg2:
|
||||
"host=... port=... dbname=... user=... password=... sslmode=..."
|
||||
|
||||
Каждое поле отдельной переменной — безопаснее чем один DATABASE_URL."""
|
||||
return (
|
||||
f"host={os.getenv('DB_HOST')} "
|
||||
f"port={os.getenv('DB_PORT', '5432')} "
|
||||
@@ -32,7 +47,10 @@ def _dsn():
|
||||
|
||||
|
||||
def _ensure_schema():
|
||||
"""Инициализировать схему при первом обращении к БД."""
|
||||
"""Инициализировать схему БД при первом обращении в этом воркере.
|
||||
|
||||
Вызывает init_db() который делает CREATE IF NOT EXISTS — идемпотентно.
|
||||
_initialized — глобальный флаг чтобы не дёргать init_db при каждом запросе."""
|
||||
global _initialized
|
||||
if _initialized:
|
||||
return
|
||||
@@ -41,25 +59,42 @@ def _ensure_schema():
|
||||
init_db()
|
||||
_initialized = True
|
||||
except Exception:
|
||||
pass
|
||||
pass # без БД приложение работает (без истории)
|
||||
|
||||
|
||||
def get_pool():
|
||||
"""Получить connection pool (создать при первом вызове).
|
||||
|
||||
ThreadedConnectionPool — каждый поток получает своё соединение.
|
||||
Для gunicorn с sync-воркерами (не threads) это эквивалентно SimpleConnectionPool.
|
||||
|
||||
Returns:
|
||||
ThreadedConnectionPool или None если DB_USER не задан."""
|
||||
global _pool
|
||||
if _pool is None:
|
||||
if not os.getenv("DB_USER"):
|
||||
return None
|
||||
return None # нет переменных БД — работаем без истории
|
||||
_pool = pool.ThreadedConnectionPool(1, 5, _dsn())
|
||||
_ensure_schema()
|
||||
_ensure_schema() # при первом соединении применяем схему
|
||||
return _pool
|
||||
|
||||
|
||||
def get_conn():
|
||||
"""Взять соединение из пула.
|
||||
|
||||
Вызывается перед КАЖДЫМ запросом к БД.
|
||||
Возвращает None если БД не настроена.
|
||||
|
||||
ВАЖНО: после использования ОБЯЗАТЕЛЬНО вернуть через put_conn()."""
|
||||
p = get_pool()
|
||||
return p.getconn() if p else None
|
||||
|
||||
|
||||
def put_conn(conn):
|
||||
"""Вернуть соединение в пул.
|
||||
|
||||
ВСЕГДА вызывается в finally-блоке после get_conn.
|
||||
Если не вернуть — пул исчерпается и приложение встанет."""
|
||||
p = get_pool()
|
||||
if p and conn:
|
||||
p.putconn(conn)
|
||||
|
||||
Reference in New Issue
Block a user