MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Tutorial do PyMongo:testando o Failover do MongoDB em seu aplicativo Python

Python é uma linguagem de programação poderosa e flexível usada por milhões de desenvolvedores em todo o mundo para criar seus aplicativos. Não é surpresa que os desenvolvedores Python geralmente aproveitem a hospedagem MongoDB, o banco de dados NoSQL mais popular, para suas implantações devido à sua natureza flexível e falta de requisitos de esquema.

Então, qual é a melhor maneira de usar o MongoDB com Python? PyMongo é uma distribuição Python que contém ferramentas para trabalhar com MongoDB e o driver Python MongoDB recomendado. É um driver bastante maduro que suporta a maioria das operações comuns com o banco de dados.

Ao implantar em produção, é altamente recomendável configurar uma configuração de conjunto de réplicas do MongoDB para que seus dados sejam distribuídos geograficamente para alta disponibilidade. Também é recomendado que as conexões SSL sejam habilitadas para criptografar o tráfego do banco de dados cliente. Frequentemente, realizamos testes de características de failover de vários drivers MongoDB para qualificá-los para casos de uso de produção ou quando nossos clientes nos pedem conselhos. Nesta postagem, mostramos como se conectar a um conjunto de réplicas do MongoDB habilitado para SSL configurado com certificados autoassinados usando PyMongo e como testar o comportamento de failover do MongoDB em seu código.

Conectando ao MongoDB SSL usando certificados autoassinados

O primeiro passo é garantir que as versões corretas do PyMongo e suas dependências estejam instaladas. Este guia ajuda você a classificar as dependências, e a matriz de compatibilidade do driver pode ser encontrada aqui.

O mongo_client.MongoClient os parâmetros que nos interessam são ssl e ss_ca_cert . Para se conectar a um endpoint MongoDB habilitado para SSL que usa um certificado autoassinado, ssl deve ser definido como True e ss_ca_cert deve apontar para o arquivo de certificado CA.

Se você for um cliente ScaleGrid, você pode baixar o arquivo de certificado CA para seus clusters MongoDB no console do ScaleGrid, conforme mostrado aqui:

Assim, um trecho de conexão ficaria assim:

>>> import pymongo
>>> MONGO_URI = 'mongodb://rwuser:@SG-example-0.servers.mongodirector.com:27017,SG-example-1.servers.mongodirector.com:27017,SG-example-2.servers.mongodirector.com:27017/admin?replicaSet=RS-example&ssl=true'
>>> client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = '')
>>> print("Databases - " + str(client.list_database_names()))
Databases - ['admin', 'local', 'test']
>>> client.close()
>>>

Se você estiver usando seus próprios certificados autoassinados onde a verificação do nome do host pode falhar, você também terá que definir o ssl_match_hostname parâmetro para Falso . Como a documentação do driver diz, isso não é recomendado, pois torna a conexão suscetível a ataques man-in-the-middle.

Testar o comportamento de failover

Com as implantações do MongoDB, os failovers não são considerados eventos importantes como eram com os sistemas tradicionais de gerenciamento de banco de dados. Embora a maioria dos drivers do MongoDB tente abstrair esse evento, os desenvolvedores devem entender e projetar seus aplicativos para esse comportamento, pois os aplicativos devem esperar erros de rede transitórios e tentar novamente antes de infiltrar os erros.

Você pode testar a resiliência de seus aplicativos induzindo failovers enquanto sua carga de trabalho é executada. A maneira mais fácil de induzir o failover é executar o comando rs.stepDown():

RS-example-0:PRIMARY> rs.stepDown()
2019-04-18T19:44:42.257+0530 E QUERY [thread1] Error: error doing query: failed: network error while attempting to run command 'replSetStepDown' on host 'SG-example-1.servers.mongodirector.com:27017' :
DB.prototype.runCommand@src/mongo/shell/db.js:168:1
DB.prototype.adminCommand@src/mongo/shell/db.js:185:1
rs.stepDown@src/mongo/shell/utils.js:1305:12
@(shell):1:1
2019-04-18T19:44:42.261+0530 I NETWORK [thread1] trying reconnect to SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) failed
2019-04-18T19:44:43.267+0530 I NETWORK [thread1] reconnect SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) ok
RS-example-0:SECONDARY>

Uma das maneiras que eu gosto de testar o comportamento dos motoristas é escrever um aplicativo simples de escrita 'perpétua'. Esse seria um código simples que continua gravando no banco de dados, a menos que seja interrompido pelo usuário, e imprimiria todas as exceções encontradas para nos ajudar a entender o comportamento do driver e do banco de dados. Também acompanho os dados gravados para garantir que não haja perda de dados não relatada no teste. Aqui está a parte relevante do código de teste que usaremos para testar nosso comportamento de failover do MongoDB:

