www.gorillastack.com Open in urlscan Pro
139.180.130.74  Public Scan

URL: https://www.gorillastack.com/blog/real-time-events/optimizing-your-lambda-cold-starts-with-serverless-webpack/
Submission: On March 11 via manual from GB — Scanned from GB

Form analysis 1 forms found in the DOM

GET https://www.gorillastack.com/

<form method="get" id="searchform" action="https://www.gorillastack.com/" data-hs-cf-bound="true"> <svg class="icon_search" width="26" viewBox="0 0 26 26">
    <defs>
      <style>
        .path {
          fill: none;
          stroke: #000;
          stroke-miterlimit: 10;
          stroke-width: 1.5px;
        }
      </style>
    </defs>
    <circle class="path" cx="11.35" cy="11.35" r="6"></circle>
    <line class="path" x1="15.59" y1="15.59" x2="20.65" y2="20.65"></line>
  </svg> <span class="mfn-close-icon icon_close"><span class="icon">✕</span></span><input type="text" class="field" name="s" autocomplete="off" placeholder="Enter your search"> <input type="submit" class="display-none" value=""></form>

Text Content

We use cookies to improve your website experience. To find out more about the
cookies we use, see our Privacy Policy.

Accept
 * Products
   * Cost Optimization
   * Real Time Events
   * Backup & Disaster Recovery
 * Pricing
 * Customer Stories
 * Resources
   * Blog
   * Webinars
   * Use Cases
   * Real Time Templates
   * Open Source
 * Support
   * Documentation
   * Changelog
 * Login



Get Started
✕


OPTIMIZING YOUR LAMBDA COLD STARTS WITH SERVERLESS-WEBPACK

By Chris Armstrong | 10 Jan 2020



AWS has been doing some great work lately to reduce Lambda cold start times for
all its Lambda runtimes, such as their VPC networking improvements and adding
native support for automatically keeping your Lambda containers warm.

These changes have reduced the number of things that a developer needs to do to
improve their Lambda start-up performance.

But there is still one variable under the control of the developer that has a
significant effect on cold starts – the packaged bundle size.


WHY BUNDLE SIZE STILL MATTERS

Despite the improvements that AWS has made, your Lambda can still cold start.
This extra startup time will impact the perceived responsiveness of user-facing
functions, such as APIs or Slack applications.

If you’re using the serverless framework with JavaScript, it packages your code
with all the npm dependencies by default. This can create very bloated Lambda
functions. Much of this code is often unused. Since this code is in your
application package, it still needs to be copied and extracted onto the file
system every time a container is provisioned. The result is a slow response time
for your customer.


WHERE BACK-END DEVELOPERS NEED TO CATCH UP

The packaged code size has long been a metric of importance to web developers.
They spent hours optimizing the JavaScript, images, CSS, and HTML delivered to a
web application.. The amount of time taken for it to download affects the time
to first paint and time to first interaction. Plus, it can have real impacts on
customer retention and conversion.

Back-end developers have been able to get away with ignoring their code size for
a long time because of the way it was packaged and executed. When you deploy a
monolithic application, you typically deploy it to a long-running server. Once
started and left running for days since start-up times were not really a
concern.

Such applications are typically clustered to horizontally scale for performance
and elastically adjust capacity for changing customer load. As start-up times
are already poor, clusters are normally overprovisioned to compensate.
Developers have been able to hide their poor application start-up times behind
this.

With serverless, the architectural concerns of the front-end come to the
back-end. Even though, it has freed developers from thinking about a number of
operational concerns like infrastructure provision and resource optimization.
Now, they need to consider things like cold starts and resource connection
management that weren’t needed in the past.

Bundle size directly affects the start-up time of a Lambda. So, optimizing the
amount of code that is deployed with a bundler will help reduce cold start time.

Thankfully for JavaScript developers, the tooling that has been used to bundle
code is quite mature on the front-end. There are now many bundlers for
JavaScript, and most of them are suitable for packaging back-end code running on
Node.js as well.


HOW DOES A BUNDLER (SERVERLESS WEBPACK) HELP?

Bundlers work by looking at the imports and exports in your code and your
referenced npm packages, building a dependency tree, and then generating a
single JavaScript file that contains all the code rolled up together.

This brings a number of benefits:

 * Include only the code used by your function.
   You may have multiple Lambda functions in the same serverless project that
   share some code. But, it is unlikely that every function uses exactly the
   same set of shared dependencies.
   
   It is possible to optimize your deployed Lambda size by only including the
   code that is imported by your function, not all your functions.
   
 * Optimize your npm dependencies
   You may only be using some of your npm dependencies, or even only part of the
   code of those referenced. Some of your npm dependencies may exist twice
   because they are referenced by multiple modules.
   
   A bundler can follow the dependency graph into a npm module. Then, grab only
   those files that are transitively referenced by your code, as well as
   de-duplicating (where compatible versions exist).
   
 * Use a single file for your source code
   Using a single file instead of multiple files can reduce the amount of file
   system accesses needed by Node.js to load your code.
   
   (This improvement is usually small. Besides, it’s a side-effect of webpack
   where packaging a single file for the web makes a huge difference over the
   network.)
   
   The result is usually a much smaller and efficient Lambda function than if
   you had just zipped up all your code and deployed it.


