Writing Clojure handlers for AWS Lambda is not always as simple as it should be. For many years, Clojure developers used libraries such as uswitch/lambada
as a glue between Java and Clojure with mediocre results due to the cold starts and high memory usage.
PS: I don't claim the AWS Lambda or Serverless is superior to traditional servers, and I will never do. AWS Lambda is a viable choice in only a limited of tasks. Still, though, it's worth having Clojure support for low memory, high-performance Clojure handlers on it, and that's why holy-lambda exists.
Cold starts To understand what causes cold starts, we have to know how Clojure distributes program classes.
When Clojure code is compiled and packed to uberjar, every function expands to a class. Imagine that you have the main
function in your namespace, which you then uberjar for later execution. In standalone uberjar, you will find all Clojure core functions and your main
function compiled to classes. Upon java -jar
execution, both your main
class, and Clojure core classes are load. Loading the classes is mostly what makes high cold start times.
For only a single Java AWS Lambda handler class, the cold start is around ~1s. For Clojure, the cold start starts from ~8s on a 2GB memory-sized environment. The difference between cold starts comes from the number of classes load upon startup (Clojure > 100, Java > 1).
Holy Lambda (HL)
HL is a microframework for running Clojure on the AWS Lambda. HL is a deployment tool independent, although ships with bb tasks
for convenience.
HL supports at the time of writing three AWS Lambda runtimes. The first one is Clojure/Java runtime that is good for development, but a bad idea for production due to the cold starts and high memory usage of Java-based lambdas.
The second one is custom native runtime which utilizes GraalVM native image to provide fast startup and a low memory footprint. The tradeoff in using native runtime is the steep learning curve of GraalVM.
The last one is the babashka
runtime which is both fast and memory efficient. Babashka supports interactive development, and it's a great fit for beginners. It's fast enough, although it's not as fast as the native runtime. The tradeoff of using babashka is that not all of the Clojure language features are supported.
Holy lambda provides very convenient environment compared to other tools such as uswitch/lambada or babashka-lambda via simple, but powerful bb tasks
recipes eg. deployment is as easy as running bb stack:sync && bb stack:compile && bb stack:pack && bb stack:deploy
).
Features
Prior work towards targeting Java runtime was done by uswitch/lambada, but lacked being convenient. Holy lambda in the other hand is very convenient and does things which lambada lacked:
deflambda
OutputStream
.I've started experimenting with native runtime around May 2019 inspired by @hjhamala blog post. It was clear to me that there is huge potential in GraalVM which could be embraced in Clojure on AWS Lambda.
Benefits
Tradeoffs
bb native:conf
)sam invoke
or bb stack:invoke
Babashka runtime provides interactive development environment. There is no need for compiling the sources since those are provided as is to AWS SAM
. More info here.
If you feel overhelmed by holy-lambda ecosystem you can check very this minimal babashka runtime.
Runtime | Cold start | Performance | Artifacts size | Memory Consumption | Interactive | Compile time | Beginners friendly? |
---|---|---|---|---|---|---|---|
:native | low | high | high >= 16mb | low | No | very long | no |
:babashka | low | moderate | low >= 50kb | low | Yes | no compile | yes |
:java | high | high | moderate >= 12mb | high | No | long | yes |
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close