sql >> Database teknologi >  >> NoSQL >> MongoDB

Sådan bruger du MongoDB Connection Pooling på AWS Lambda

I dette indlæg viser vi dig, hvordan du bruger MongoDB-forbindelsespooling på AWS Lambda ved hjælp af både Node.js og Java-drivere.

Hvad er AWS Lambda?

AWS Lambda er en hændelsesdrevet, serverløs computertjeneste leveret af Amazon Web Services . Det giver en bruger mulighed for at køre kode uden nogen af ​​de administrative opgaver, i modsætning til EC2-forekomster hvor en bruger er ansvarlig for klargøring af servere, skalering, høj tilgængelighed osv. I stedet skal du kun uploade koden og konfigurere hændelsesudløseren, og AWS Lambda tager sig automatisk af alt andet.

AWS Lambda understøtter forskellige kørselstider, herunder Node.js , Python , Java , og Go . Det kan udløses direkte af AWS-tjenester som S3 , DynamoDB , Kinesis , SNS osv. I vores eksempel bruger vi AWS API-gatewayen til at udløse Lambda-funktionerne.

Hvad er en forbindelsespool?

Åbning og lukning af en databaseforbindelse er en dyr operation, da det involverer både CPU-tid og hukommelse. Hvis et program skal åbne en databaseforbindelse for hver handling, vil det have en alvorlig indvirkning på ydeevnen.

Hvad hvis vi har en masse databaseforbindelser, der holdes i live i en cache? Når et program skal udføre en databasehandling, kan det låne en forbindelse fra cachen, udføre den nødvendige handling og give den tilbage. Ved at bruge denne tilgang kan vi spare den tid, der kræves til at etablere en ny forbindelse hver gang og genbruge forbindelserne. Denne cache er kendt som forbindelsespuljen .

Størrelsen af ​​forbindelsespuljen kan konfigureres i de fleste MongoDB-drivere, og standardpuljestørrelsen varierer fra driver til driver. For eksempel er det 5 i Node.js driver, hvorimod det er 100 i Java driver. Forbindelsespuljens størrelse bestemmer det maksimale antal parallelle anmodninger, som din chauffør kan håndtere på et givet tidspunkt. Hvis grænsen for forbindelsespuljen nås, vil eventuelle nye anmodninger blive stillet til at vente, indtil de eksisterende er afsluttet. Derfor skal poolstørrelsen vælges omhyggeligt under hensyntagen til den påføringsbelastning og samtidighed, der skal opnås.

MongoDB Connection Pools i AWS Lambda

I dette indlæg viser vi dig eksempler, der involverer både Node.js og Java-driver til MongoDB. Til denne tutorial bruger vi MongoDB hostet på ScaleGrid ved hjælp af AWS EC2-instanser. Det tager mindre end 5 minutter at konfigurere, og du kan oprette en gratis 30-dages prøveperiode her for at komme i gang.
Sådan bruger du #MongoDB Connection Pooling på AWS Lambda ved hjælp af Node.js og Lambda-drivereKlik for at tweete

Java Driver MongoDB Connection Pool

Her er koden til at aktivere MongoDB-forbindelsespuljen ved hjælp af Java-driveren i AWS Lambda-handlerfunktion:


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");
	}

}

Forbindelsespoolingen opnås her ved at erklære en sgMongoClient variabel uden for handlerfunktionen. Variablerne erklæret uden for behandlermetoden forbliver initialiserede på tværs af kald, så længe den samme container genbruges. Dette gælder for ethvert andet programmeringssprog, der understøttes af AWS Lambda.

Node.js Driver MongoDB Connection Pool

For Node.js-driveren vil det også gøre tricket at erklære forbindelsesvariablen i globalt omfang. Der er dog en særlig indstilling, uden hvilken forbindelsespooling ikke er mulig. Denne parameter er callbackWaitsForEmptyEventLoop som hører til Lambdas kontekstobjekt. Indstilling af denne egenskab til falsk vil få AWS Lambda til at fryse processen og eventuelle tilstandsdata. Dette gøres kort efter, at tilbagekaldet er kaldt, selvom der er hændelser i hændelsesløkken.

Her er koden til at aktivere MongoDB-forbindelsespuljen ved hjælp af Node.js-driveren i AWS Lambda-handlerfunktionen:


'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 };
	}
}

AWS Lambda Connection Pool Analyse og observationer

