Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

Introdução às transações ACID multidocumento em Python

Joe Drumgoole10 min read • Published Feb 05, 2022 • Updated Sep 11, 2024
MongoDBPython
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty

Introdução

Logotipo do QuickStart Python
Transações multidocumento chegaram ao MongoDB 4.0 em 2018 junho. O MongoDB sempre foi transacional em torno de atualizações em um único documento. Agora, com transações ACID de vários documentos, podemos encapsular um conjunto de operações de banco de dados dentro de uma chamada de transação de início e confirmação. Isso garante que, mesmo com inserções e/ou atualizações ocorrendo em várias collections e/ou bancos de dados, a visualização externa dos dados atenda às restrições do ACID.
Para demonstrar transações na natureza, usamos um exemplo de aplicativo trivial que emula uma reserva de voo para um aplicativo de companhia aérea on-line. Nesta reserva simplificada precisamos realizar três operações:
  • Alocar um assento no seat_collection
  • Pague pelo assento no payment_collection
  • Atualizar a contagem de assentos alocados e vendas no audit_collection
Para este aplicativo, usaremos três collection separadas para esses documentos, conforme detalhado acima. O código em transactions_main.py atualiza essas collection em série, a menos que o --usetxns argument seja usado. Em seguida, envolvemos o conjunto completo de operações dentro de uma ACID transaction. O código em transactions_main.py é compilado diretamente usando o driver MongoDB Python (PyMongo 3.7.1).
O objetivo deste código é demonstrar aos desenvolvedores de Python como é fácil converter o código existente para transações, se necessário, ou portar sistemas antigos baseados em SQL.

Configurando seu ambiente

Os arquivos a seguir podem ser encontrados no repositório github associado, pymongo-transactions.
  • gitignore : padrão do Github .gitignore para Python.
  • LICENSE : 2 do Apache. Licença 0 (Github padrão).
  • Makefile : Makefile com alvos para operações padrão.
  • transaction_main.py : executa um conjunto de gravações com e sem transações. Execute python transactions_main.py -h para obter ajuda.
  • transactions_retry.py : o arquivo que contém as funções de repetição das transações.
  • watch_transactions.py : Use um MongoDB change stream para observar as collection à medida que elas mudam quando transactions_main.py estiver em execução.
  • kill_primary.py : inicia um conjunto de réplicas do MongoDB (na porta 7100) e elimina o primário regularmente. Isso é usado para emular uma eleição acontecendo no meio de uma transação.
  • featurecompatibility.py : verifique e/ou defina a compatibilidade de recursos para o banco de dados (ele precisa ser definido como "4.0" para transações).
Você pode clonar este repositório e trabalhar ao nosso lado durante esta publicação no blog (registre qualquer problema na aba Problemas no Github).
Presumimos que você tem Python 3.6 ou superior instalado corretamente e no seu caminho.
O Makefile descreve as operações necessárias para configurar o ambiente de teste.
Todos os programas neste exemplo usam um intervalo de portas que começa em 27100 para garantir que esse exemplo não entre em conflito com uma instalação MongoDB existente.

Preparação

Para configurar o ambiente, você pode executar as etapas a seguir manualmente. As pessoas que têm make podem acelerar a instalação usando o comandomake install .

Set a python virtualenv

Confira o documento para virtualenv.
1$ cd pymongo-transactions
2$ virtualenv -p python3 venv
3$ source venv/bin/activate

Instalar o Python MongoDB Driver pymongo

Instale a versão mais recente do driver PyMongo MongoDB (3.7.1 no momento da escrita).
1pip install --upgrade pymongo

Instalar mtools

mtools é uma coleção de scripts auxiliares para analisar, filtrar e visualizar arquivos de log do MongoDB (mongod, mongos). O mtools também inclui omlaunch, um utilitário para configurar rapidamente ambientes de teste complexos do MongoDB em uma máquina local. Para esta demonstração, usaremos apenas o programamilaunch.
1pip install mtools
O programamlaunch também requer o pacotepsutil.
1pip install psutil
O programamlaunch nos fornece um comando simples para iniciar um conjunto de réplicas do MongoDB, pois as transações só são suportadas em um conjunto de réplicas.
Inicie um conjunto de réplicas cujo nome seja txntest. Consulte a meta de criaçãomake init_serverpara obter detalhes:
1mlaunch init --port 27100 --replicaset --name "txntest"

