MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Comment utiliser le regroupement de connexions MongoDB sur AWS Lambda

Dans cet article, nous allons vous montrer comment utiliser le regroupement de connexions MongoDB sur AWS Lambda en utilisant à la fois les pilotes Node.js et Java.

Qu'est-ce qu'AWS Lambda ?

AWS Lambda est un service informatique sans serveur basé sur les événements fourni par Amazon Web Services . Il permet à un utilisateur d'exécuter du code sans aucune des tâches administratives, contrairement aux instances EC2 où un utilisateur est responsable de l'approvisionnement des serveurs, de la mise à l'échelle, de la haute disponibilité, etc. Au lieu de cela, il vous suffit de télécharger le code et de configurer le déclencheur d'événement, et AWS Lambda s'occupe automatiquement de tout le reste.

AWS Lambda prend en charge divers environnements d'exécution, y compris Node.js , Python , Java , et Aller . Il peut être directement déclenché par des services AWS comme S3 , DynamoDB , Kinesis , SRS , etc. Dans notre exemple, nous utilisons la passerelle API AWS pour déclencher les fonctions Lambda.

Qu'est-ce qu'un pool de connexions ?

L'ouverture et la fermeture d'une connexion à une base de données est une opération coûteuse car elle implique à la fois du temps CPU et de la mémoire. Si une application doit ouvrir une connexion à la base de données pour chaque opération, cela aura un impact important sur les performances.

Que se passe-t-il si nous avons un tas de connexions à la base de données qui sont maintenues en vie dans un cache ? Chaque fois qu'une application doit effectuer une opération de base de données, elle peut emprunter une connexion à partir du cache, effectuer l'opération requise et la restituer. En utilisant cette approche, nous pouvons économiser le temps nécessaire pour établir une nouvelle connexion à chaque fois et réutiliser les connexions. Ce cache est appelé pool de connexion .

La taille du pool de connexions est configurable dans la plupart des pilotes MongoDB, et la taille du pool par défaut varie d'un pilote à l'autre. Par exemple, c'est 5 dans le pilote Node.js, alors que c'est 100 dans le pilote Java. La taille du pool de connexion détermine le nombre maximum de requêtes parallèles que votre pilote peut gérer à un instant donné. Si la limite du pool de connexions est atteinte, toute nouvelle demande sera faite pour attendre que celles existantes soient terminées. Par conséquent, la taille du pool doit être choisie avec soin, en tenant compte de la charge d'application et de la simultanéité à atteindre.

Pools de connexion MongoDB dans AWS Lambda

Dans cet article, nous vous montrerons des exemples impliquant à la fois Node.js et le pilote Java pour MongoDB. Pour ce didacticiel, nous utilisons MongoDB hébergé sur ScaleGrid à l'aide d'instances AWS EC2. La configuration prend moins de 5 minutes et vous pouvez créer un essai gratuit de 30 jours ici pour commencer.
Comment utiliser le regroupement de connexions #MongoDB sur AWS Lambda à l'aide de pilotes Node.js et LambdaCliquez pour tweeter

Groupe de connexions MongoDB du pilote Java

Voici le code pour activer le pool de connexions MongoDB à l'aide du pilote Java dans la fonction de gestionnaire AWS Lambda :


public class LambdaFunctionHandler
		implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

	private MongoClient sgMongoClient;
	private String sgMongoClusterURI;
	private String sgMongoDbName;

	@Override
	public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
		APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
		response.setStatusCode(200);

		try {
			context.getLogger().log("Input: " + new Gson().toJson(input));
			init(context);
			String body = getLastAlert(input, context);
			context.getLogger().log("Result body: " + body);
			response.setBody(body);
		} catch (Exception e) {
			response.setBody(e.getLocalizedMessage());
			response.setStatusCode(500);
		}

		return response;
	}

	private MongoDatabase getDbConnection(String dbName, Context context) {
		if (sgMongoClient == null) {
			context.getLogger().log("Initializing new connection");
			MongoClientOptions.Builder destDboptions = MongoClientOptions.builder();
			destDboptions.socketKeepAlive(true);
			sgMongoClient = new MongoClient(new MongoClientURI(sgMongoClusterURI, destDboptions));
			return sgMongoClient.getDatabase(dbName);
		}
		context.getLogger().log("Reusing existing connection");
		return sgMongoClient.getDatabase(dbName);
	}

	private String getLastAlert(APIGatewayProxyRequestEvent input, Context context) {
		String userId = input.getPathParameters().get("userId");
		MongoDatabase db = getDbConnection(sgMongoDbName, context);
		MongoCollection coll = db.getCollection("useralerts");
		Bson query = new Document("userId", Integer.parseInt(userId));
		Object result = coll.find(query).sort(Sorts.descending("$natural")).limit(1).first();
		context.getLogger().log("Result: " + result);
		return new Gson().toJson(result);
	}

	private void init(Context context) {
		sgMongoClusterURI = System.getenv("SCALEGRID_MONGO_CLUSTER_URI");
		sgMongoDbName = System.getenv("SCALEGRID_MONGO_DB_NAME");
	}

}

