How to Memoize/Cache Lambda Function

I have this situation, where in I don’t have the required API for the front end I am trying to implement. So I’m trying to solve this by having an intermediary cloud function that fetches all data from the original backend server and then does a simple filter to generate the data i need in my front end. The only issue with this approach is this, since I am fetching all data from the backend on every call, this becomes heavy on the original backend server in terms of database reads. Also I’m having to wait for the call to finish from the front end.

How can i Memoize my Netlify AWS Lambda Function, such that it periodically fetches data from the original server, say once every 2 hours and then caches it, so when i call from my front end the cloud function doesn’t have to talk to the original backend server and can just send me the cached data.

I understand that doing this will result in getting data that is old, ie, products that could be sold out. To mitigate this to some extend, I’m thinking I will call the original server to check the stock availability of the products returned by the Netlify AWS Lambda server. The only issue remaining is that when new Products are added they will have a delay period equaling the cache expiry time. I’m okay with that trade off, do let me know if there’s a better approach that can deal with that too, Thank You.

CODE

import fetch from 'node-fetch';
const {API_TOKEN} = process.env;

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

    const activeCategoriesRes = await fetch('https://api.example.com/category/GetActiveCategories',{
        method: 'GET',
        headers: {'APIToken': API_TOKEN}
    });
    const activeCategoriesData = await activeCategoriesRes.json();
    const categories = activeCategoriesData.result;
    const categoriesIdArray = categories.map(category => category.CategoryId);
    const productsNested = await Promise.all(categoriesIdArray.map(categoryID => {
        const categoryProductsRes = await fetch(`http://api.example.com/Product/GetProductSearchByCategory`,{
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'APIToken': API_TOKEN
        },
        body: JSON.stringify({
            "CategoryID": CategoryID,
            "Search": " ",
            "lstBrand": [],
            "lstColor": [],
            "lstSize": [],
            "PriceFrom": 0,
            "PriceTo": 0,
            "MaxPrice": 0,
            "Page": 1,
            "Show": 9999,
            "UserID": ""
        })
    })
    const categoryProductsData = await categoryProductsRes.json();
    return categoryProductsData.result;
    }));
    const productsList =  productsNested.flat();
    const hotDeals = productsList.filter(product => product.DiscountPercentage > 15 && product.SellingPrice > 50);

    callback(null,{
        statusCode: 200,
        body: JSON.stringify(hotDeals)
    })
}

I came up with this solution, I don’t if it’s a proper implementation, please let me know if there is a better implementation.

import fetch from 'node-fetch';
const {API_TOKEN,USER_ID} = process.env;
let data = [];
let lastUpdate = 0;

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

	const fetchHotDeals = async () => {
		try{
			const activeCategoriesRes = await fetch('http://api.example.com/category/GetActiveCategories',{
			method: 'GET',
			headers: {'APIToken': API_TOKEN}
			});
			
			const activeCategoriesData = await activeCategoriesRes.json();
			const allCategories = activeCategoriesData.result;
			const subCategories = allCategories.filter(category => category.ParentCategoryId !== 0);
			const categoriesIdArray = subCategories.map(category => category.CategoryId);
			
			const productsNested = await Promise.all(categoriesIdArray.map( async categoryID => {
				const categoryProductsRes = await fetch(`http://api.example.com/Product/GetProductByCategory?CategoryID=${categoryID}&UserID=${USER_ID}`,{
					method: 'GET',
					headers: {'APIToken': API_TOKEN }
				});
				const categoryProductsData = await categoryProductsRes.json();
				return categoryProductsData.result;
			}))
			
			const productsList =  productsNested.flat();
			const hotDeals = productsList.filter(product => product.DiscountPercentage > 15 && product.SellingPrice > 50);

			return hotDeals;
				
		} catch (error) {
			callback(new Error("unable to generate hot deals"))
		}
	}

	const currentTime = new Date().getTime();

	if(data.length === 0 || (currentTime-lastUpdate > 3600000)){ /* 3600000 = 1hour */

		const hotDeals = await fetchHotDeals();
		data = [...hotDeals];
		lastUpdate = new Date().getTime();

		callback(null,{
			statusCode: 200,
			body: JSON.stringify(data)
		});

	} else {
		callback(null,{
			statusCode: 200,
			body: JSON.stringify(data)
		});
	}
	

}

Hi,

Lambda functions are intended to be stateless, you can’t guarantee that the data will be there or even that it will run on the same server it ran on before. That said, any variables you establish outside of your handler function will cache for a time on that single server, and future requests will use it until the server wipes the cache. There are no promises that it will be there or how often or how long. One more thing you can do is pass a custom cache control header and we’ll cache the response, again there is no guarantee on how long. Ultimately the better option would be to make a proper backend, or if you need caching for products try standing up a redis instance or any other database as a service and querying that instead.

4 Likes

I am assumng this is how you set cache headers, please correct me if am wrong

callback(null,{
			statusCode: 200,
			headers: {'Cache-Control': 'public, s-maxage=1800'},
			body: JSON.stringify(data)
		});

Yup, that’s right @siliconchild! Let us know if you still have trouble.

Hi. Just wondering if there’s any way to avoid this temporary caching of variables outside of the handler function. Thinking of cases where you’ve got a function and you wouldn’t want anything cached. Or is it a case of keeping anything where there would be a risk from cached results within your handler function?

Hi @katcoutts,

As mentioned, any code outside the handler function would be cached. So, you could write stuff inside the handler function and that should not be cached.