Usando o Makefile para configuração

Há um Makefile com alvos para todas essas operações. Para aqueles que estão em plataformas sem acesso ao Make, deve ser fácil recortar e colar os comandos dos alvos e executá-los na linha de comando.
Executando o Makefile:
1$ cd pymongo-transactions
2$ make
Você precisará ter MongoDB 4.0 no seu caminho. Existem outras metas de conveniência para iniciar os programas de demonstração:
  • make notxns : inicie o cliente de transações sem usar transactions.
  • make usetxns : iniciar o cliente de transações com transações habilitadas.
  • make watch_seats : veja a mudança da coleção de assentos.
  • make watch_payments : observe a mudança na cobrança de pagamentos.

Executando o exemplo de transações

O exemplo de transações consiste em dois programas python.
  • transaction_main.py,
  • watch_transactions.py.

Executando transactions_main.py

1$ python transaction_main.py -h
2usage: transaction_main.py [-h] [--host HOST] [--usetxns] [--delay DELAY]
3 [--iterations ITERATIONS]
4 [--randdelay RANDDELAY RANDDELAY]
5
6optional arguments:
7 -h, --help show this help message and exit
8 --host HOST MongoDB URI [default: mongodb://localhost:27100,localh
9 ost:27101,localhost:27102/?replicaSet=txntest&retryWri
10 tes=true]
11 --usetxns Use transactions [default: False]
12 --delay DELAY Delay between two insertion events [default: 1.0]
13 --iterations ITERATIONS
14 Run N iterations. O means run forever
15 --randdelay RANDDELAY RANDDELAY
16 Create a delay set randomly between the two bounds
17 [default: None]
Você pode optar por usar --delay ou --randdelay. Se você usar ambos, o parâmetro --delay terá precedência. O parâmetro--randdelaycria um atraso aleatório entre um limite inferior e um limite superior que será adicionado entre cada evento de inserção.
O programa transactions_main.pysabe que deve usar o conjunto de réplicastxntest e o intervalo de portas padrão correto.
Para executar o programa sem transações, você pode executá-lo sem argumentos:
1$ python transaction_main.py
2using collection: SEATSDB.seats
3using collection: PAYMENTSDB.payments
4using collection: AUDITDB.audit
5Using a fixed delay of 1.0
6
71. Booking seat: '1A'
81. Sleeping: 1.000
91. Paying 330 for seat '1A'
102. Booking seat: '2A'
112. Sleeping: 1.000
122. Paying 450 for seat '2A'
133. Booking seat: '3A'
143. Sleeping: 1.000
153. Paying 490 for seat '3A'
164. Booking seat: '4A'
174. Sleeping: 1.000
O programa executa uma função chamada book_seat() que reserva um assento em um avião adicionando documentos a três coleções. Primeiro, ele adiciona a alocação de assentos ao seats_collection, depois adiciona um pagamento ao payments_collection, finalmente atualiza uma contagem de auditoria no audit_collection. (Este é um processo de reserva muito simplificado usado apenas para ilustração).
O padrão é executar o programa sem usar transações. Para usar transações, temos que adicionar o sinalizador de linha de comando --usetxns. Execute isto para testar se você está executando MongoDB 4.0 e que a featureCompatibilitycorreta está configurada (deve ser definida como 4.0). Se você instalar o MongoDB 4.0 em um diretório/dataexistente contendo 3.6 bancos de dados, então featureCompatibility será definido como 3.6 por padrão e as transações não estarão disponíveis.
Observação: se você receber o seguinte erro ao executar o python transaction_main.py --usetxns, isso significa que está selecionando uma versão mais antiga do pymongo (anterior a 3.7.x) para a qual não há suporte para transações multidocumento.
1Traceback (most recent call last):
2 File "transaction_main.py", line 175, in
3 total_delay = total_delay + run_transaction_with_retry( booking_functor, session)
4 File "/Users/jdrumgoole/GIT/pymongo-transactions/transaction_retry.py", line 52, in run_transaction_with_retry
5 with session.start_transaction():
6AttributeError: 'ClientSession' object has no attribute 'start_transaction'

