At AWS’s re:Invent a huge announcement was dropped for Lambda: custom runtimes! While Lambda already supported a number of good languages, like Node, C#, and Python, it is now possible to use a wide array of languages, as long as you are willing to build the custom runtime. Kelsey Hightower even famously showed off using Docker containers to create a runtime for Fortran.
While you can use Typescript by transpiling to a Node 6/8 runtime already available, it does have some downsides. Mostly, that any review/editing of your code through the Lambda console is considerably more difficult. While this isn’t something I find myself doing often, it is very handy in some situations and something I don’t like losing.
The initial work was easy, and largely based on the runtime put together for Node v10/11 here. It was pretty trivial to add a --require /opt/node_modules/ts-node/register and we were off to the races. However, the main issue at hand became very apparent quite quickly when running my simple test Lambda function, a simple ‘echo’ function. The final Report has some interesting numbers:
REPORT RequestId: 77d17761-98aa-4a28-9cc9-700a5d7b3d60 Init Duration: 1608.70 ms Duration: 1001.15 ms Billed Duration: 2700 ms Memory Size: 128 MB Max Memory Used: 128 MB
That was the cold-start time, almost 3 seconds. Not good! Too much time and already maxing out the memory. My test function was a simple echo, so that’s all runtime initialization! Subsequent runs were fine:
REPORT RequestId: 76ae5d3b-0aa0-49e4-b312-03c3d31cb0de Duration: 28.41 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 128 MB
So, still maxing the memory out with no actual logic. Not great. Can this be made better? Sure! Just have to up our memory allocation, since that also increases CPU and makes the Typescript compilation run quicker. At 1024 MB the cold-start time decreases somewhat:
REPORT RequestId: 4e125647-1b8a-48a5-943e-6bde2dfd792e Init Duration: 1507.07 ms Duration: 7.34 ms Billed Duration: 1600 ms Memory Size: 1024 MB Max Memory Used: 179 MB
But 1.5 seconds for the runtime warm-up still isn’t good. Let’s keep going, 2048 MB:
REPORT RequestId: f28e76e6-2c1b-4eae-b9b8-44c86c50c427 Init Duration: 1565.81 ms Duration: 9.46 ms Billed Duration: 1600 ms Memory Size: 2048 MB Max Memory Used: 157 MB
Wow, no apparent affect. So, we're not bottlenecked by memory or cpu. In fact, just bumping it up to 192MB is enough to get comparable results:
REPORT RequestId: f57119d8-bcc8-4b04-a15d-8d8e2dfda94c Init Duration: 1614.29 ms Duration: 53.32 ms Billed Duration: 1700 ms Memory Size: 192 MB Max Memory Used: 150 MB
So, there we go, that’s about the best we can do on reducing cold-start times. I think that’s just the nature of Typescript and TSC doing the compilation at warm-up. Unless they make a Typescript compiler that can work quicker or a straight-up Typescript runtime (and not just using Node), I don’t see this getting much better.
But, you do still get a Typescript runtime, which has some value, and if you can handle the cold-start times, then maybe this is a good option for you.
The repo can be found here. Follow the README and you can build a runtime.
So, there you go. You could build this Typescript runtime and build your Lambda function in pure Typescript. But, you’ll take a pretty big hit on every cold invocation and that really adds up. If these Lambdas were sitting behind an ALB or an API Gateway, I couldn’t recommend a paying client go this direction in most situations. Long cold-starts are something that have a very negative impact to the user. Perhaps this would be fine if you were triggering off of things like DynamoDB Streams or Kinesis where long cold-starts are less of an issue. Maybe in the future there will be some better options for getting Typescript into Lambda.