If I had 1/25,000th of a penny for every time I called a function

TL;DR: The flat cost of invoking serverless functions can be a lot more than the execution of the functions. For short lived functions, choose patterns that reduce calls. When using storage bucket based triggers, use multiple buckets to avoid unnecessary invocations. Use the pricing calculators and read the pricing information.

This is another one of my classic newbie tales of nickels and dimes adding up in the cloud. In this case it was in Google Cloud, but the same applies to Lambda functions in AWS as well when dealing with buckets.

A small scale project I once worked on involved the typical process of creating a set of thumbnail images from source images regularly uploaded to a bucket. Using a scheduled Compute Engine or similar to batch process them did not fit our needs or our approach, so serverless functions were decided on. This project was using Google Cloud so Cloud Functions with a Storage bucket was the obvious choice.

Setting up the functions was quick and painless. When a new image file was created, a NodeJS function triggered to generate the thumbnails. When a source image was deleted it would delete the thumbnails. There is no way to have it only trigger on a given pattern within a bucket, so to avoid endless execution loops, where the function would create new thumbnails from new thumbnails, we put a guard clause around the path and filename. This would make sure the function returned early and not waste execution time and trigger new functions.

After some early experiments with creating ~1M thumbnails, the performance and cost appeared acceptable and it looked like it would suit our needs. There would be some upfront costs in the beginning as the initial set of images were uploaded and processed, but everything seemed in order. As things settled into day-to-day levels, the Billing Report showed the cost of Cloud Functions jumping up suddenly.

A jump was expected, as we knew we would exceed the allotted free calls, but the amount wasn’t what was of concern, it was the efficiency and efficacy of what we were spending it on. Looking at the SKU cost breakdown we could see only 15% of the Cloud Function cost was for the resources involved in the execution. Our functions were small, quick and the ones that returned early near instant. The remaining 85% was all cost for invocation. The pricing documentation had mentioned (and cautioned about) the flat cost of $0.0000004 per invocation. But there were bigger, pricier and more concerning things than that quick thumbnail generator burning less than a dollar per millions calls to worry about, so the warnings fell to the wayside.

After a quick trip to the official docs, we did as recommended and reworked our app and Cloud Functions to use multiple buckets. The thumbnails would now be created in a separate bucket and avoid invoking the function at all and served from a slightly different URL. A bit over 24 hours of transferring files to the new buckets and we were good to go. Invocation calls were down to a fraction of what they were before and our spend was now better focused on the services that provided far greater value to the user.

The rather obvious lesson learned that day was when it comes to serverless architectures, inefficiencies in the performance and complexity of our code and patterns, can translate very directly into dollars and cents. Wasting an extra function call and returning early for that click handler or that recursive function might be add up in cpu and memory cost and PR scrutiny, but it really sticks out when it starts costing a flat amount each time, even if that amount is 1/25,000th of a cent.