I’m looking to create a low-traffic web site and have been thinking that it would be a good project to try with AWS Lambda. What’s the current best thinking about how to use Clojure on Lambda? I’ve been Googling and reading some blog posts about it, but most of them are at least 2 years old. And in this world, 2 years is pretty much an eternity. To beat the Lambda cold-start lag with Clojure running on Java, it seems like folks have gravitated toward either Clojurescript on Node or Clojure on GraalVM. Do folks have any particular feelings about those, or something else that might have come to fruition in the mean time? It seems like some of GraalVM’s issues with Clojure’s dynamism might have gotten a lot better in the last year or so. Anyway, let me have your best thinking. Thanks!
@daver Can you explain more what this website should do?
Some pointers on JVM Clojure and GraalVM for instant startup:
- babashka can be used for very basic sites that should just serve some HTML and JS for example. It starts instantly. It now comes with http-kit server, hiccup and core.match (you can build some simple routing out of this). This way you can prototype and not use GraalVM from the start. If babashka doesn’t have something you need, or the performance isn’t good enough, you can “outgrow” to the JVM and indeed compile with GraalVM for similar startup. The babashka project can also be seen as a collection of libs that work with GraalVM native-image.
- clj-graal-docs contains tutorials for Clojure and GraalVM native-image
- graalvm-clojure gives an overview of libraries that tend to work with GraalVM native-image
You can also use normal Clojure and set Lambda reserved instances. You can configure it to scale up/down at different times of day.
It’s not as good as.full on Lambda scale, but it can help, if you know you’ll be getting consistent traffic.
Can you explain more what this website should do?
@borkdude, website would just be a basic database backend. Lambdas would just service web requests, fetch data from DynamoDB, and render pages. Pretty standard stuff, nothing fancy. Thanks for the pointers. I’ll check out Babashka. I had always thought of it more as command line scripting, not in this context, but that’s interesting what you’ve added to it.
@didibus, yes, I know that’s an option, but this is all about a hobby project with very small traffic, so I was trying to avoid generating any charges, presuming that traffic is low and stays low. But yes, maybe later on.
If you want to deal with other amazon services, take a look at pod-babashka-aws.
I tinkered around with various solutions using clojure on aws lambda. Plain old clojure jars were far to slow on cold starts. Native binaries compiled with graal using a custom runtime, had excellent startup time, but took several minutes to compile. I had high hopes for a custom runtime using OpenJ9 jvm which has a startup class cache which does significantly help startup time when run locally, but for whatever reason or I couldn’t get it working or to make any difference on the lambda custom runtime and lowest cold start for a hello world was in the 4-5 second range. So for clojure graal is your only option, but being an oracle product isn’t something I’m super keen on. There is some hope for native compiled java outside of graal I think as well at some point in the future.
Thanks for the info. Actually, my other alternative to Lambda was perhaps using a very small EC2 instance using J9 to keep the memory footprint low. That would be always-on, but I thought that would run the possibility of not being able to keep the memory size within the free tier footprint. So, Lambda seemed like a better option if the cold start delay could be mitigated. Plus, it’s fun to experiment.
It sounds like part of your motivation is to explore AWS, but if you’re open to other hosting options then I’ve found that you can host hobby-size clojure web apps for free with Jelastic (sort of like Heroku but cheaper) - blog post.
@tobyloxy, thanks! I’ll check it out.
I’m pretty happy with running ClojureScript on NodeJS in Lambda.
The amount of libraries in the NodeJS ecosystem together with ShadowCljs’s NPM support really makes for a nice experience.
@daver I did this very recently to get around cold start times. The business logic of the lambda went in one project. In a second project I built a command line wrapper with a -main method to call the business logic library. Using graal via clj.native-image was fine. The dynamism story does seem to have improved since I last used Graal a couple of years ago and there were no problems there. I use MacOS so had to build the native image in a Docker container; bit fiddly but all scripted up now.
I put the native image in a lambda layer and then wrote a very simple lambda in Python to shell out to it.
In terms of cold start times, very much improved; ~160ms vs several seconds with an Uberjar lambda.
Overall I felt it was worth the extra steps, especially as it’s mostly a one off investment to script them. Of course, all the scripts are written in babashka!
I’m late to this post but another way is AWS Elastic Beanstalk. Nice because you can keep it small and cheap but scale if needed. Works great with Clojure!
There are a lot of different options if you would like to use Clojure with AWS Lambda. It all depends on what you want.
For a fast startup, you can use babashka or GraalVM native-image. Both will require custom runtime, but the latter will be slightly faster. On the other hand, native-image requires some experience with GraalVM and it’s a challenge to write a code that is easily compiled to an executable.
I’m biased since it’s my project, but I would highly recommend trying holy-lambda out. Some comparison of runtimes is here if you’re interested : holy-lambda/1-01-whatits.md at master · FieryCod/holy-lambda · GitHub.
I have a start to finish example of doing a simple (but complete) reagent app on AWs Lambda.
There is also a reddit discussion where I get a bit more into the weeds.
Hope this helps :-).
Yes, I saw that on Reddit and have already bookmarked the GitHub repo. Good stuff, thanks!
I’ve helped ship four or five services on Lambda over the past four years. It’s definitely doable, and I believe quite cost-effective in terms of AWS billing, but there are some caveats:
- startup time is a real limitation, in some case going well over 30 seconds if you have a lot of code. This means you shouldn’t use it for anything interactive. In both cases we had a user-facing component, which suffered when we deployed a new version or when load scaled upwards (provisioning new containers or their equivalent under the hood, I presume). If your use case allows Babashka, that could be worth a try.
- jar size was quite limited for awhile; it may be much better now, but check. We had to do various tricks such as excluding unneeded transitive dependencies in our projects.
- execution time limits – you must make sure your function terminates in (I think) 15 minutes.
- troubleshooting a Lambda is generally harder, because more is hidden from you.
I would tend to avoid Lambda now for most things. Certain use cases still make sense; for example, various AWS services (SQS, Kinesis, …) deliver events as Lambda invocations (though you could poll the same queues from long-running programs).
After few years of developing holy-lambda I can now fully recommend trying it out.
The library solves a lot of drawbacks the official AWS Java Runtime had:
- for hello world on native backend ARM64 with memory size 512MB the cold start is around 100ms,
- the hello world on native backend binary size is about 11 MB (this means you still have 239MB more, before you hit the limit)
- it’s already used in production by many companies for: Integrations, Http/Rest Api, Websocket API, GraphQL with amplify and many many more!
- it’s relatively easy to deploy a ring application on AWS via holy-lambda-ring-adapter. See announcement
In the current company I’m working in our Ring API (that we develop locally via standard Ring tooling with almost 20 dependencies) has a
<= 500ms cold start on ARM64. The artifact size is around 80MB.