Using AWS Lambda in CFML: Debugging, Observability, and More

Posted 1 March 2019

Developing for AWS Lambda, or Google Cloud Functions, or Azure Functions, is a core part of what is known as “serverless” development. You focus on application logic and workflow, and never really worry about the servers that power the execution of that code. It’s a powerful, liberating development model, but not one without issues. It’s a maturing space, and like all maturing spaces, has pain points. This post explores a couple of those pain points: debugging, observability, and planning the right execution environment. I’ll wrap up by talking about potential intersection points between CFML applications and Lambda.

Debugging Lambda Functions Called from CFML

Debugging Lambda function execution is one of the biggest pain points in Lambda development right now. Because you don’t have access to the execution environment for your Lambda function, you can’t really see what happens when something goes wrong. You’ll just be told that something went wrong.

There are a couple strategies for dealing with this limited debugging ability:

First, you can console.log() information during function execution to help you see what’s happening. When you log from within a Lambda execution environment, regardless of the language used, that information goes to AWS CloudWatch, AWS’ core logging and metrics reporting service. The pain here is that you have to switch from your local development environment (if you’re developing locally) or the Lambda console in AWS over to CloudWatch, and then search for the log output in CloudWatch. The CloudWatch console provides some rudimentary tools for searching for log output, but it’s often like searching for a needle in a haystack.

Second, you can develop your Lambda function locally. All officially supported Lambda languages are available to run locally. There are two problems with this:

  1. You can’t really call other AWS services from the local environment.
  2. Your function has to be adjusted and rewritten for the Lambda execution context.

You can mitigate the first issue by using aws-sdk-mock, a very handy NodeJS package for mocking out AWS services in your tests. There are similar mocking and local execution libraries for other languages. In Python, for example, you’d likely use moto. The Serverless framework offers the Serverless offline plugin to develop your Serverless framework apps locally. AWS SAM offers the SAM CLI to test Lambda functions locally.

The second issue, where your function ultimately has to expect the standard event, context, and callback objects, is somewhat more problematic. Again, if you’re not using the Serverless framework or AWS SAM, you’re going to have to figure this one out, and either mock things locally or write locally, upload to Lambda, run your function, test the results, and repeat the process over and over and over. That’s a lot of time wasted.

Both of these problems can be mitigated, in part, by developing in the Lambda console itself. The Lambda console includes the excellent AWS Cloud 9 editor, and offers integrated debugging for NodeJS Lambda functions. You get breakpoints, watch expressions, and many other tools you’d see in the browser while developing JavaScript locally. This is a very handy way to develop and debug your NodeJS Lambda applications.

As you’re calling Lambda functions from CFML, I would strongly recommend the following workflow:

  1. Build, test, and debug your Lambda application separately from your CFML application.
  2. Build, test, and debug your CFML code using a mocked call to Lambda.
  3. Build, test, and debug the code which invokes the AWS Java SDK from your CFML to call Lambda.

Error messages from the AWS Java SDK aren’t always very helpful — particularly in the CFML execution context. You’ll often get “can’t find this method” messages even though the method is in the AWS Java SDK. This is most often the result of overloaded Java method signatures, and you just have to provide the right kinds of arguments to the method call.

Observability When Calling Lambda Functions from CFML

As with debugging, observability of your Lambda function execution is pretty limited. You do get a little bit of information in the context object that’s in every Lambda function invocation. You also get basic metrics in the AWS Lambda console itself. A number of companies offer wrappers for your Lambda functions that tie in to their logging and observability platforms. DataDog, Epsagon, IOPipe, and more offer toolkits to bring broader observability to your Lambda function executions.

AWS also offers X-Ray, an integrated tracing package that can show you the time it takes for a call to run across all AWS services that are requested. X-Ray is hugely helpful if you are making calls from your Lambda function to other AWS services. The visualizations (service map) provided by X-Ray are excellent for discovering bottlenecks in your serverless application workflows.

On the CFML side, Lambda is a black box. You make a request, you get a response. That’s pretty much it. There’s little that you can really see about the Lambda function execution from within your CFML application. You can run a getTickCount() before and after your Lambda function invocation to see how long things take, but that’s about the limit of observability from within your CFML application.

Lambda Execution Environment Configuration and Speed

One of the few things you can actually configure about your Lambda execution environment is the amount of memory available to the execution environment. Memory allocation has a direct and measurable impact on the speed of execution of your Lambda function, regardless of language. Much has been written about this issue, but here’s an example of how increasing memory allocated to your Lambda execution environment affects performance. The basic principle is this: the more memory you allocate (and therefore pay for), the faster your Lambda function will run. There are limits to this, of course, as most Lambda functions stop seeing performance gains around 2GB of allocated memory.

This does not mean that you should allocate 2GB of memory for all of your functions. If you have simple functions that are processing small JSON objects, you can likely get great performance with 128MB of RAM. If you’re processing 5MB CSV files on every Lambda function invocation, you probably want more memory to increase performance. Either way, you have to test your Lambda function, and see the results of execution time in the Lambda console or whatever observabilty platform you choose to monitor your Lambda executions.

Where Lambda and CFML Intersect

As I mentioned in the first post, the power of Lambda and related serverless development environments is enormous. It’s a hugely freeing development model. You can do pretty much anything you want in a serverless model, but it’s not the right tool for every job.

Most of us have existing CFML codebases. Lambda, and the whole serverless development model as a whole, gives us many options to add in entire classes of functionality that would otherwise be impossible or impractical from within the CFML runtime. Here are some examples:

I’m big on Lambda and other serverless execution environments. Hopefully this series has shown you how easy it is to get started with serverless functions, and inspired you to try building hybrid CFML-Lambda workflows on your own. As always, if you have any questions about any of the posts in this series, feel free to message me on the Twitter!

Categories: AWS ColdFusion