¿Cómo utilizar mejor la agrupación de conexiones en SQLAlchemy para la agrupación de nivel de transacción PgBouncer?

15

Usando SQLAlchemy para consultar una base de datos PostgreSQL detrás de PgBouncer, usando la agrupación a nivel de transacción.

¿Cuál es el mejor patrón para este tipo de configuración? ¿Debería tener un motor por proceso, usando un ConnectionPool, o debería crear un motor por solicitud, y usar NullPoolpara cada uno de ellos? ¿Hay un patrón completamente diferente que debería estar usando?

¡Muchas gracias! Avíseme si necesita más información y lo actualizaré lo antes posible.

Juan Carlos Coto
fuente

Respuestas:

9

con PGBouncer, probablemente quieras seguir con NullPool. En ese caso, es posible que pueda compartir un solo motor entre subprocesos, ya que no se realizarán conexiones de socket sobre el límite del subproceso. Pero no puede compartir nada que se refiera a un objeto Connection, como una sesión con una transacción activa, sobre este límite. Sin embargo, definitivamente no querrá hacer "motor por solicitud", un motor es un objeto costoso que acumula mucha información sobre una URL de base de datos particular la primera vez que lo ve.

zzzeek
fuente
4

Establecer el nombre de la aplicación

Si espera ejecutar muchos procesos, necesita saber desde dónde se conectan. PGBouncer lo hará invisible para pg_stat_activity. Resuelva esto configurando cuidadosamente application_namela información que necesitará:

# Sets the application name for this connection in the form of
#   application-name:user@host
prog = os.path.basename(sys.argv[0]) or 'desjob'
username = pwd.getpwuid (os.getuid ()).pw_name
hostname = socket.gethostname().split(".")[0
args.setdefault('connect_args', {'application_name': "%s:%s@%s" %
    (prog, username, hostname)})
args.setdefault('isolation_level', "AUTOCOMMIT")
engine = create_engine(url, **args)

Prefiero Sesiones

Use Sesiones ya que las solicitudes de un objeto Engine pueden generar y mantener múltiples conexiones. Conectarse a Postgres no es muy costoso, con PGBouncer lo es aún menos. Siempre usaría NullPoolpara que las únicas conexiones que verá en Postgres sean las conexiones que realmente se están utilizando.

from sqlalchemy.pool import Pool, NullPool
engine = create_engine(uri, poolclass=NullPool)

Eliminar transacciones inactivas

Si su intención es usar PGBouncer para escalar, entonces es imprescindible que evite dejar las transacciones abiertas. Para ello es necesario activar autocommit el . Esto no es simple con SQLAlchemy ... hay tres lugares donde se puede configurar algo llamado "autocommit":

psycopg2 autocommit

conn = psycopg2.connect(uri)
conn.autocommit = True

Se presume que no es seguro porque SQLAlchemy necesita saber qué sucede debajo.

Compromiso automático de sesión

Session = sessionmaker(bind=engine, autocommit=True)
session = Session()

Esto requiere una entrega cuidadosa y explícita:

session.begin()
session.execute(...)
session.rollback()

Llamando a la función y la entrega excepción es extremadamente difícil debido begin()y commit()no pueden anidarse:

def A():
  session.begin()
  ...
  session.rollback()

def B():
  session.begin()
  try:
      A() # error, already open

En este modo, autocommitparece que psycopg2 es False(el valor predeterminado)

Compromiso automático del motor

Establecer el modo de aislamiento del motor en "AUTOCOMMIT"cuando se crea el motor establece un nuevo comportamiento predeterminado que puede no requerir cambios en el código existente.

engine = create_engine(uri, isolation_level="AUTOCOMMIT")

En este modo, psycopg2 autocommitparece serTrue

El principal problema aquí es que la única forma de garantizar que un bloque de código se envuelva en una transacción es emitir las declaraciones manualmente:

session.execute("BEGIN")
#...
session.execute("COMMIT")
eradman
fuente