import logging
import traceback
...
import pymongo
...
logger = logging.getLogger("test")

MONGO_URI = 'mongodb://rwuser:@SG-example-0.servers.mongodirector.com:48273,SG-example-1.servers.mongodirector.com:27017,SG-example-2.servers.mongodirector.com:27017/admin?replicaSet=RS-example-0&ssl=true'

try:
    logger.info("Attempting to connect...")
    client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem')
    db = client['test']
    collection = db['test']
    i = 0
    while True:
        try:
            text = ''.join(random.choices(string.ascii_uppercase + string.digits, k = 3))
            doc = { "idx": i, "date" : datetime.utcnow(), "text" : text}
            i += 1
            id = collection.insert_one(doc).inserted_id
            logger.info("Record inserted - id: " + str(id))
            sleep(3)
        except pymongo.errors.ConnectionFailure as e:
            logger.error("ConnectionFailure seen: " + str(e))
            traceback.print_exc(file = sys.stdout)
            logger.info("Retrying...")

    logger.info("Done...")
except Exception as e:
    logger.error("Exception seen: " + str(e))
    traceback.print_exc(file = sys.stdout)
finally:
    client.close()

O tipo de entradas que isso escreve se parece com:

RS-example-0:PRIMARY> db.test.find()
{ "_id" : ObjectId("5cb6d6269ece140f18d05438"), "idx" : 0, "date" : ISODate("2019-04-17T07:30:46.533Z"), "text" : "400" }
{ "_id" : ObjectId("5cb6d6299ece140f18d05439"), "idx" : 1, "date" : ISODate("2019-04-17T07:30:49.755Z"), "text" : "X63" }
{ "_id" : ObjectId("5cb6d62c9ece140f18d0543a"), "idx" : 2, "date" : ISODate("2019-04-17T07:30:52.976Z"), "text" : "5BX" }
{ "_id" : ObjectId("5cb6d6329ece140f18d0543c"), "idx" : 4, "date" : ISODate("2019-04-17T07:30:58.001Z"), "text" : "TGQ" }
{ "_id" : ObjectId("5cb6d63f9ece140f18d0543d"), "idx" : 5, "date" : ISODate("2019-04-17T07:31:11.417Z"), "text" : "ZWA" }
{ "_id" : ObjectId("5cb6d6429ece140f18d0543e"), "idx" : 6, "date" : ISODate("2019-04-17T07:31:14.654Z"), "text" : "WSR" }
..

Lidando com a exceção ConnectionFailure

Observe que capturamos a exceção ConnectionFailure para lidar com todos os problemas relacionados à rede que podemos encontrar devido a failovers – imprimimos a exceção e continuamos a tentar gravar no banco de dados. A documentação do driver recomenda que:


Se uma operação falhar devido a um erro de rede, ConnectionFailure é gerado e o cliente se reconecta em segundo plano. O código do aplicativo deve tratar essa exceção (reconhecendo que a operação falhou) e, em seguida, continuar a execução.

Vamos executar isso e fazer um failover do banco de dados enquanto ele é executado. Aqui está o que acontece:

04/17/2019 12:49:17 PM INFO Attempting to connect...
04/17/2019 12:49:20 PM INFO Record inserted - id: 5cb6d3789ece145a2408cbc7
04/17/2019 12:49:23 PM INFO Record inserted - id: 5cb6d37b9ece145a2408cbc8
04/17/2019 12:49:27 PM INFO Record inserted - id: 5cb6d37e9ece145a2408cbc9
04/17/2019 12:49:30 PM ERROR PyMongoError seen: connection closed
Traceback (most recent call last):
    id = collection.insert_one(doc).inserted_id
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\collection.py", line 693, in insert_one
    session=session),
...
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 173, in receive_message
    _receive_data_on_socket(sock, 16))
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 238, in _receive_data_on_socket
    raise AutoReconnect("connection closed")
pymongo.errors.AutoReconnect: connection closed
04/17/2019 12:49:30 PM INFO Retrying...
04/17/2019 12:49:42 PM INFO Record inserted - id: 5cb6d3829ece145a2408cbcb
04/17/2019 12:49:45 PM INFO Record inserted - id: 5cb6d3919ece145a2408cbcc
04/17/2019 12:49:49 PM INFO Record inserted - id: 5cb6d3949ece145a2408cbcd
04/17/2019 12:49:52 PM INFO Record inserted - id: 5cb6d3989ece145a2408cbce

Observe que o driver leva cerca de 12 segundos para entender a nova topologia, conectar-se ao novo primário e continuar escrevendo. A exceção levantada é errors . Reconexão automática que é uma subclasse de ConnectionFailure .

Tutorial do PyMongo:testando o Failover do MongoDB em seu aplicativo PythonClique para tuitar

