This is a premium alert message you can set from Layout! Get Now!

Introduction to Snapshot flags in Node.js v18.8.0

0

Node.js recently introduced an experimental feature to generate run-time user-land (user scripts) snapshots in v18.8.0. In this post, we’ll look at the importance of this feature, and some of the options it provides. We’ll also compare this snapshot feature to other packaging solutions, such as pkg.

To jump ahead:

  • Understanding Node.js startup
  • Node.js flags
    • The --build-snapshot flag
    • The --snapshot-blob flag
  • Building a snapshot
  • Running a snapshot blob
  • An alternative to using a separate entry script
  • --snapshot-blob and --build-snapshot vs. --node-snapshot-main
  • Node.js snapshot feature vs. other packaging solutions

Understanding Node.js startup

To understand the need for generating run-time user-land snapshots, we need to understand the way Node.js starts up.

Node.js builds a v8::Isolate, v8::context, and node::Environment at startup. It then constructs a process object and launches bootstrap Node.js to prepare the environment. Node.js only executes user script once all of this is complete.

The new snapshot flags feature enables the Node executable to create a single binary that contains both Node.js and an embedded snapshot without building Node.js from the source. This means that the binary file already contains Node, so there is no need for another initialization (such as creating the v8::Isolate, v8::context, and all the other processes usually required to start a user script), which would have increased the start-up time of the scripts.

Node.js flags

To enable the Node.js executable to achieve this feature, a couple of new flags were introduced: the --snapshot-blob and --build-snapshot flags. In this section, we’ll see how to use these new flags.

The --build-snapshot flag

The --build-snapshot flag tells Node.js to build a snapshot of the file supplied as an argument to the flag:

--build-snapshot snapshot.js

Snapshot.js serves as the entry point script.

The --snapshot-blob flag

The --snapshot-blob flag allows us to tell the Node.js executable file what to save the snapshot blob to. If the snapshot blob file exists, Node simply overrides its content with the new blob, and if it is non-existent, Node creates a new blob file and saves it to the disk in the current working directory:

--snapshot-blob snapshot.blob

snapshot.blob serves as the name of the binary file where the generated blob is saved.

Now that the function of these flags is understood, we can attempt to build a snapshot of our own.

Building a snapshot

  1. Open up a terminal and create a snapshot_test folder
  2. Open the snapshot_test folder in your favorite code editor and initialize npm using npm init -y
  3. Create a file named snapshot.js
  4. Copy the following lines of code into snapshot.js:
    const path = require('path')
    
    console.log(process.cwd())
    globalThis.path = process.cwd()
    globalThis.file = __dirname
    const name = 'I am geezy'
    
    console.log(process.argv)
    globalThis.firstArg = process.argv[2]
    globalThis.secondArg = process.argv[3]

This is a simple script that sets a couple of global variables using the globalThis. The globalThis provides us with a way to access global variables(global object).

To build a snapshot of this script along with its current Node.js run-time environment, run the following command:

node --snapshot-blob snapshot.blob --build-snapshot snapshot.js name home

The extra name and home arguments given to the commands are available to us through process.argv.

This is the output:

/home/phantom/Documents/node_js_projects/node_testing
[
  '/home/phantom/.nvm/versions/node/v18.9.1/bin/node',
  '/home/phantom/Documents/node_js_projects/node_testing/snapshot.js',
  'name',
  'home'
]

Node.js executes Snapshot.js as usual, then it creates a snapshot of the script’s state.

Inspecting the current working directory, we find the snapshot.blob file that Node.js generated. When we open up the file, we see gibberish:

Opened Snapshot-Blob File

This prompts the question: How do we execute the generated blob?

Running a snapshot blob

This new feature makes it easy to run a snapshot blob — all we need is to create an entry file for our snapshot.blob file. This file will attempt to read from the Global Object.

Create an index.js file in the snapshot_test directory, and add the following lines of code to it:

console.log('current working directory', globalThis.path)
console.log('First Arg', globalThis.firstArg)
console.log('Second Argument', globalThis.secondArg)
console.log('current process Argv', process.argv)
console.log('Global Object', globalThis)

Then, run the following command:

node --snapshot-blob snapshot.blob index.js

This is the output:

current working directory /home/phantom/Documents/node_js_projects/node_testing
First Arg name
Second Argument home
current process Argv [
  '/home/phantom/.nvm/versions/node/v18.9.1/bin/node',
  '/home/phantom/Documents/node_js_projects/node_testing/index.js'
]
Global Object <ref *1> Object [global] {
  global: [Circular *1],
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  structuredClone: [Function: structuredClone],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  atob: [Function: atob],
  btoa: [Function: btoa],
  performance: Performance {
    nodeTiming: PerformanceNodeTiming {
      name: 'node',
      entryType: 'node',
      startTime: 0,
      duration: 99.07323400303721,
      nodeStart: 3.987049002200365,
      v8Start: 31.41652700304985,
      bootstrapComplete: 89.83720200136304,
      environment: 66.75902900099754,
      loopStart: -808954.4995539971,
      loopExit: -808949.1088810004,
      idleTime: 0
    },
    timeOrigin: 1665479964732.965
  },
  fetch: [AsyncFunction: fetch],
  path: '/home/phantom/Documents/node_js_projects/node_testing',
  file: '/home/phantom/Documents/node_js_projects/node_testing',
  firstArg: 'name',
  secondArg: 'home'
}

