Como usar melhor o pool de conexões no SQLAlchemy para pool no nível de transação do PgBouncer?

15

Usando SQLAlchemy para consultar um banco de dados PostgreSQL por trás do PgBouncer, usando pool no nível da transação.

Qual é o melhor padrão a ser usado para esse tipo de configuração? Devo ter um mecanismo por processo usando um ConnectionPoolou devo criar um mecanismo por solicitação e usar NullPoolpara cada um deles? Existe um padrão diferente que eu deveria estar usando?

Muito obrigado! Deixe-me saber se mais informações são necessárias e eu atualizo o mais rápido possível.

Juan Carlos Coto
fonte

Respostas:

9

com o PGBouncer, você provavelmente preferiria ficar com o NullPool. Nesse caso, você poderá compartilhar um único mecanismo entre os subprocessos, pois nenhuma conexão de soquete será transportada pelo limite do subprocesso. Mas você não pode compartilhar nada referente a um objeto Connection, como uma Sessão com uma transação ativa, além desse limite. Você definitivamente não gostaria de fazer "mecanismo por solicitação", porém, um mecanismo é um objeto caro que acumula muitas informações sobre um URL de banco de dados específico na primeira vez que o vê.

zzzeek
fonte
4

Defina o nome do aplicativo

Se você espera executar muitos processos, precisa saber de onde eles estão se conectando. O PGBouncer tornará isso invisível para pg_stat_activity. Resolva isso configurando cuidadosamente application_nameas informações necessárias:

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

Preferir sessões

Use Sessões, pois as solicitações de um objeto Engine podem gerar e manter várias conexões. Conectar-se ao Postgres não é muito caro, com o PGBouncer é ainda menos. Eu sempre usaria NullPoolpara que as únicas conexões que você verá no Postgres sejam as conexões que realmente estão sendo usadas.

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

Eliminar transações inativas

Se sua intenção é usar o PGBouncer para escalar, é essencial que você evite deixar as transações em aberto. Para fazer isso você precisa se transformar autocommit em . Isso não é simples com o SQLAlchemy ... há três lugares em que algo chamado "confirmação automática" pode ser definido:

confirmação automática de psycopg2

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

Presume-se inseguro, porque o SQLAlchemy precisa saber o que está acontecendo por baixo.

Confirmação automática de sessão

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

Isso requer uma entrega cuidadosa e explícita:

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

Função de chamada e entregar exceção é extremamente difícil porque begin()e commit()não pode ser aninhada:

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

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

Nesse modo, o psycopg2 autocommitparece ser False(o padrão)

Confirmação automática do motor

Definir o modo de isolamento do mecanismo como "AUTOCOMMIT"ao criar o mecanismo estabelece um novo comportamento padrão que pode não exigir alterações no código existente.

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

Nesse modo, o psycopg2 autocommitparece estarTrue

O principal problema aqui é que a única maneira de garantir que um bloco de código seja envolvido em uma transação é emitir as instruções manualmente:

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