Let’s take a moment to compare and contrast the AWS Lambda supported languages. In this post, we’ll take a look at these languages from some angles:
- Cold start performance: performance during a cold start
- Warm performance: performance after the initial cold start
- Cost: does it cost you more to run functions in one language over another? If so, why?
- Ecosystem: libraries, deployment tooling, etc.
- Platform support: what support do other function-as-a-service (FaaS) platforms offer for the language?
Now that AWS Lambda has added PowerShell to its growing list of supported languages, this is more interesting than ever. We will also talk about specialized use cases such as Machine Learning (ML) as well as paying attention to the unique needs of the enterprise. Finally, we’ll round off the discussion by looking at a few not-officially-supported languages.
I should stress that the goal of this post is to consider the relative strengths and weaknesses of each language within the specific context of AWS Lambda. It is not a general purpose language comparison!
Comparison of Officially Supported AWS Lambda Languages
Cold Start Performance
In 2017, I analyzed the effect of AWS Lambda language runtime, memory, and code size had on the cold start performance. At the time, Go was not yet supported and C#, .NET Core support was still at v1.0, but several subsequent analyses by others that included these newer runtimes reached similar conclusions.
The typical finding is that Python, Node.js, and Go, have far superior cold start performances compared to C# and Java. They are often an order of magnitude better. It makes intuitive sense and corresponds with my personal experience with these languages.
How Programming Languages Affect Cold Start
Node.js and Python both have interpreters and they have a lightweight runtime as well. It is why they have great cold start performances.
Go’s compiler uses tree shaking to bundle only code that is used by your application. Additionally, the bundled code is compiled to native as well. This leads to very little initialization overhead during a cold start.
With both C# and Java, Lambda has to do a lot of extra work during a cold start. It needs to bootstrap a massive virtual machine (VM) and language runtime. In addition, it has to load all the classes and resources defined in your compiled binary even when you’re not using them in your code.
This overhead has always existed for these languages, but they were usually written off in traditional application development. Before AWS Lambda came along, these applications ran on long-lived servers, didn’t serve user requests until they fully initialized, and passed load balancer health checks.
With AWS Lambda, that’s no longer the case. These initialization overheads are now felt by your users, which is why I’d recommend avoiding Java and C# for APIs. Alternatively, you can throw more CPU (and therefore more money!) at the problem by using higher memory settings for these functions to help improve the cold start time.
Adding latency is not the same when talking about background processing. Even the 3-7 seconds of cold-start time in Java functions is usually not a big issue since it is not user-facing.
My main takeaway from the report is that there is no meaningful difference between the warm performances of the different languages. Of course, don’t take it as a general statement about these languages. After all, we are talking about a particular context here:
- Requests processing is done one-by-one in the container, so even the most efficient concurrency model would not offer any meaningful gains.
- Most functions are small and IO-heavy, and no amount of optimization in the language runtime is going to make the network go faster.
As discussed above, C# and Java functions have a significantly higher cold start time. Also, they also tend to have a higher memory footprint as well. Putting the two together means that you will likely have to run these functions on a higher memory setting than their equivalents in Node.js, Python or Go, which will affect the cost.
Since both execution time and memory affect the cost of AWS Lambda, you will likely pay more for these C#, and Java functions as a result. It is particularly true given that warm performances are indistinguishable between different languages, especially for the type of IO-heavy workload we usually see with Lambda. That said, if strong library support for specialized workloads (such as image processing or machine learning) can make your function run a lot faster, you might be able to compensate for the higher memory allocation.
VPC and the AWS Lambda Language of Choice
Another unfortunate side effect of running on higher memory settings for C# and Java functions is that they’ll require more ENIs when placed inside a VPC. Although Lambda reuses ENIs where possible, the formula for calculating your ENI capacity requirement tells us that a higher memory setting equates to needing more ENIs.
Needing more ENIs means longer cold starts more often. A 1GB function would need to create a new ENI during one-third of cold starts, whereas a 1.5GB function would need to create a new ENI during one-half of cold starts.
All five officially supported languages are widely adopted and enjoy a vibrant ecosystem of both open-source as well as commercial libraries. Serverless Framework and Stackery support all five languages.
Generally speaking, there are many language-agnostic deployment frameworks out there, including AWS’s very own Serverless Application Model (SAM) framework. In fact, why not check out this post by Nitzan Shapira, which offers an informative roundup of many deployment frameworks! Check out the various use-cases for DevOps, as well.
Azure Functions has a wide range of supported languages, but the 1.x version of Node.js is stuck at 6.11, and Python support is only “experimental.” Judging by the published product roadmap, there is no planned support for Go in the next version of Azure Functions either.
Considerations for Specialized Use Cases
For specialized use cases such as machine learning or deep learning, a good, highly optimized library can make all the difference. It seems that Python-based libraries dominate the scene with the likes of TensorFlow, scikit-learn, Caffe2, and Keras.
If you’re working within the data science space, the chances are that the libraries that you’re familiar with and want to use would dictate the language in which you would author your Lambda functions.
Considerations for the Enterprise
Similarly, for the enterprise user, company rules would likely take precedence over technical merits when it comes to choosing the language you should use with Lambda. While this can sound like company politics, these restrictions often exist for a good reason.
It is notoriously difficult to enforce any form of standardization and governance across a large, distributed, and often siloed engineering team. It is necessary to ensure everyone adheres to guidelines and best practices.
In addition, we need production systems to meet the required quality standards. To achieve this, we often need to rely on shared libraries and tooling support. To provide consistent support for multiple languages is a massive burden on the teams that maintain these shared libraries and tools.
Another common theme among enterprises is to run your AWS Lambda functions inside a VPC to stay compliant with the company’s security rules. In my experience, placing a function inside a VPC can add as much as 10s to its cold start time. It is because creating an ENI is a particularly expensive and complicated step.
It brings us back to the point mentioned above about higher memory settings equaling more ENIs and more frequent slow cold starts. With these settings, you should perhaps avoid C# and Java functions due to the impact of cold starts. Ironically, it is sometimes a necessity to use C# or Java in these enterprise environments.
Unofficially Supported AWS Lambda Language
Since AWS Lambda supported languages addition of Java and C# are at the runtime level, i.e., the JVM and .NET Core, any other languages that can run on these VMs are supported.
JVM brings unofficial support to Kotlin, Clojure, Scala, and Groovy. The Serverless framework already has templates for all four! The same is true for F#, a function-first language that runs on .NET Core.
Besides these semi-officially supported languages, the community has cleverly exploited different techniques to add support for other languages. The interoperability options in Python 3.7 and Go are some of them. Here are a few of the more noteworthy examples:
- Rust: the rust-crowbar project with a Python shim. You can also try the rust-aws-lambda project which leverages the Go Lambda runtime. Clever!
- Elixir: the exlam project, which uses a Node.js 4.3 function to wrap an installation of the Erlang VM and an Elixir app by starting it as a child process.
- Haskell: the serverless-haskell plugin to the Serverless framework, which also uses a Node.js wrapper.
Language Choice in AWS Lambda: Summary
The most significant technical reason for picking one language over another is probably the cold start performance. It also has a knock-on effect on the memory setting and, therefore, ENI capacity requirements. It, on itself, also impacts cold starts.
This cold start performance is mainly relevant for APIs, where the added latency is user-facing. As I discussed in the previous post, many of the famous use cases for Lambda do not have user-facing latency. Therefore, they can tolerate even the most brutal cold start times.
Aside from cold starts, other factors such as library support and company restrictions can also influence your language choice. By and large, though, I think you’ll find folks would naturally gravitate towards languages with which they are most comfortable. The extraordinary efforts many have put in to run those unofficially supported AWS Lambda languages are evidence to that.
I’m confident that with time, limitations such as cold starts would just go away. This will open the AWS Lambda platform even more. The AWS Lambda Layer technology provides a way to run almost any languages on AWS Lambda. This will put all these discussions around what languages to use behind us.
I strongly believe that you can build a great product with any programming language. The fact that our code now runs inside a Lambda function should not change that!