The Good the Bad & the Ugly - AWS Service Deep Dive

When building a lambda, we have the option to put variables outside of the handler. Anything that we store outside of the handler is accessible to all future lambda invocations that use that container. This can be used very well or can cause hard to find bugs. This article and video will help you understand these global variable so you can use them deliberately and correctly.

Correct Use of Global Variables

You can use global variables to cache responses from APIs or databases.

let cachedData = {};


exports.handler = async (event) => {
    if (cachedData && cachedData.expiresOn > Date.now() ) {
         const response = {
            statusCode: 200,
            body: `The weather is ${cachedData.weather} and it is ${cachedData.temperature} decrees Celcius`,
        };
        return response;
    }
    
    const { weather, temperature } = await fakeWeatherAPIRequest(event);
    
    cachedData = {
        weather,
        temperature,
        expiresOn: Date.now() + 30 * 1000 
    };
    
    const response = {
        statusCode: 200,
        body: `The weather is ${weather} and it is ${temperature} decrees Celcius`
    };
    return response;
};


const fakeWeatherAPIRequest = async (event) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({
                weather: 'sunny',
                temperature: '15'
            });
        }, 3000);
    });
};

Just make sure the thing that you are caching isn't changing very quickly and that you the customer isn't passing up a parameter which would make your cached data wrong.

There are libraries such as lambda-local-cache which do a lot of the heavy lifting and allow you to cache a data locally.

When Global Variables Cause Bugs

If you are relying on the data in a global variable to be consistent then you need to be very careful. If you ever reference the value of one of those variables and then change that value, it can cause bugs in future invocations of that lambda.

let config = {
    minimumScore: 50,
};

exports.handler = async (event) => {
    
    const { score, mode } = event;
    
    let minimumScore = config.minimumScore
    
    if ( mode === 'hard') {
        minimumScore = 25;
    }
    
    if (score < minimumScore ){
        return `You need to score higher to get onto the ${mode} leaderboard. ${score}/${minimumScore}`;
    }
    
    return `Congratuations! You made it onto the ${mode} leaderboard. ${score}/${minimumScore}`;
};

If we invoke this lambda with a user like playing on easy mode with a score of 30, we would expect the response to be: You need to score higher to get onto the easy leaderboard. 30/50. This is what you'd expect from reading the code.

If you then have a player on hard mode with a score of 45 you'd also expect a result of Congratuations! You made it onto the hard leaderboard. 45/25.

When the first player submits a new score of 32 on the easy mode to the same lambda we get a bug. Congratuations! You made it onto the easy leaderboard. 32/25. There are a few things wrong with this. The player only got 32 so didn't beat the easy leaderboard level of 50. They message says 32/25 when 25 isn't the easy score to beat.

This is because when the player on hard submitted the score, this line fo code ran: minimumScore = 25;. This variable references the global variable which has now set to the global minimum score to 25. When the easy player submits a score of 32, the global minimum is 25, hence the congratulations message.

This mutation of the global variable can cause a range of bugs like this, but now you know about it you'll be better equipped to find it and fix it.