Комментарий пользователя
Здравствуйте! У нас падает БД при нагрузке: слишком много открытых соединений и утечки. Как управлять подключениями и транзакциями, может есть какие-то советы?
Ответ специалиста
Здравствуйте, Илья. Основная ошибка — открывать соединение на каждый запрос и не использовать пул. Для синхронного кода с PostgreSQL используйте psycopg2 + psycopg2.pool.ThreadedConnectionPool или ставьте приложение за connection pooler (PgBouncer).
В async — asyncpg с пулом. И всегда используйте контекстные менеджеры для транзакций — это гарантирует откат и закрытие соединения при ошибках.
Вот пример с psycopg2 и пулом:
from psycopg2.pool import ThreadedConnectionPool
DSN = "postgresql://user:pass@localhost/dbname"
pool = ThreadedConnectionPool(minconn=1, maxconn=20, dsn=DSN)
def do_work():
conn = pool.getconn()
try:
with conn.cursor() as cur:
cur.execute("UPDATE users SET balance = %s WHERE id = %s", (100, 123))
conn.commit()
except Exception:
conn.rollback()
finally:
pool.putconn(conn)
Пример с asyncpg:
import asyncpg
import asyncio
async def main():
pool = await asyncpg.create_pool(dsn=DSN, min_size=2, max_size=50)
async with pool.acquire() as conn:
async with conn.transaction():
await conn.execute("INSERT ...")
await pool.close()
asyncio.run(main())
На последок, посоветую несколько правил:
- не держите транзакции открытыми долго (это блокирует vacuum / репликацию);
- используйте короткие транзакции и batch-операции;
- мониторьте активные соединения в БД;
- используйте PgBouncer в transaction-mode для высокого QPS;
- контролируйте число соединений через лимиты пула: при большом числе воркеров легко упереться в max_connections, поэтому тяжелые задачи лучше выносить в отдельные процессы.
Если материал пригодился — заглядывайте в Академию Selectel чаще. Делимся проверенными практиками по инфраструктуре и не только.