Improving the Ghost boot time changing how Lodash is loaded

@daniellockyer Here’s a method that will make Ghost bootslightly faster.

Using strace filtered to calls that deal with files, I can see that there are over 900 system calls to files read just to load lodash functions:

 strace -r --trace=file ghost run 2>&1| grep lodash

One cause of this is the style of loading Lodash with the one-function-per-file method, like: const foo = require('lodash/foo');

For each modules that does it, about 6 more system calls are made.

Perhaps that’s a useful pattern on the frontend to minimize the amount of Lodash code shipped, but is it best on the backend? To test, I put together an experiment. I found all the Lodash modules that Ghost was loading this way and put them in a file that contains just the require statements. I used time to test how long it took to load the functions one at a time like that.

For a control, another file contains just const _ = require('lodash'); and tested now long this takes to load. This loads all the Lodash code, but it only reads and parses the files once.

The result was that it was about as twice as fast-- saving 40 ms on my laptop, to load Lodash once whether than many times.

Testing loading Lodash one function at a time with time node lodash-files.js

const each = require('lodash/each');
const filter = require('lodash/filter');
const findKey = require('lodash/findKey');
const find = require('lodash/find');
const flatten = require('lodash/flatten');
const get = require('lodash/get');
const _has = require('lodash/has');
const intersection = require('lodash/intersection');
const isArray = require('lodash/isArray');
const isEmpty = require('lodash/isEmpty');
const isFunction = require('lodash/isFunction');
const isNil = require('lodash/isNil');
const isObject = require('lodash/isObject');
const isPlainObject = require('lodash/isPlainObject');
const isString = require('lodash/isString');
const kebabCase = require('lodash/kebabCase');
const mapValues = require('lodash/mapValues');
const omit = require('lodash/omit');
const _set = require('lodash/set');
const some = require('lodash/some');
const startCase = require('lodash/startCase');
const toString = require('lodash/toString');
const uniqueId = require('lodash/uniqueId');
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);

Testing loading Lodash all at once with time node lodash-one.js

const _ = require('lodash');
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`The script uses approximately ${Math.round(used * 100) / 100} MB`);

The simple script that loads Lodash all at once makes 9 system calls to load Lodash-related files.

The downsides I see to this boot optimization:

  • Inconsistency with best practice for loading Lodash on the frontend
  • More memory usaged. It took about an extra 1 MB of memory to load all of Lodash. Assumming Ghost(Pro) is hosting hundreds of copy of Ghost on the same servers, the cost to rent more memory may add up, so perhaps it’s better to find a boot optimization that doesn’t trade speed for memory!