La mise en commun des connexions est réalisée ici en déclarant un sgMongoClient variable en dehors de la fonction de gestionnaire. Les variables déclarées en dehors de la méthode du gestionnaire restent initialisées d'un appel à l'autre, tant que le même conteneur est réutilisé. Cela est vrai pour tout autre langage de programmation pris en charge par AWS Lambda.

Pool de connexion MongoDB du pilote Node.js

Pour le pilote Node.js, déclarer la variable de connexion dans la portée globale fera également l'affaire. Cependant, il existe un paramètre spécial sans lequel le regroupement de connexions n'est pas possible. Ce paramètre est callbackWaitsForEmptyEventLoop qui appartient à l'objet de contexte de Lambda. Si vous définissez cette propriété sur false, AWS Lambda gèlera le processus et toutes les données d'état. Cela se fait peu de temps après l'appel du rappel, même s'il y a des événements dans la boucle d'événements.

Voici le code pour activer le pool de connexions MongoDB à l'aide du pilote Node.js dans la fonction de gestionnaire AWS Lambda :


'use strict'

var MongoClient = require('mongodb').MongoClient;

let mongoDbConnectionPool = null;
let scalegridMongoURI = null;
let scalegridMongoDbName = null;

exports.handler = (event, context, callback) => {

    console.log('Received event:', JSON.stringify(event));
    console.log('remaining time =', context.getRemainingTimeInMillis());
    console.log('functionName =', context.functionName);
    console.log('AWSrequestID =', context.awsRequestId);
    console.log('logGroupName =', context.logGroupName);
    console.log('logStreamName =', context.logStreamName);
    console.log('clientContext =', context.clientContext);
   
    // This freezes node event loop when callback is invoked
    context.callbackWaitsForEmptyEventLoop = false;

    var mongoURIFromEnv = process.env['SCALEGRID_MONGO_CLUSTER_URI'];
    var mongoDbNameFromEnv = process.env['SCALEGRID_MONGO_DB_NAME'];
    if(!scalegridMongoURI) {
	if(mongoURIFromEnv){
		scalegridMongoURI = mongoURIFromEnv;
	} else {
		var errMsg = 'Scalegrid MongoDB cluster URI is not specified.';
		console.log(errMsg);
		var errResponse = prepareResponse(null, errMsg);
		return callback(errResponse);
	}			
    }

    if(!scalegridMongoDbName) {
	if(mongoDbNameFromEnv) {
                scalegridMongoDbName = mongoDbNameFromEnv;
	} else {
		var errMsg = 'Scalegrid MongoDB name not specified.';
		console.log(errMsg);
		var errResponse = prepareResponse(null, errMsg);
		return callback(errResponse);
	}
    }

    handleEvent(event, context, callback);
};


function getMongoDbConnection(uri) {

    if (mongoDbConnectionPool && mongoDbConnectionPool.isConnected(scalegridMongoDbName)) {
        console.log('Reusing the connection from pool');
        return Promise.resolve(mongoDbConnectionPool.db(scalegridMongoDbName));
    }

    console.log('Init the new connection pool');
    return MongoClient.connect(uri, { poolSize: 10 })
        .then(dbConnPool => { 
                            mongoDbConnectionPool = dbConnPool; 
                            return mongoDbConnectionPool.db(scalegridMongoDbName); 
                          });
}

function handleEvent(event, context, callback) {
    getMongoDbConnection(scalegridMongoURI)
        .then(dbConn => {
			console.log('retrieving userId from event.pathParameters');
			var userId = event.pathParameters.userId;
			getAlertForUser(dbConn, userId, context);
		})
        .then(response => {
            console.log('getAlertForUser response: ', response);
            callback(null, response);
        })
        .catch(err => {
            console.log('=> an error occurred: ', err);
            callback(prepareResponse(null, err));
        });
}

function getAlertForUser(dbConn, userId, context) {

    return dbConn.collection('useralerts').find({'userId': userId}).sort({$natural:1}).limit(1)
        .toArray()
        .then(docs => { return prepareResponse(docs, null);})
        .catch(err => { return prepareResponse(null, err); });
}

function prepareResponse(result, err) {
	if(err) {
		return { statusCode:500, body: err };
	} else {
		return { statusCode:200, body: result };
	}
}

