Transações
A versão 4.0 do servidor MongoDB introduz transações de vários documentos. (As atualizações de vários campos em um único documento são atômicas em todas as versões do MongoDB.) Versão 2.6.0 do driver Ruby adiciona suporte para transações.
Observação
Operações paralelas não suportadas
O driver Ruby não suporta a execução de operações paralelas em uma única transação.
Usando transação
Para iniciar uma transação, o aplicação deve ter umasessão .
A maneira recomendada de usar transação é utilizar o método auxiliar do with_transaction
:
session = client.start_session session.with_transaction do collection.insert_one({hello: 'world'}, session: session) end
O auxiliar do with_transaction
faz o seguinte:
Ele inicia uma transação antes de chamar o bloco fornecido e confirma a transação quando o bloco é concluído.
Se qualquer uma das operações no bloco, ou a operação de confirmação, resultar em um erro de transação transitório, o bloco e/ou o commit serão executados novamente.
O bloqueio deve ser idempotente, pois pode ser chamado várias vezes.
O bloco pode confirmar ou abortar explicitamente a transação, ligando para commit_transaction
ou abort_transaction
; nesse caso, with_transaction
não tentará cometer ou abortar (mas ainda pode tentar novamente o bloco em caso de erros de transação transitórios propagados para fora do bloco).
O bloqueio também será repetido se o resultado do commit da transação for desconhecido. Isso pode acontecer, por exemplo, se o cluster passar por uma eleição durante o commit. Nesse caso, quando o bloqueio fosse repetido, o servidor principal da topologia provavelmente teria mudado.
Atualmente, with_transaction
parará de tentar novamente o bloqueio e a confirmação quando 120 segundos se passarem desde o início de sua execução. Este tempo não é configurável e pode mudar em uma versão futura do driver. Observe que isso não garante que o tempo de execução geral de with_transactions
seja de 120 segundos ou menos - apenas que, depois de decorridos 120 segundos do tempo do relógio de parede, outras tentativas de repetição não serão iniciadas.
Uma API de baixo nível também estará disponível se desejar mais controle sobre a transação.
with_transaction
usa as mesmas opções que start_transaction
, que são referência de leitura, referência de escrita e preferência de leitura:
session = client.start_session session.with_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} ) do collection.insert_one({hello: 'world'}, session: session) end
Manipulação de erros dentro do with_transaction
bloco
Se um comando dentro do bloco with_transaction
falhar, ele poderá fazer com que a transação no servidor seja cancelada. Essa situação normalmente é tratada de forma transparente pelo driver. No entanto, se o aplicativo detectar esse erro e não o aumentar novamente, o driver não poderá determinar se a transação foi cancelada ou não. Depois, o driver tentará novamente o bloqueio indefinidamente.
Para evitar essa situação, o aplicativo não deve lidar silenciosamente com erros dentro de with_transaction
blocos. Se o aplicativo precisar lidar com erros dentro do bloco, ele deverá gerar os erros novamente.
session.with_transaction do collection.insert_one({hello: 'world'}, session: session) rescue Mongo::Error::OperationFailure => e # Do something in response to the error raise e end
Se os aplicativos precisarem lidar com erros de forma personalizada, eles devem usar a API de baixo nível.
API de baixo nível
Uma transação pode ser iniciada chamando o método start_transaction
em uma sessão:
session = client.start_session session.start_transaction
Também é possível especificar a referência de leitura, referência de escrita e preferência de leitura ao iniciar uma transação:
session = client.start_session session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary})
Para persistir as alterações feitas em uma transação no banco de dados, a transação deve ser explicitamente confirmada. Se uma sessão terminar com uma transação aberta, a transação será cancelada. Uma transação também pode ser abortada explicitamente.
Para confirmar ou cancelar uma transação, chame commit_transaction
ou abort_transaction
na instância da sessão:
session.commit_transaction session.abort_transaction
Observação: uma transação pendente pode conter travas em vários objetos no servidor, como o banco de dados. Por exemplo, a chamada de queda no trecho a seguir ficará pendurada por transactionLifetimeLimitSeconds segundos (padrão 60) até que o servidor expire e cancele a transação:
c1 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) session = c1.start_session c1['foo'].insert_one(test: 1) session.start_transaction c1['foo'].insert_one({test: 2}, session: session) c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # hangs c2.database.drop
Since transactions are associated with server-side sessions, closing the client does not abort a transaction that this client initiated - the application must either call abort_transaction
or wait for the transaction to time out on the server side. Além de confirmar ou cancelar a transação, um aplicativo também pode encerrar a sessão, o que cancelará uma transação nessa sessão, se houver uma em andamento:
session.end_session c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # ok c2.database.drop
Tratamento de erros
Se um comando dentro da transação falhar, a transação poderá ser cancelada no servidor. Os erros que abortam transações não têm TransientTransactionError
em seus rótulos de erro. Uma tentativa de confirmar essa transação será rejeitada com NoSuchTransaction
erro.
Tentando novamente os commits
A confirmação da transação pode ser repetida se falhar. Aqui está o código Ruby para fazer isso:
begin session.commit_transaction rescue Mongo::Error => e if e.label?('UnknownTransactionCommitResult') retry else raise end end
Aninhamento de transação
O MongoDB não oferece suporte a aninhamento de transação. Tentar ligar para start_transaction
ou with_transaction
quando uma transação já está em andamento resultará em um erro.