Assistindo às transações

Para realmente ver o efeito das transações, precisamos observar o que está ocorrendo dentro das collection SEATSDB.seats e PAYMENTSDB.payments.
Podemos fazer isso com watch_transactions.py. Este roteiro usa MongoDB change stream para ver o que está acontecer dentro de uma collection em tempo real. Precisamos executar dois deles em paralelo, então é melhor alinhá-los lado a lado.
Aqui está o programawatch_transactions.py:
1$ python watch_transactions.py -h
2usage: watch_transactions.py [-h] [--host HOST] [--collection COLLECTION]
3
4optional arguments:
5 -h, --help show this help message and exit
6 --host HOST mongodb URI for connecting to server [default:
7 mongodb://localhost:27100/?replicaSet=txntest]
8 --collection COLLECTION
9 Watch [default:
10 PYTHON_TXNS_EXAMPLE.seats_collection]
Precisamos assistir a cada coleção para que em duas Windows de terminal separadas inicie o observador.
Janela 1:
1$ python watch_transactions.py --watch seats
2Watching: seats
3...
Janela 2:
1$ python watch_transactions.py --watch payments
2Watching: payments
3...

O que acontece quando você executa sem transações?

Vamos executar o código sem transações primeiro. Se você examinar o códigotransaction_main.py, verá uma função book_seats.
1def book_seat(seats, payments, audit, seat_no, delay_range, session=None):
2 '''
3 Run two inserts in sequence.
4 If session is not None we are in a transaction
5
6 :param seats: seats collection
7 :param payments: payments collection
8 :param seat_no: the number of the seat to be booked (defaults to row A)
9 :param delay_range: A tuple indicating a random delay between two ranges or a single float fixed delay
10 :param session: Session object required by a MongoDB transaction
11 :return: the delay_period for this transaction
12 '''
13 price = random.randrange(200, 500, 10)
14 if type(delay_range) == tuple:
15 delay_period = random.uniform(delay_range[0], delay_range[1])
16 else:
17 delay_period = delay_range
18
19 # Book Seat
20 seat_str = "{}A".format(seat_no)
21 print(count( i, "Booking seat: '{}'".format(seat_str)))
22 seats.insert_one({"flight_no" : "EI178",
23 "seat" : seat_str,
24 "date" : datetime.datetime.utcnow()},
25 session=session)
26 print(count( seat_no, "Sleeping: {:02.3f}".format(delay_period)))
27 #pay for seat
28 time.sleep(delay_period)
29 payments.insert_one({"flight_no" : "EI178",
30 "seat" : seat_str,
31 "date" : datetime.datetime.utcnow(),
32 "price" : price},
33 session=session)
34 audit.update_one({ "audit" : "seats"}, { "$inc" : { "count" : 1}}, upsert=True)
35 print(count(seat_no, "Paying {} for seat '{}'".format(price, seat_str)))
36
37 return delay_period
Este programa emula uma reserva de companhia aérea muito simplificada com um assento sendo alocado e depois pago. Estes são frequentemente separados por um período de tempo razoável (por exemplo, alocação de assentos versus validação externa de cartão de crédito e verificação antifraude) e emulamos isso inserindo um atraso. O padrão é 1 segundo.
Agora, com os dois scripts watch_transactions.pyem execução, paraseats_collection e payments_collection, podemos executar transactions_main.py da seguinte forma:
1$ python transaction_main.py
A primeira execução é sem transações habilitadas.
A janela inferior mostra transactions_main.py em execução. Na parte superior esquerda, estamos observando as inserções na coleção de assentos. Na parte superior direita, estamos observando as inserções na coleção de pagamentos.
assistindo sem transações
Podemos ver que a janela de pagamentos fica atrasada em relação à janela de assentos, pois os observadores só atualizam quando a inserção é concluída. Assim, os assentos vendidos não podem ser facilmente conciliados com os pagamentos correspondentes. Se após o terceiro assento ter sido reservado nós CTRL-C o programa, podemos ver que o programa sai antes de escrever o pagamento. Isso é refletido no Change Streams da collection de pagamentos, que mostra apenas os pagamentos para as assentos 1A e 2A, em comparação com as alocações de assentos para 1A, 2A e 3A.
Se quisermos que os pagamentos e assentos sejam instantaneamente reconciliáveis e consistentes, devemos executar as inserções em uma transação.

O que acontece quando você executa com transações?

Agora vamos executar o mesmo sistema com --usetxns habilitado.
1$ python transaction_main.py --usetxns
Executamos exatamente a mesma configuração, mas agora definimos --usetxns.
assistindo com transações
Observe agora como os change streams estão travados e são atualizados em paralelo. Isso ocorre porque todas as atualizações só se tornam visíveis quando a transação é confirmada. Observe como abortamos a terceira transação pressionando CRTL-C. Agora, nem o assento nem o pagamento aparecem nos fluxos de alteração, ao contrário do primeiro exemplo em que o assento passou.
É nesse ponto que as transações se destacam em um mundo em que tudo ou nada é a palavra de ordem. Nunca queremos manter os assentos alocados, a menos que eles sejam pagos.

O que acontece durante uma falha?

Em um conjunto de réplicas do MongoDB, todas as gravações são direcionadas ao nó primário. Se o nó primário falhar ou ficar inacessível (por exemplo, devido a uma partição de rede), as gravações em andamento poderão falhar. Em um cenário não transacional, o driver se recuperará de uma única falha e tentará a gravação novamente. Em uma transação com vários documentos, devemos recuperar e tentar novamente no caso desses tipos de falhas transitórias. Esse código está encapsulado em transaction_retry.py. Tentamos novamente a transação e repetimos a confirmação para lidar com cenários em que a primária falha na transação e/ou na operação de confirmação .
1def commit_with_retry(session):
2 while True:
3 try:
4 # Commit uses write concern set at transaction start.
5 session.commit_transaction()
6 print("Transaction committed.")
7 break
8 except (pymongo.errors.ConnectionFailure, pymongo.errors.OperationFailure) as exc:
9 # Can retry commit
10 if exc.has_error_label("UnknownTransactionCommitResult"):
11 print("UnknownTransactionCommitResult, retrying "
12 "commit operation ...")
13 continue
14 else:
15 print("Error during commit ...")
16 raise
17
18def run_transaction_with_retry(functor, session):
19 assert (isinstance(functor, Transaction_Functor))
20 while True:
21 try:
22 with session.start_transaction():
23 result=functor(session) # performs transaction
24 commit_with_retry(session)
25 break
26 except (pymongo.errors.ConnectionFailure, pymongo.errors.OperationFailure) as exc:
27 # If transient error, retry the whole transaction
28 if exc.has_error_label("TransientTransactionError"):
29 print("TransientTransactionError, retrying "
30 "transaction ...")
31 continue
32 else:
33 raise
34
35 return result
Para observar o que acontece durante as eleições, podemos usar o roteiro kill_primary.py. Este script iniciará um conjunto de réplicas e matará continuamente o primário.
1$ make kill_primary
2. venv/bin/activate && python kill_primary.py
3no nodes started.
4Current electionTimeoutMillis: 500
51. (Re)starting replica-set
6no nodes started.
71. Getting list of mongod processes
8Process list written to mlaunch.procs
91. Getting replica set status
101. Killing primary node: 31029
111. Sleeping: 1.0
122. (Re)starting replica-set
13launching: "/usr/local/mongodb/bin/mongod" on port 27101
142. Getting list of mongod processes
15Process list written to mlaunch.procs
162. Getting replica set status
172. Killing primary node: 31045
182. Sleeping: 1.0
193. (Re)starting replica-set
20launching: "/usr/local/mongodb/bin/mongod" on port 27102
213. Getting list of mongod processes
22Process list written to mlaunch.procs
233. Getting replica set status
243. Killing primary node: 31137
253. Sleeping: 1.0
kill_primary.py redefine electionTimeOutMillis para 500ms do padrão de 10000ms (10 segundos). Isso permite que as eleições sejam resolvidas mais rapidamente para os fins deste teste, pois estamos executando tudo localmente.
Quando kill_primary.py estiver em execução, podemos iniciar transactions_main.py novamente usando o argumento--usetxns .
1$ make usetxns
2. venv/bin/activate && python transaction_main.py --usetxns
3Forcing collection creation (you can't create collections inside a txn)
4Collections created
5using collection: PYTHON_TXNS_EXAMPLE.seats
6using collection: PYTHON_TXNS_EXAMPLE.payments
7using collection: PYTHON_TXNS_EXAMPLE.audit
8Using a fixed delay of 1.0
9Using transactions
10
111. Booking seat: '1A'
121. Sleeping: 1.000
131. Paying 440 for seat '1A'
14Transaction committed.
152. Booking seat: '2A'
162. Sleeping: 1.000
172. Paying 330 for seat '2A'
18Transaction committed.
193. Booking seat: '3A'
203. Sleeping: 1.000
21TransientTransactionError, retrying transaction ...
223. Booking seat: '3A'
233. Sleeping: 1.000
243. Paying 240 for seat '3A'
25Transaction committed.
264. Booking seat: '4A'
274. Sleeping: 1.000
284. Paying 410 for seat '4A'
29Transaction committed.
305. Booking seat: '5A'
315. Sleeping: 1.000
325. Paying 260 for seat '5A'
33Transaction committed.
346. Booking seat: '6A'
356. Sleeping: 1.000
36TransientTransactionError, retrying transaction ...
376. Booking seat: '6A'
386. Sleeping: 1.000
396. Paying 380 for seat '6A'
40Transaction committed.
41...
Como você pode ver durante as eleições, a transação será abortada e deve ser repetida. Se você olhar para o códigotransaction_rety.py , verá como isso acontece. Se uma operação de gravação encontrar um erro, ela lançará uma das seguintes exceções:
Dentro dessas exceções, haverá um rótulo chamado TransientTransactionError. Este rótulo pode ser detectado usando a funçãohas_error_label(label) que está disponível no pymongo 3.7.x. Erros transitórios podem ser recuperados e o código de repetição em transactions_retry.py tem código que tenta novamente tanto para escrita quanto para commit (veja acima).

Conclusão

As transações multidocumento são a peça final do quebra-cabeça para desenvolvedores SQL que têm evitado experimentar o MongoDB. As transações ACID facilitam o trabalho do programador e oferecem às equipes que estão migrando de um esquema SQL existente um caminho de transição muito mais consistente e conveniente.
Como a maioria das migrações envolve uma mudança de estruturas de dados altamente normalizadas para documentos JSON aninhados mais naturais e flexíveis, seria de se esperar que o número de transações multidocumento necessárias fosse menor em um aplicativo MongoDB construído corretamente. Mas onde as transações de vários documentos são necessárias, os programadores agora podem incluí-las usando uma sintaxe muito semelhante ao SQL.
Com transações ACID no MongoDB 4.0 agora ele pode ser a primeira opção para uma variedade ainda mais ampla de casos de uso de aplicativos.
Se você ainda não configurou seu cluster gratuito no MongoDB Atlas, agora é um ótimo momento para fazer isso. Você tem todas as instruções nesta publicação no blog.
Para experimentar localmente,baixe MongoDB 4.0.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Início rápido
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Exemplo de código

MergeURL - Aplicativo de exemplo Python


Jul 07, 2022 | 3 min read
Tutorial

Crie aplicativos inteligentes com o Atlas Vector Search e o Google Vertex AI


Sep 18, 2024 | 4 min read
Artigo

Implementando o direito ao apagamento com o CSFLE


Mar 08, 2023 | 7 min read
Tutorial

Parte #2: criar um modelo de endpoint com o Amazon SageMaker, o AWS Lambda e o AWS API Gateway


Sep 18, 2024 | 7 min read
Sumário