GETTING STARTED WITH SERVERLESS-WEBPACK

The first step is to install the plugin and register it in your serverless file.

Change to your project directory and install the serverless-webpack npm module,
along with webpack:

npm i -D serverless-webpack webpack

Add the plugin to your serverless.yaml file:

plugins:
  - serverless-webpack
  ...
  # if you have serverless-offline-plugin, make sure it comes after serverless-webpack
  - serverless-offline-plugin

Last, add a webpack.config.js file in the root directory:

const slsw = require('serverless-webpack');
module.exports = {
  target: 'node',
  entry: slsw.lib.entries,
  mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
  node: false,
  optimization: {
    minimize: false,
  },
  devtool: 'inline-cheap-module-source-map',
};

At this point, we have configured the serverless-webpack plugin and given it a
minimal webpack configuration.

If you have been reading the serverless-webpack documentation, you may have
noticed I’ve included some extra options that don’t appear in their examples.

 * node: false is unusual, but I’ve found it to be necessary for my
   configurations.
   
   Setting node as the target is normally enough to get webpack to compile for
   Node.js — it uses CommonJS require() for dependencies and ensures that
   built-in dependencies are not substituted with stubs. However, I’ve found it
   still interferes with the process.env global, which is needed to use Lambda
   environment variables.

 * minimize: false turns off code minimization (uglification).
   
   Obfuscation makes it harder to read stack traces and reduces bundle size a
   bit. It is not enough to justify doing it for a server application. Moreover,
   it isn’t really needed where code isn’t distributed directly to users (like
   over the web).
   
   
   My configuration disables minimization, because it greatly reduces the amount
   of CPU and memory needed at build time and helps
   avoid ‘out-of-memory’ errors (see the Troubleshooting section below).


PACKAGE ONCE, OR ONCE PER FUNCTION

If you run serverless package at this point, your serverless output will change
to include a typical webpack report. Thus, showing build times and bundle sizes.

By default, serverless generates one package with all your code and deploys it
for all your Lambda functions. serverless-webpack does the same, but it creates
one JavaScript file per Lambda function instead, with all the code needed by
each Lambda, including npm dependencies, bundled up together and then zipped.

NOTE: it doesn’t include non-source code files in the bundle – for these
see Copying other files into the bundle

You can find your packaged source bundle in the <project>/.serverless directory.
You will find that this is still a huge reduction in size compared to the
serverless default for non-trivial applications.

If you want to optimize further and create a bundle specific to each Lambda
function, you can make serverless package each one individually by setting the
following option in serverless.yaml:

---
package:
  individually: true

serverless-webpack utilizes this value too, and it will create a separate
deployment package for each Lambda with the single webpack bundle in each one.
This takes much longer, but the result is a more optimized output.


USING BABEL

The plugin documentation outlines how to use babel for transpilation (but I’ve
included some of my own changes to ensure you get the most of out it).

Firstly, install the babel-loader and related babel npm packages:

npm i -D @babel/core @babel/preset-env babel-loader corejs@3

Add the following entry to your webpack.config.js (making sure to set
the node target to the Node.js runtime version you are using on Lambda):

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  { targets: { node: '12' }, useBuiltIns: 'usage', corejs: 3 }
                ]
              ]
            }
          }
        ]
      }
    ]
  },
  ...
}

Compared to the serverless-webpack documentation, the main differences with my
configuration are:

 * adding the @babel/preset-env preset to ensure that we don’t include
   transformations or polyfills for features that are already available in
   Node.js.
 * set useBuiltins: 'usage' so that any polyfills that are used are included on
   a case-by-case basis
 * set corejs: 3 so that the old, buggy core-js v2 is not used to provide
   polyfills


COPYING OTHER FILES INTO THE BUNDLE

Because serverless normally copies everything into the deployment package, you
may get a rude shock when your code suddenly stops being able to load arbitrary
non-code files from the file system in your Lambda function.

As mentioned before, serverless-webpack only includes the source code bundle,
effectively ignoring whatever is set in the include and exclude options under
the package section of serverless.yaml. You must configure webpack to copy the
non-source files you need using the copy-webpack-plugin

Install it first with npm:

npm i copy-webpack-plugin

In your webpack.config.js include and configure the plugin with the file paths
and globs you wish to copy:

