Python est un langage de programmation puissant et flexible utilisé par des millions de développeurs dans le monde pour créer leurs applications. Il n'est pas surprenant que les développeurs Python utilisent généralement l'hébergement MongoDB, la base de données NoSQL la plus populaire, pour leurs déploiements en raison de sa nature flexible et de l'absence d'exigences de schéma.
Alors, quelle est la meilleure façon d'utiliser MongoDB avec Python ? PyMongo est une distribution Python contenant des outils pour travailler avec MongoDB et le pilote Python MongoDB recommandé. C'est un pilote assez mature qui prend en charge la plupart des opérations courantes avec la base de données.
Lors du déploiement en production, il est fortement recommandé d'effectuer une configuration dans une configuration d'ensemble de répliques MongoDB afin que vos données soient réparties géographiquement pour une haute disponibilité. Il est également recommandé d'activer les connexions SSL pour chiffrer le trafic de la base de données client. Nous testons souvent les caractéristiques de basculement de divers pilotes MongoDB afin de les qualifier pour des cas d'utilisation en production ou lorsque nos clients nous demandent conseil. Dans cet article, nous vous montrons comment vous connecter à un ensemble d'instances dupliquées MongoDB compatible SSL configuré avec des certificats auto-signés à l'aide de PyMongo, et comment tester le comportement de basculement de MongoDB dans votre code.
Connexion à MongoDB SSL à l'aide de certificats auto-signés
La première étape consiste à s'assurer que les bonnes versions de PyMongo et de ses dépendances sont installées. Ce guide vous aide à trier les dépendances, et la matrice de compatibilité des pilotes se trouve ici.
Le mongo_client.MongoClient les paramètres qui nous intéressent sont ssl et ss_ca_cert . Pour se connecter à un point de terminaison MongoDB compatible SSL qui utilise un certificat auto-signé, ssl doit être défini sur True et ss_ca_cert doit pointer vers le fichier de certificat CA.
Si vous êtes un client ScaleGrid, vous pouvez télécharger le fichier de certificat CA pour vos clusters MongoDB à partir de la console ScaleGrid comme indiqué ici :
Ainsi, un extrait de connexion ressemblerait à :
>>> 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() >>>
Si vous utilisez vos propres certificats auto-signés où la vérification du nom d'hôte peut échouer, vous devrez également définir le ssl_match_hostname paramètre sur False . Comme le dit la documentation du pilote, cela n'est pas recommandé car cela rend la connexion vulnérable aux attaques de l'homme du milieu.
Test du comportement de basculement
Avec les déploiements MongoDB, les basculements ne sont pas considérés comme des événements majeurs comme ils l'étaient avec les systèmes de gestion de base de données traditionnels. Bien que la plupart des pilotes MongoDB tentent d'abstraire cet événement, les développeurs doivent comprendre et concevoir leurs applications pour un tel comportement, car les applications doivent s'attendre à des erreurs réseau transitoires et réessayer avant de transmettre les erreurs.
Vous pouvez tester la résilience de vos applications en provoquant des basculements pendant l'exécution de votre charge de travail. Le moyen le plus simple d'induire un basculement consiste à exécuter la commande 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>
L'une des façons dont j'aime tester le comportement des pilotes consiste à écrire une simple application d'écriture "perpétuelle". Il s'agirait d'un code simple qui continuerait d'écrire dans la base de données à moins d'être interrompu par l'utilisateur, et imprimerait toutes les exceptions qu'il rencontre pour nous aider à comprendre le comportement du pilote et de la base de données. Je garde également une trace des données qu'il écrit pour m'assurer qu'il n'y a pas de perte de données non signalée lors du test. Voici la partie pertinente du code de test que nous utiliserons pour tester notre comportement de basculement 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()
Le genre d'entrées auxquelles cela ressemble :
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" } ..
Gestion de l'exception ConnectionFailure
Remarquez que nous interceptons l'exception ConnectionFailure pour traiter tous les problèmes liés au réseau que nous pouvons rencontrer en raison de basculements ; nous imprimons l'exception et continuons d'essayer d'écrire dans la base de données. La documentation du pilote recommande que :
Si une opération échoue en raison d'une erreur réseau, ConnectionFailure est déclenché et le client se reconnecte en arrière-plan. Le code de l'application doit gérer cette exception (en reconnaissant que l'opération a échoué) puis continuer à s'exécuter.
Exécutons ceci et effectuons un basculement de la base de données pendant son exécution. Voici ce qui se passe :
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
Notez que le pilote prend environ 12 secondes pour comprendre la nouvelle topologie, se connecter au nouveau principal et continuer à écrire. L'exception levée est errors . Reconnexion automatique qui est une sous-classe de ConnectionFailure .
Tutoriel PyMongo :Tester le basculement MongoDB dans votre application PythonCliquez pour tweeter
Vous pouvez effectuer quelques exécutions supplémentaires pour voir quelles autres exceptions sont observées. Par exemple, voici une autre trace d'exception que j'ai rencontré :
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
Cette exception est également une sous-classe de ConnectionFailure.
Paramètre ‘retryWrites’
Un autre domaine pour tester le comportement de basculement de MongoDB serait de voir comment d'autres variations de paramètres affectent les résultats. Un paramètre pertinent est "retryWrites ' :
retryWrites :(booléen) Indique si les opérations d'écriture prises en charge exécutées dans ce MongoClient seront réessayées une fois après une erreur réseau sur MongoDB 3.6+. La valeur par défaut est False.
Voyons comment ce paramètre fonctionne avec un basculement. La seule modification apportée au code est :
client = pymongo.MongoClient(MONGO_URI, ssl = True, ssl_ca_certs = 'path-to-cacert.pem', retryWrites = True)
Exécutons-le maintenant, puis effectuons un basculement du système de base de données :
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 ...
Remarquez comment l'insertion après le basculement prend environ 12 secondes, mais réussit en tant que retryWrites Le paramètre garantit que l'écriture ayant échoué est réessayée. N'oubliez pas que la définition de ce paramètre ne vous dispense pas de gérer le ConnectionFailure exception :vous devez vous soucier des lectures et des autres opérations dont le comportement n'est pas affecté par ce paramètre. Cela ne résout pas non plus complètement le problème, même pour les opérations prises en charge. Parfois, les basculements peuvent prendre plus de temps et retryWrites seul ne suffira pas.
Configuration des valeurs de délai d'attente du réseau
rs.stepDown() induit un basculement assez rapide, car le jeu de réplicas primaire est chargé de devenir un secondaire, et les secondaires organisent une élection pour déterminer le nouveau primaire. Dans les déploiements de production, la charge du réseau, la partition et d'autres problèmes similaires retardent la détection de l'indisponibilité du serveur principal, prolongeant ainsi votre temps de basculement. Vous rencontrerez également souvent des erreurs PyMongo telles que errors.ServerSelectionTimeoutError , errors.NetworkTimeout, etc. lors de problèmes de réseau et de basculements.
Si cela se produit très souvent, vous devez chercher à modifier les paramètres de délai d'attente. Le MongoClient associé les paramètres de délai d'attente sont serverSelectionTimeoutMS , connectTimeoutMS, et socketTimeoutMS . Parmi ceux-ci, en sélectionnant une valeur plus élevée pour serverSelectionTimeoutMS aide le plus souvent à gérer les erreurs lors des basculements :
serverSelectionTimeoutMS :(entier) Contrôle la durée (en millisecondes) pendant laquelle le pilote attendra pour trouver un serveur disponible et approprié pour effectuer une opération de base de données ; pendant qu'il attend, plusieurs opérations de surveillance du serveur peuvent être effectuées, chacune contrôlée par connectTimeoutMS. La valeur par défaut est 30 000 (30 secondes).
Prêt à utiliser MongoDB dans votre application Python ? Consultez notre article Premiers pas avec Python et MongoDB pour découvrir comment vous pouvez être opérationnel en seulement 5 étapes simples. ScaleGrid est le seul fournisseur MongoDB DBaaS qui vous donne un accès SSH complet à vos instances afin que vous puissiez exécuter votre serveur Python sur la même machine que votre serveur MongoDB. Automatisez vos déploiements cloud MongoDB sur AWS, Azure ou DigitalOcean avec des serveurs dédiés, une haute disponibilité et une reprise après sinistre afin que vous puissiez vous concentrer sur le développement de votre application Python.