typelevel / typelevel/feral

Make Feral more amenable to SnapStart optimization?

Open
#299 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Scala
Stars
195
Forks
53
PR merge metrics
No merged PRs in 30d

Description

A few weeks ago AWS announced SnapStart, a feature to improve cold-start performance for JVM Lambdas. A few lines of config to make my Lambdas magically faster? Yes please!

With SnapStart, Lambda initializes your function when you publish a function version. Lambda takes a Firecracker microVM snapshot of the memory and disk state of the initialized execution environment, encrypts the snapshot, and caches it for low-latency access. When you invoke the function version for the first time, and as the invocations scale up, Lambda resumes new execution environments from the cached snapshot instead of initializing them from scratch, improving startup latency.

My understanding of that paragraph is that they will "initialize" by calling the constructor on the handler class, but they won't invoke the Lambda, i.e. call the handler method.

Unfortunately (if I'm understanding Feral's code correctly) it appears that Feral Lambdas won't benefit much from this optimization. As I understand it, Feral does pretty much nothing at class initialization time. The resource defined in def init is acquired the first time the Lambda is invoked, and then memoized for reuse by subsequent invocations. So the SnapStart snapshot will capture the JVM startup and a bit of classloading, but none of the work performed while acquiring the init resource.

It would be nice if we could make everything that happens in IOSetup happen eagerly at class init time to take full advantage of SnapStart.

For now we can emulate this in user-land by eschewing def init and just doing a good old unsafeRunSync:

class MyLambda extends IOLambda.Simple[KinesisStreamEvent, INothing]:
  type Init = Unit

  private def buildAlgebra: IO[MyAlgebra[IO]] = ??? // the stuff that would usually go in `def init`

  private val algebra: MyAlgebra[IO] = buildAlgebra[IO].unsafeRunSync()

  override def apply(
      event: KinesisStreamEvent,
      context: Context[IO],
      init: Init
  ): IO[Option[INothing]] = algebra.process(event, context)

Disclaimer: I haven't done any benchmarking with SnapStart and Feral yet.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Feral's IOSetup and the def init lifecycle described in the issue, then compare that behavior with AWS Lambda SnapStart's class-initialization model. Determine how resource acquisition could occur before the first invocation and how its behavior should be validated, including the benchmarking that the issue notes is currently missing.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, scala
Domain
backend, cloud
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.