const CopyPlugin = require('copy-webpack-plugin`);
...
module.exports = {
  ...
  plugins: [
    new CopyPlugin([
      'path/to/specific/file',
      'recursive/directory/**',
    ]),
  ],
  ...
};




TROUBLESHOOTING


OUT OF MEMORY

Using webpack to package your code uses far more CPU and memory than normal.
Besides, it’s not unusual for Node.js to report an out-of-memory
error. serverless-webpack runs a webpack instance per-function, and each one has
to combine and minimize the output.

Serverless: Bundling with Webpack...
<--- Last few GCs --->
[8233:0x393fa70]   207373 ms: Scavenge 1885.9 (2042.4) -> 1885.9 (2042.9) MB, 9.3 / 0.0 ms  (average mu = 0.262, current mu = 0.159) allocation failure
[8233:0x393fa70]   207386 ms: Scavenge 1886.4 (2042.9) -> 1886.4 (2043.6) MB, 11.4 / 0.0 ms  (average mu = 0.262, current mu = 0.159) allocation failure
[8233:0x393fa70]   207404 ms: Scavenge 1887.0 (2043.6) -> 1886.9 (2044.1) MB, 15.8 / 0.0 ms  (average mu = 0.262, current mu = 0.159) allocation failure
<--- JS stacktrace --->
==== JS stack trace =========================================
    0: ExitFrame [pc: 0x1374fd9]
    1: StubFrame [pc: 0x13afd14]
Security context: 0x39352fbc08a1 <JSObject>
    2: replace [0x39352fbccf51](this=0x2c60c4e7f591 <String[#27]: n//# sourceMappingURL=[url]>,0x16535c280e41 <JSRegExp <String[#7]: [url]>>,0x16535c280e79 <JSFunction (sfi = 0x35c9e77a48b9)>)
    3: /* anonymous */(aka /* anonymous */) [0x17c38da35a9] [/home/chris/dev/slack-app/bot/node_modules/webpack/lib/SourceMapDevToolPlu...
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x9da7c0 node::Abort() [node]
 2: 0x9db976 node::OnFatalError(char const*, char const*) [node]
 3: 0xb39f1e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xb3a299 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xce5635  [node]
 6: 0xce5cc6 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
 7: 0xcf1b5a v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
 8: 0xcf2a65 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 9: 0xcf5478 v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [node]
10: 0xcc30c6 v8::internal::Factory::NewRawOneByteString(int, v8::internal::AllocationType) [node]
11: 0x103253a v8::internal::Runtime_StringBuilderConcat(int, unsigned long*, v8::internal::Isolate*) [node]
12: 0x1374fd9  [node]

The solution is to increase the amount of heap space available to Node.js when
starting serverless (this only works in Node.js v8 and later):

node --max-old-space-size=4096 node_modules/.bin/serverless package --stage dev ...

(Also note that we have to invoke serverless with its full path in the
`nodemodules/bin` folder because_ we are starting Node.js directly.)

If you don’t want to call Node.js directly, you can set the node options using
an environment variable:

export NODE_OPTIONS=--max-old-space-size=4096
npx serverless package


CORRECT LINE NUMBER AND FUNCTION NAMES IN STACK TRACES

Because webpack transforms your code and packages it into the same file, stack
traces in CloudWatch logs will reflect what is packaged by default. This can
hamper your ability to debug your code.

webpack uses the devtool option to control source maps, which are extra comments
included in the code used to help debuggers and stack trace generators translate
references to the bundled source code back to the transformed or original source
code.

In our sample webpack.config.js, we set the devtool option
to 'inline-cheap-module-source-map', which should render stack traces with line
references to the original source code.

This option can affect webpack build speed, so if you want to use something
else, check out the webpack documentation on devtool.




Share




RELATED POSTS

15 Jul 2021

AZURE CLOUD AUTOMATION – A DEVOPS GUIDE

--------------------------------------------------------------------------------

Read more
05 Mar 2021

EMBRACE AUTOMATIC INCIDENT REMEDIATION WITH PAGERDUTY AND GORILLASTACK

--------------------------------------------------------------------------------

Read more
29 Jan 2021

AWS CONFIG VS CLOUDTRAIL

--------------------------------------------------------------------------------

Read more

AUTOMATE DEVOPS

Cloud Optimization
Real Time Events
Backup and Disaster Recovery
Book a Demo
Try for Free


SUPPORT

Documentation
Changelog
Open Source
System Status
Submit a Ticket


RESOURCES

Customer Stories
Blog
Templates
Use Cases
Join us on Slack


ABOUT GORILLASTACK

Why GorillaStack?
Press Releases
Find Us at GitHub
Partner with Us
Contact Us

       

© Copyright 2021 GorillaStack by PyraCloud
 * Privacy Policy
 * Terms of Services


Get Started






INTRODUCING – TEMPLATES

24 Dec 2019


CREATE DB SNAPSHOTS NOW SUPPORTS CLUSTERS

23 Jan 2020