Emitting AWS CloudWatch metrics from AWS Lambda within synchronous functions

Viewed 285

I'm looking for a solution whereby I can safely (and nearly reliably) emit CW metrics (putMetricData) within synchronous code. My use case is as follows:

// Lambda Main Handler

export const handler = async () => {
    await step1Aync();
    step2Sync();
    step3Sync();
    return {};
}

async function step1Aync() { 
    await doSomething();
    await doSomething();
    // could await this call, but dont want to since I dont need the value, nor do I want to handle failures
    cloudwatch.putMetricData();
}

function step2Sync() { 
    doSomething();
    // do not want to make this function async to use await here
    cloudwatch.putMetricData();
}

function step3Sync() { 
    doSomething();
    // do not want to make this function async to use await here
    cloudwatch.putMetricData();
}

My understanding is that as the putMetricData requests are pushed onto Lambda's Event Loop, there is no guarantee that they end up sending their HTTP request to CW (as Lambda execution context may freeze before the request happens). That is of course, unless I make step2Sync and step3Sync asynchronous and use await for each Promise.

Part of my reasoning here is that I'd like reduce the response time of the Lambda by specifically not handling the responses from CloudWatch. This Lambda is a time-sensitive API (API Gateway). If 'some' requests to CW fail (less than 1%), thats okay.

I read that if the same Lambda Context is used again, it will empty the event loop. This is however, NOT guaranteed. I dont know what the SLA here is, so to speak.

Any who, in short, I dont want to use async functions when I don't explicitly have to. Is my understanding here correct?

1 Answers

Possible solution:

Define a global array that is continuously added to, and then flush the array to CloudWatch in one Promise.all call at the end of the function. We still have to wait for the calls, but its done concurrently or in parallel at the end (depending on the underlying silicon).

// global metrics array

export const PENDING_METRICS: PutMetricDataInput[] = [];

// handler

// Lambda Main Handler

export const handler = async () => {
    await step1Aync();
    step2Sync();
    step3Sync();
    
    // emit all of the metrics
    await Promise.all( PENDING_METRICS.map(emitMetricToCloudWatch) );
    
    // clear the global array for safety
    PENDING_METRICS.length = 0;
    return {};
}

async function step1Aync() { 
    await doSomething();
    await doSomething();
    PENDING_METRICS.push(PutMetricDataInput)
}

function step2Sync() { 
    doSomething();
    PENDING_METRICS.push(PutMetricDataInput)
}

function step3Sync() { 
    doSomething();
    PENDING_METRICS.push(PutMetricDataInput)
}

// CloudWatch Code

export const emitMetricToCloudWatch = async (client, input) => client.putMetricData(input);

Note: Global objects can bleed over into other Execution Contexts. The array therefore needs to be emptied after each metric is emitted.

Related