Analyse et observations du pool de connexions AWS Lambda

Pour vérifier les performances et l'optimisation de l'utilisation des pools de connexions, nous avons effectué quelques tests pour les fonctions Lambda Java et Node.js. En utilisant la passerelle d'API AWS comme déclencheur, nous avons invoqué les fonctions dans une rafale de 50 requêtes par itération et déterminé le temps de réponse moyen d'une requête à chaque itération. Ce test a été répété pour les fonctions Lambda sans utiliser le pool de connexions initialement, puis avec le pool de connexions.

Les graphiques ci-dessus représentent le temps de réponse moyen d'une requête à chaque itération. Vous pouvez voir ici la différence de temps de réponse lorsqu'un pool de connexions est utilisé pour effectuer des opérations de base de données. Le temps de réponse à l'aide d'un pool de connexions est nettement inférieur car le pool de connexions s'initialise une fois et réutilise la connexion au lieu d'ouvrir et de fermer la connexion pour chaque opération de base de données.

La seule différence notable entre les fonctions Lambda Java et Node.js est l'heure de démarrage à froid.

Qu'est-ce que l'heure de démarrage à froid ?

Le temps de démarrage à froid fait référence au temps pris par la fonction AWS Lambda pour l'initialisation. Lorsque la fonction Lambda reçoit sa première requête, elle initialise le conteneur et l'environnement de processus requis. Dans les graphiques ci-dessus, le temps de réponse de la requête 1 inclut le temps de démarrage à froid, qui diffère considérablement en fonction du langage de programmation utilisé pour la fonction AWS Lambda.

Dois-je m'inquiéter de l'heure de démarrage à froid ?

Si vous utilisez la passerelle d'API AWS comme déclencheur pour la fonction Lambda, vous devez tenir compte du temps de démarrage à froid. La réponse de la passerelle API générera une erreur si la fonction d'intégration AWS Lambda n'est pas initialisée dans la plage horaire donnée. Le délai d'expiration de l'intégration de la passerelle API est compris entre 50 millisecondes et 29 secondes.

Dans le graphique de la fonction Java AWS Lambda, vous pouvez voir que la première requête a pris plus de 29 secondes, par conséquent, la réponse de la passerelle API a généré une erreur. Le temps de démarrage à froid pour la fonction AWS Lambda écrite à l'aide de Java est plus élevé par rapport aux autres langages de programmation pris en charge. Afin de résoudre ces problèmes de temps de démarrage à froid, vous pouvez lancer une demande d'initialisation avant l'appel réel. L'autre alternative est d'avoir une nouvelle tentative côté client. Ainsi, si la demande échoue en raison d'un démarrage à froid, la nouvelle tentative réussira.

Qu'advient-il de la fonction AWS Lambda pendant l'inactivité ?

Lors de nos tests, nous avons également observé que les conteneurs d'hébergement AWS Lambda étaient arrêtés lorsqu'ils étaient inactifs pendant un certain temps. Cet intervalle variait de 7 à 20 minutes. Ainsi, si vos fonctions Lambda ne sont pas utilisées fréquemment, vous devez envisager de les maintenir actives en lançant des requêtes de pulsation ou en ajoutant de nouvelles tentatives côté client.

Que se passe-t-il lorsque j'appelle des fonctions Lambda simultanément ?

Si les fonctions Lambda sont appelées simultanément, alors Lambda utilisera de nombreux conteneurs pour répondre à la demande. Par défaut, AWS Lambda fournit une simultanéité non réservée de 1 000 requêtes et est configurable pour une fonction Lambda donnée.

C'est là que vous devez faire attention à la taille du pool de connexions, car les demandes simultanées peuvent ouvrir trop de connexions. Vous devez donc conserver une taille de pool de connexions optimale pour votre fonction. Cependant, une fois les conteneurs arrêtés, les connexions seront libérées en fonction du délai d'expiration du serveur MongoDB.

Conclusion du regroupement de connexions AWS Lambda

Les fonctions Lambda sont sans état et asynchrones, et en utilisant le pool de connexions à la base de données, vous pourrez y ajouter un état. Cependant, cela ne sera utile que lorsque les conteneurs seront réutilisés, ce qui vous permettra de gagner beaucoup de temps. Le regroupement de connexions à l'aide d'AWS EC2 est plus facile à gérer car une seule instance peut suivre l'état de son pool de connexions sans aucun problème. Ainsi, l'utilisation d'AWS EC2 réduit considérablement le risque de manquer de connexions à la base de données. AWS Lambda est conçu pour mieux fonctionner lorsqu'il peut simplement accéder à une API et n'a pas besoin de se connecter à un moteur de base de données.