Although we didn’t run the snapshot.js file, by running the blob file with an entry point, the globalThis.path, globalThis.firstArg, and globalThis.secondArg variables are assigned values as though we ran the snapshot.js file. This goes to prove that the state of our application is captured in the snapshot.blob file.

To know if the snapshot.blob file is being run, we can attempt to run the index.js file without specifying a blob.

Run the following command:

 node index.js

This is the output:

current working directory undefined
First Arg undefined
Second Argument undefined
current process Argv [
  '/home/phantom/.nvm/versions/node/v18.9.1/bin/node',
  '/home/phantom/Documents/node_js_projects/node_testing/index.js'
]
Global Object <ref *1> Object [global] {
  global: [Circular *1],
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  structuredClone: [Function: structuredClone],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  atob: [Function: atob],
  btoa: [Function: btoa],
  performance: Performance {
    nodeTiming: PerformanceNodeTiming {
      name: 'node',
      entryType: 'node',
      startTime: 0,
      duration: 104.0962289981544,
      nodeStart: 15.742054000496864,
      v8Start: 21.813469998538494,
      bootstrapComplete: 88.35036600008607,
      environment: 66.38047299906611,
      loopStart: -1,
      loopExit: -1,
      idleTime: 0
    },
    timeOrigin: 1665480382060.152
  },
  fetch: [AsyncFunction: fetch]
}

Inspecting both outputs, we notice some differences:

  1. In the second instance, the globalThis.path, globalThis.firstArg, and globalThis.secondArg values are undefined, because those values are only set when snapshot.js is run
  2. The Global Object does not contain the extra key-value pairs that we initialized in the snapshot.js file

Alternative to using a separate entry script

We can restore our application state without the use of an entry script by using the v8.startupSnapshot API to specify an entry point as the snapshot is being built.

In the current directory, create a second_snapshot.js file and add the following lines of code:

const path = require('path')

console.log(process.cwd())
globalThis.path = process.cwd()
globalThis.file = __dirname
const name = 'I am geezy'

console.log(process.argv)
globalThis.firstArg = process.argv[2]
globalThis.secondArg = process.argv[3]

require('v8').startupSnapshot.setDeserializeMainFunction(() => {
    console.log('firstArg', this.firstArg)
    console.log('secondArg', this.secondArg)
    console.log('I am from the second snapshot')
})

Build the snapshot blob using this command:

node --snapshot-blob second_snapshot.blob --build-snapshot second_snapshot.js name home

To restore the script state from second_snapshot.blob, run the following command:

node --snapshot-blob second_snapshot.blob

This is the output:

firstArg name
secondArg home
I am from the second snapshot

Notice how we didn’t have to specify an entry script when trying to restore our application state.

--snapshot-blob and --build-snapshot vs. --node-snapshot-main

These new flags allow for run-time snapshots, but the ability to take snapshots has existed since node v18.0.0 by using the --node-snapshot-main flag. However, this flag only supports build-time snapshots. It also requires building Node from the source, which is not user friendly and takes a considerable amount of time depending on the host machine.

To understand the difference in performance when running run-time snapshots and build-time snapshots, let’s look at the metrics from the author of both features:

Node.js Snapshot Metrics

Looking at the metrics above, it’s easy to see that the run-time snapshot (--snapshot-blob) version, which performs 19 runs, outperforms the build-time snapshot (11 runs) while taking much less time.

Support --snapshot-blob and --build-snapshot --node-snapshot-main
Run-time snapshots Yes No
Uses configure script No Yes
User-land modules No No
Requires separate startup script Not necessary Yes
Building Node from source No Yes
Build-time snapshots No Yes

Node.js snapshot feature vs. other packaging solutions

Using packaging solutions (such as pkg), the app source can be bundled into a binary. But in order to launch the app once the binary is loaded, you still need to parse the source.

On the other hand, using the Node.js snapshot, the heap state that was initialized by the code is included in the binary, negating the requirement to run the initialization code during load time.

Conclusion

The Node.js snapshot feature is highly experimental and limited at the time of writing this article, but more features will be added as time goes on. The feature is a promising prospect for the Node.js community and hopefully you have a better understanding of the topic after reading this article.

The post Introduction to Snapshot flags in Node.js v18.8.0 appeared first on LogRocket Blog.



from LogRocket Blog https://ift.tt/6GdfsLV
Gain $200 in a week
via Read more

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
Post a Comment

Search This Blog

To Top