Jetpack: trace your way to faster and smaller Serverless packages

2 April 2020

Getting Serverless Framework applications to production as fast as possible is the serverless-jetpack plugin's unending quest. Our adventure now leads us to an exciting new development that offers:

  • 🚀 faster-than-ever-before packaging speed
  • 📦 the smallest possible application package
  • 🤩 25x faster and 4x smaller builds in real projects

Let's meet Jetpack's new tracing mode feature.

A packaging journey

Out of the box, the Serverless Framework bundles up the code in a Node.js application using a series of pattern inclusions/exclusions and implicit exclusion of unneeded development dependencies. The serverless-jetpack plugin dramatically speeds this up with blazingly fast production dependency discovery, producing equivalent packages often over 10 times faster.

Hitting the wall

Unfortunately, the underlying dependency-based Serverless approach can only go so far and so fast. For example, in one of our recent client projects, a seemingly straightforward Serverless project faced egregiously slow build times of over seven minutes for a single package.

Digging into the details, we discovered that the application contained overly broad production dependencies in package.json (directly and transitively) that needlessly filled the application package with extra files. The vast majority were completely unused at runtime—including development-only big hitters like webpack and babel.

After itemizing the package contents, we discovered that of the over 20,000 files included in the application package, less than 900 files were actually used at runtime!

Big is slow

Adding unused files to a Serverless application package hurts on multiple levels:

  • Packaging: Additional files take up more file I/O for file discovery and package copying, and consume more CPU for zipping up a large application package.
  • Deployment: Larger zip packages take more network time to deploy from your build server to the destination cloud.
  • Execution: Larger application code size can even slow down the execution of your function at runtime in the cloud. See discussions here and here.

Thus, even though our client project used Jetpack's faster approach and additional enhancements like parallel processing, it was still ridiculously slow to build. And, our AWS Lambda code package was significantly larger than it needed to be.

What we really wanted was some way to find and package just the code that our handlers needed and nothing else. We brainstormed, researched, and experimented, and are pleased to present the result: serverless-jetpack's new tracing mode.

Tracing just what you need

Jetpack's tracing mode is an alternative way to package Serverless applications. To try it out, install serverless-jetpack and enhance your serverless.yml configuration with:

plugins:
  - serverless-jetpack

custom:
  jetpack:
    trace: true

How it works

Packaging in tracing mode starts with each Node.js function's handler source file. Jetpack parses each source file and extracts all statements that indicate a dependency on another file like: require() and import. These files are recursively traversed to create a complete list of the files actually used in the original handler, which Jetpack includes in a Serverless application bundle instead of package.json production dependencies.

Best practices

When using tracing mode, there are a few tweaks you'll likely want to make to a standard Serverless Framework configuration file. Because tracing mode automatically packages all discoverable imports, package.include|exclude patterns are only really needed for additional files that aren't statically traceable—like dynamically imported code, etc. It is also a great opportunity to use Jetpack's jetpack.preInclude feature to exclude everything to start, and then package.include for the few extra things handler tracing can't discover.

The following example uses a few tracing mode options for a tight, fast build (with comment hints for the order of inclusion):

# serverless.yml
plugins:
  - serverless-jetpack

custom:
  jetpack:
    trace: true
    preInclude:
      # STEP 1: Start with nothing.
      - "!**"

package:
  include:
    # STEP 3: Manually add dynamically-require'ed
    #         config files to the package
    - "config/**"

functions:
  hello:
    # STEP 2: Trace code files starting at `src/hello.js`
    handler: src/hello.handler

Gotchas

Tracing mode produces Serverless application packages that are different (notably smaller) than vanilla Serverless or Jetpack in dependency mode. While the packages should still correctly execute in the cloud, we recommend that you review the known tracing mode caveats to make sure that your Serverless application is a good fit for tracing mode and that everything will work once integrated.

Smaller and faster!

We reviewed Jetpack's tracing mode against both Serverless' built-in packaging and Jetpack's existing dependency mode. For projects with large numbers of production dependencies the results were impressively in favor of tracing mode. Let's review a couple of these scenarios:

  • Jetpack's test fixture huge-prod: A contrived scenario with lots of production dependencies that are unused by the function handler.
  • Client GraphQL project: A client's Serverless-based GraphQL endpoint.
  • Client Server project: A client's Serverless-based Next.js application server.

Smaller!

Let's first look at the size improvements using Jetpack's tracing mode versus Serverless built-in packaging (which is the same as Jetpack dependency mode):

ProjectScenarioTypePackage Sizevs Base
Jetpack Testhuge-prodjetpack trace0.5 mb-92.27 %
Jetpack Testhuge-prodserverless6.6 mb
ClientGraphQLjetpack trace13.1 mb-76.81 %
ClientGraphQLserverless56.5 mb
ClientServerjetpack trace21.0 mb-73.75 %
ClientServerserverless80.0 mb

In these scenarios, Jetpack tracing mode was able to get a package zip file size reduction of between 4 to 13 times smaller!

Faster!

With fewer files to discover on disk and copy over to the Serverless zip package, tracing mode packaging times are quite impressive (even when comparing our client projects that already used Jetpack dependency mode):

ProjectScenarioTypeTimevs Base
Jetpack Testhuge-prodjetpack trace8 s-70.00 %
Jetpack Testhuge-prodserverless25 s
ClientGraphQLjetpack trace10 s-95.00 %
ClientGraphQLjetpack deps200 s
ClientServerjetpack trace15 s-96.38 %
ClientServerjetpack deps415 s

These scenarios see packaging time speedups of between 3 to 27 times faster.

One way or another, Jetpack is what you need

Our ongoing experiences using serverless-jetpack in real-world client infrastructures gives us confidence that it is the right solution for most Serverless application deployments.

Jetpack's new tracing mode offers substantial speedups + size reductions in common situations (overly broad production dependencies). At the same time, Jetpack's battle-tested default dependency mode still provides an always faster, drop-in replacement for Serverless built-in packaging.

All in all, we're confident that serverless-jetpack will provide you with the flexibility to get the fastest possible Serverless application packaging and deployment. And with the introduction of Jetpack's new tracing mode, we'd love to hear which mode works best for you! 🚀

Related Posts

Jetpack: multiple engines for your Serverless pack ...

The 'serverless-jetpack' plugin continues its mission to make Serverless Framework packaging and deployment rocket-fast with new features including pa ...

Jetpack revisited: Even faster Serverless packagin ...

After introducing the 'serverless-jetpack' plugin two weeks ago, we took the entire problem back to the drawing board and came up with an even faster ...

Jetpack: blazingly fast Serverless packaging and d ...

The Serverless Framework is amazing, but can become incredibly slow to package and deploy applications as projects grow. We introduce the 'serverless- ...

Check out more of Ryan's blog posts