For at verificere ydeevnen og optimeringen af ​​at bruge forbindelsespuljer, kørte vi få tests for både Java- og Node.js Lambda-funktioner. Ved at bruge AWS API-gatewayen som en udløser aktiverede vi funktionerne i et burst på 50 anmodninger pr. iteration og bestemte den gennemsnitlige responstid for en anmodning i hver iteration. Denne test blev gentaget for Lambda-funktioner uden først at bruge forbindelsespuljen og senere med forbindelsespuljen.

Graferne ovenfor repræsenterer den gennemsnitlige responstid for en anmodning i hver iteration. Du kan se forskellen i responstid her, når en forbindelsespulje bruges til at udføre databasehandlinger. Svartiden ved brug af en forbindelsespulje er væsentligt lavere på grund af det faktum, at forbindelsespuljen initialiseres én gang og genbruger forbindelsen i stedet for at åbne og lukke forbindelsen for hver databaseoperation.

Den eneste bemærkelsesværdige forskel mellem Java- og Node.js Lambda-funktioner er koldstartstiden.

Hvad er koldstartstid?

Kold starttid refererer til den tid, det tager AWS Lambda-funktionen til initialisering. Når Lambda-funktionen modtager sin første anmodning, vil den initialisere beholderen og det påkrævede procesmiljø. I ovenstående grafer inkluderer responstiden for anmodning 1 koldstartstiden, som afviger væsentligt afhængigt af det programmeringssprog, der bruges til AWS Lambda-funktion.

Behøver jeg at bekymre mig om koldstartstid?

Hvis du bruger AWS API-gatewayen som en udløser for Lambda-funktionen, skal du tage koldstartstid i betragtning. API-gateway-svaret vil fejle, hvis AWS Lambda-integrationsfunktionen ikke initialiseres inden for det givne tidsinterval. API-gateway-integrationstimeoutet varierer fra 50 millisekunder til 29 sekunder.

I grafen for Java AWS Lambda-funktionen kan du se, at den første anmodning tog mere end 29 sekunder, og derfor fejlede API-gateway-svaret. Kold starttid for AWS Lambda-funktion skrevet ved hjælp af Java er højere sammenlignet med andre understøttede programmeringssprog. For at løse disse problemer med koldstartstid kan du affyre en initialiseringsanmodning før den faktiske påkaldelse. Det andet alternativ er at prøve igen på klientsiden. På den måde, hvis anmodningen mislykkes på grund af koldstartstid, vil forsøget igen lykkes.

Hvad sker der med AWS Lambda-funktion under inaktivitet?

I vores test observerede vi også, at AWS Lambda-hostingcontainere blev stoppet, da de var inaktive i et stykke tid. Dette interval varierede fra 7 til 20 minutter. Så hvis dine Lambda-funktioner ikke bruges ofte, så skal du overveje at holde dem i live ved enten at affyre hjerteslagsanmodninger eller tilføje genforsøg på klientsiden.

Hvad sker der, når jeg aktiverer Lambda-funktioner samtidigt?

Hvis Lambda-funktioner påkaldes samtidigt, vil Lambda bruge mange containere til at betjene anmodningen. Som standard giver AWS Lambda ureserveret samtidighed på 1000 anmodninger og kan konfigureres til en given Lambda-funktion.

Det er her, du skal være forsigtig med forbindelsespuljens størrelse, da samtidige anmodninger kan åbne for mange forbindelser. Så du skal holde tilslutningsbassinets størrelse optimal til din funktion. Men når containerne er stoppet, frigives forbindelser baseret på timeout fra MongoDB-serveren.

Konklusion om AWS Lambda Connection Pooling

Lambda-funktioner er statsløse og asynkrone, og ved at bruge databaseforbindelsespuljen vil du være i stand til at tilføje en tilstand til den. Dette vil dog kun hjælpe, når beholderne genbruges, hvilket giver dig mulighed for at spare en masse tid. Forbindelsespooling ved hjælp af AWS EC2 er nemmere at administrere, fordi en enkelt instans kan spore tilstanden af ​​sin forbindelsespulje uden problemer. Brug af AWS EC2 reducerer således risikoen for at løbe tør for databaseforbindelser markant. AWS Lambda er designet til at fungere bedre, når den bare kan ramme en API og ikke behøver at oprette forbindelse til en databasemotor.


  1. MongoDB-serveren kan stadig tilgås uden legitimationsoplysninger

  2. Sletning af en nøgle/værdi fra eksisterende MongoDB-indgang

  3. Opret en database i MongoDB

  4. PyMongo -- cursor iteration