Você pode fazer mais algumas execuções para ver quais outras exceções são vistas. Por exemplo, aqui está outro rastreamento de exceção que encontrei:

    id = collection.insert_one(doc).inserted_id
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\collection.py", line 693, in insert_one
    session=session),
...
  File "C:\Users\Randome\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\network.py", line 150, in command
    parse_write_concern_error=parse_write_concern_error)
  File "C:\Users\Random\AppData\Local\Programs\Python\Python36-32\lib\site-packages\pymongo\helpers.py", line 132, in _check_command_response
    raise NotMasterError(errmsg, response)
pymongo.errors.NotMasterError: not master

Esta exceção também é uma subclasse de ConnectionFailure.

Parâmetro 'retryWrites'

Outra área para testar o comportamento de failover do MongoDB seria ver como outras variações de parâmetros afetam os resultados. Um parâmetro relevante é 'retryWrites ':


retryWrites:(boolean) Se as operações de gravação suportadas executadas neste MongoClient serão repetidas uma vez após um erro de rede no MongoDB 3.6+. Padrões para Falso.

Vamos ver como esse parâmetro funciona com um failover. A única alteração feita no código é:

client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem', retryWrites = True)

Vamos executá-lo agora e, em seguida, fazer um failover do sistema de banco de dados:

04/18/2019 08:49:30 PM INFO Attempting to connect...
04/18/2019 08:49:35 PM INFO Record inserted - id: 5cb895869ece146554010c77
04/18/2019 08:49:38 PM INFO Record inserted - id: 5cb8958a9ece146554010c78
04/18/2019 08:49:41 PM INFO Record inserted - id: 5cb8958d9ece146554010c79
04/18/2019 08:49:44 PM INFO Record inserted - id: 5cb895909ece146554010c7a
04/18/2019 08:49:48 PM INFO Record inserted - id: 5cb895939ece146554010c7b <<< Failover around this time
04/18/2019 08:50:04 PM INFO Record inserted - id: 5cb895979ece146554010c7c
04/18/2019 08:50:07 PM INFO Record inserted - id: 5cb895a79ece146554010c7d
04/18/2019 08:50:10 PM INFO Record inserted - id: 5cb895aa9ece146554010c7e
04/18/2019 08:50:14 PM INFO Record inserted - id: 5cb895ad9ece146554010c7f
...

Observe como a inserção após o failover leva cerca de 12 segundos, mas é executada com êxito como retryWrites O parâmetro garante que a gravação com falha seja repetida. Lembre-se de que definir este parâmetro não o isenta de lidar com o ConnectionFailure exceção - você precisa se preocupar com leituras e outras operações cujo comportamento não seja afetado por esse parâmetro. Ele também não resolve completamente o problema, mesmo para operações com suporte. Às vezes, os failovers podem demorar mais para serem concluídos e retryWrites sozinho não será suficiente.

Configurando os valores de tempo limite da rede

rs.stepDown() induz um failover bastante rápido, pois o primário do conjunto de réplicas é instruído para se tornar um secundário e os secundários realizam uma eleição para determinar o novo primário. Em implantações de produção, carga de rede, partição e outros problemas atrasam a detecção de indisponibilidade do servidor principal, prolongando o tempo de failover. Você também encontraria erros do PyMongo como errors.ServerSelectionTimeoutError , errors.NetworkTimeout, etc. durante problemas de rede e failovers.

Se isso ocorrer com muita frequência, você deve procurar ajustar os parâmetros de tempo limite. O MongoClient relacionado parâmetros de tempo limite são serverSelectionTimeoutMS , connectTimeoutMS, e socketTimeoutMS . Destes, selecionar um valor maior para serverSelectionTimeoutMS mais frequentemente ajuda a lidar com erros durante failovers:


serverSelectionTimeoutMS:(integer) Controla quanto tempo (em milissegundos) o driver aguardará para encontrar um servidor disponível e apropriado para realizar uma operação de banco de dados; enquanto espera, várias operações de monitoramento do servidor podem ser realizadas, cada uma controlada por connectTimeoutMS. O padrão é 30.000 (30 segundos).

Pronto para usar o MongoDB em seu aplicativo Python? Confira nosso artigo Primeiros passos com Python e MongoDB para ver como você pode começar a trabalhar em apenas 5 etapas fáceis. O ScaleGrid é o único provedor MongoDB DBaaS que oferece acesso SSH completo às suas instâncias para que você possa executar o servidor Python na mesma máquina que o servidor MongoDB. Automatize suas implantações de nuvem MongoDB na AWS, Azure ou DigitalOcean com servidores dedicados, alta disponibilidade e recuperação de desastres para que você possa se concentrar no desenvolvimento de seu aplicativo Python.