Comment utiliser au mieux le regroupement de connexions dans SQLAlchemy pour le regroupement au niveau des transactions PgBouncer?

15

Utilisation de SQLAlchemy pour interroger une base de données PostgreSQL derrière PgBouncer, en utilisant le regroupement au niveau des transactions.

Quel est le meilleur schéma à utiliser pour ce type de configuration? Dois-je avoir un moteur par processus, en utilisant un ConnectionPool, ou dois-je créer un moteur par demande et l'utiliser NullPoolpour chacun d'eux? Existe-t-il un modèle différent que je devrais utiliser?

Merci beaucoup! Faites-moi savoir si plus d'informations sont nécessaires et je mettrai à jour dès que possible.

Juan Carlos Coto
la source

Réponses:

9

avec PGBouncer, vous voudrez probablement rester avec NullPool. Dans ce cas, vous pourrez peut-être partager un seul moteur entre des sous-processus car aucune connexion de socket ne sera transportée sur la limite du sous-processus. Mais vous ne pouvez pas partager quoi que ce soit faisant référence à un objet Connection, comme une session avec une transaction active, sur cette limite. Vous ne voudriez certainement pas faire de "moteur par demande", un moteur est un objet coûteux qui accumule beaucoup d'informations sur une URL de base de données particulière la première fois qu'il la voit.

zzzeek
la source
4

Définissez le nom de l'application

Si vous prévoyez d'exécuter de nombreux processus, vous devez savoir d'où ils se connectent. PGBouncer rendra cela invisible pg_stat_activity. Résolvez cela en définissant soigneusement le application_nameavec les informations dont vous aurez besoin:

# 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)

Préférer les sessions

Utilisez Sessions, car les demandes d'un objet Engine peuvent apparaître et s'accrocher à plusieurs connexions. Se connecter à Postgres n'est pas très cher, avec PGBouncer c'est encore moins le cas. J'utiliserais toujours NullPoolpour que les seules connexions que vous verrez dans Postgres soient les connexions qui sont réellement utilisées.

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

Éliminer les transactions inactives

Si votre intention est d'utiliser PGBouncer pour évoluer, il est impératif d'éviter de laisser les transactions bloquées. Pour ce faire , vous devez activer autocommit le . Ce n'est pas simple avec SQLAlchemy ... il y a trois endroits où quelque chose appelé "autocommit" peut être défini:

autocommit psycopg2

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

Présumé dangereux dangereux car SQLAlchemy doit savoir ce qui se passe en dessous.

Autocommit de session

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

Cela nécessite une remise soigneuse et explicite:

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

L'appel de fonction et la remise d'exception sont extrêmement difficiles car begin()et commit()ne peuvent pas être imbriqués:

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

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

Dans ce mode, psycopg2 autocommitsemble être False(par défaut)

Autocommit du moteur

La définition du mode d'isolation du moteur "AUTOCOMMIT"lors de la création du moteur établit un nouveau comportement par défaut qui peut ne pas nécessiter de modifications du code existant.

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

Dans ce mode, psycopg2 autocommitsemble êtreTrue

Le problème majeur ici est que la seule façon de garantir qu'un bloc de code est encapsulé dans une transaction est d'émettre les instructions manuellement:

session.execute("BEGIN")
#...
session.execute("COMMIT")
eradman
la source