Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): selective cache invalidation #8379

Closed
wants to merge 112 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
9fee494
feat(gatsby): start work anew on robust cache invalidation
DSchau May 20, 2019
c86b9d4
cohre: make some more progress (but broken)
DSchau May 21, 2019
f1abdee
chore: remove unused deps
DSchau May 21, 2019
f632301
chore: iterate
DSchau May 22, 2019
8bece71
chore: cache _validation_ (this works; need to actually invalidate now)
DSchau Jun 4, 2019
d928b51
chore: write crap algorithm (onwards and upwards)
DSchau Jun 4, 2019
e60b4ce
chore: add some tests
DSchau Jun 5, 2019
8f3f270
chore: remove cache deletion test
DSchau Jun 5, 2019
5c98955
chore: invalidate parents, as well. Get things good and broken
DSchau Jun 5, 2019
3a6bf91
chore: remove testing change
DSchau Jun 5, 2019
fcade8c
chore: reset package json changes
DSchau Jun 5, 2019
4f59cdd
fix: invalidate cache correctly (thanks @gatsbyjs/core)
DSchau Jun 17, 2019
65ffc17
test: get all tests passing (in theory)
DSchau Jun 17, 2019
86ac889
Merge branch 'master' into gatsby/fast-cache
sidharthachatterjee Nov 21, 2019
6bac503
Add an integration test suite for cache resilience
sidharthachatterjee Nov 21, 2019
bfb6045
Add tools to diff state with
sidharthachatterjee Nov 22, 2019
69ee236
wip
sidharthachatterjee Nov 22, 2019
63c1cb0
wip
sidharthachatterjee Nov 22, 2019
43c3d2b
update
pieh Nov 26, 2019
35df7a1
format
pieh Nov 26, 2019
e1936ce
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Nov 28, 2019
aee34fb
add ci job for cache resilience integration test
pieh Nov 28, 2019
65aa06d
remove packageJson from SitePlugin nodes
pieh Nov 28, 2019
f435c19
invalidating node fields
pieh Nov 28, 2019
5e4ad8d
add tests for node fields
pieh Nov 28, 2019
485e822
no debugger
pieh Nov 28, 2019
9178917
remove comment
pieh Nov 28, 2019
f8cdbc4
fixup create remote file node
pieh Nov 28, 2019
ad4094c
remove junk
pieh Nov 28, 2019
bef17d0
move compareState to utils
pieh Nov 28, 2019
cda7773
remove commented out, unused code
pieh Nov 28, 2019
6caf320
warning formatting
pieh Nov 28, 2019
49ee332
sanity check
pieh Nov 28, 2019
33ff065
add printed object diff to node state diffs
pieh Nov 28, 2019
4ffe63a
nicer object diff (?)
pieh Nov 28, 2019
df7e9ef
use getNodes instead of redux state directly to allow same test to ru…
pieh Nov 28, 2019
4954d8c
remove unused import
pieh Nov 28, 2019
21389e5
test re-org
pieh Nov 28, 2019
f3cb6e7
re-use build sequence for base case (no plugin changes between runs),…
pieh Nov 28, 2019
5bf849f
use jest-serializer-path
pieh Nov 28, 2019
7e582d7
prep for loki tests
pieh Nov 28, 2019
48a8db1
use some constants instead of same literals in multiple places
pieh Nov 28, 2019
ace5d48
init loki before cache invalidation
pieh Nov 29, 2019
b54037a
delay regenerating redux nodes-by-type state to after cache invalidation
pieh Nov 29, 2019
b23eea4
fix some unit tests
pieh Nov 29, 2019
891dedf
remove junk
pieh Nov 29, 2019
f3726dd
streamline page creator SitePlugin nodes so tests work in multiple envs
pieh Nov 29, 2019
778b997
more sanitization to make test work cross-env
pieh Nov 29, 2019
9adc472
remove unused
pieh Nov 29, 2019
d54e564
remove unused arg
pieh Nov 29, 2019
05e79cf
update snapshots
pieh Nov 29, 2019
9070f84
no need to create lookup - nodes state is already lookup
pieh Nov 29, 2019
d7eee08
fix remove-nodes-by-plugin tests
pieh Nov 29, 2019
c6299e1
move loki init around
pieh Nov 29, 2019
bcd342b
don't delete redux state or loki persisted state, it will ultimately …
pieh Nov 29, 2019
dee6334
tmp
pieh Nov 29, 2019
d99a2f7
finish loki selective cache invalidation
pieh Nov 30, 2019
b31b60b
make node stored for assertions sorted to make sure re-ordering doesn…
pieh Nov 30, 2019
c67009f
use loki in cache resilience tests
pieh Nov 30, 2019
e61af8f
default delete_cache for loki
pieh Nov 30, 2019
4fc1aea
try to separate loki run in separate circle Ci step for better visibi…
pieh Nov 30, 2019
dd0e450
move node sorting to after sanitization
pieh Nov 30, 2019
cd15efa
remove some debug logs
pieh Nov 30, 2019
615468d
remove `undefined` values from snapshots
pieh Nov 30, 2019
8eab38e
rely on default-site-plugin version hash generation
pieh Dec 2, 2019
45fc8c2
don't delete redux.state (not redux-state)
pieh Dec 2, 2019
78b20e8
Refactor test assertions for readability
sidharthachatterjee Dec 2, 2019
bbd676b
remove commented out console.log
pieh Dec 3, 2019
5901348
add tests for disk cache, re-org tests a bit
pieh Dec 4, 2019
4c95dac
Remove store since createRemoteFileNode no longer needs it
sidharthachatterjee Dec 9, 2019
b021445
Remove store from createFileNodeFromBuffer
sidharthachatterjee Dec 9, 2019
cba9874
Remove outdated test for createRemoteFileNode
sidharthachatterjee Dec 9, 2019
d6178ce
Ensure added and deleted plugins are invalidated
sidharthachatterjee Dec 9, 2019
177fbbd
Fix linting
sidharthachatterjee Dec 9, 2019
1fe24f1
Add tests for plugins being added or deleted
sidharthachatterjee Dec 9, 2019
7de6227
Remove errant skip
sidharthachatterjee Dec 9, 2019
faf4e12
add framework for asserting page query results and first query result…
pieh Dec 11, 2019
6aa01c5
remove
pieh Dec 11, 2019
fd047b7
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Dec 11, 2019
9592dd8
Merge remote-tracking branch 'upstream/master' into gatsby/fast-cache
Dec 12, 2019
0f5c6ab
wip
sidharthachatterjee Dec 12, 2019
8de1071
Merge branch 'master' into gatsby/fast-cache
sidharthachatterjee Jan 21, 2020
0b85858
Wrap up tests
sidharthachatterjee Jan 22, 2020
b6af933
Fix query for test
sidharthachatterjee Jan 22, 2020
ab776b7
Add back skipped tests
sidharthachatterjee Jan 22, 2020
24337d4
wip
sidharthachatterjee Jan 24, 2020
36d7a63
allow using useGatsbyNodeAndConfigAndQuery via 'yarn use-setup <numbe…
pieh Feb 28, 2020
c26c4c2
tmp
pieh Mar 2, 2020
f42283e
more tmp
pieh Mar 3, 2020
2292cdf
more tmp
pieh Mar 4, 2020
3cfd9ba
tmp
pieh Mar 4, 2020
2755b63
first query actual test
pieh Mar 4, 2020
a293d15
just data
pieh Mar 4, 2020
8cceeb5
next plugin
pieh Mar 4, 2020
bef829d
adding/removing plugins should add/remove type to schema
pieh Mar 4, 2020
3434ca4
type and data type queries, coverage for soure and transform scenarios
pieh Mar 5, 2020
649dddf
replace old tests with new ones
pieh Mar 5, 2020
9b82810
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Mar 5, 2020
b966048
add TO-DO
pieh Mar 5, 2020
5735159
let's try with flag on
pieh Mar 5, 2020
4683c00
buildTime is now on SiteBuildMetadata Node
pieh Mar 6, 2020
8de6b54
verbose output
pieh Mar 6, 2020
d6f70d2
node fields, source changes scenarios
pieh Mar 6, 2020
5b27a65
don't use selective cache invalidation
pieh Mar 6, 2020
f3bb120
add query/schema scenarios for node fields transfomer changes
pieh Mar 6, 2020
7c1849e
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Mar 6, 2020
f1b4364
remove unused query fixtures
pieh Mar 6, 2020
e54775e
cleanup
pieh Mar 6, 2020
1ac1646
remove unneeded changes
pieh Mar 17, 2020
7960579
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Mar 17, 2020
7f77d82
Merge remote-tracking branch 'origin/master' into gatsby/fast-cache
pieh Mar 24, 2020
b81e673
remove unused file
pieh Mar 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports.onCreateNode = async (
if (filter(node)) {
const fileNode = await createRemoteFileNode({
url: node.url,
parentNodeId: node.id,
sidharthachatterjee marked this conversation as resolved.
Show resolved Hide resolved
store,
cache,
createNode,
Expand Down
143 changes: 143 additions & 0 deletions packages/gatsby/src/bootstrap/cache/__tests__/plugin-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
jest.mock(`md5-file/promise`, () => {
const path = jest.requireActual(`path`)
return jest.fn(file => Promise.resolve(path.basename(file)))
})
const md5File = require(`md5-file/promise`)
const getPluginHash = require(`../plugin-hash`)

const getHash = ({
directory = __dirname,
plugins = [],
existing = {},
...rest
} = {}) => getPluginHash({ directory, plugins, existing, ...rest })
muescha marked this conversation as resolved.
Show resolved Hide resolved

beforeEach(() => {
md5File.mockClear()
})

describe(`basic functionality`, () => {
it(`invalidates gatsby-node.js if it has changed`, () => {
const additional = [`gatsby-node.js`]
const existing = {
"gatsby-node.js": ``,
}

expect(getHash({ additional, existing })).resolves.toEqual(
expect.objectContaining({
changes: [`gatsby-node.js`],
})
)
})
})

describe(`getting changed plugins`, () => {
it(`returns single plugin, if single plugin changed`, () => {
const plugins = [{ name: `gatsby-source-filesystem`, version: `2.0.0` }]
const existing = { "gatsby-source-filesystem": `1.0.0` }

expect(getHash({ plugins, existing })).resolves.toEqual(
expect.objectContaining({
changes: [`gatsby-source-filesystem`],
})
)
})

it(`returns multiple plugins, if multiple plugins changed`, () => {
const plugins = [
{ name: `gatsby-source-filesystem`, version: `2.0.0` },
{ name: `gatsby-remark-prismjs`, version: `1.0.0` },
]
const existing = {
"gatsby-source-filesystem": `1.0.0`,
"gatsby-remark-prismjs": `0.9.9`,
}

expect(getHash({ plugins, existing })).resolves.toEqual(
expect.objectContaining({
changes: [`gatsby-source-filesystem`, `gatsby-remark-prismjs`],
})
)
})

it(`does not include plugin as changed if fresh install of plugin`, () => {
const plugins = [{ name: `gatsby-source-filesystem`, version: `1.0.0` }]
const existing = {}

expect(getHash({ plugins, existing })).resolves.toEqual(
expect.objectContaining({
changes: [],
})
)
})

it(`does not include plugin as changed if no changes in version`, () => {
const plugins = [{ name: `gatsby-source-filesystem`, version: `1.0.0` }]
const existing = { "gatsby-source-filesystem": plugins[0].version }

expect(getHash({ plugins, existing })).resolves.toEqual(
expect.objectContaining({
changes: [],
})
)
})

it(`does not include changed plugins if fresh cache`, () => {
const plugins = [
{ name: `gatsby-source-filesystem`, version: `1.0.0` },
{ name: `gatsby-remark-prismjs`, version: `1.0.0` },
]
const existing = {}

expect(getHash({ plugins, existing })).resolves.toEqual(
expect.objectContaining({
changes: [],
})
)
})
})

describe(`getting hash`, () => {
it(`creates hash based on plugin versions`, () => {
const plugins = [
{ name: `gatsby-source-filesystem`, version: `1.0.0` },
{ name: `gatsby-remark-prismjs`, version: `1.0.0` },
]

expect(getHash({ plugins })).resolves.toEqual(
expect.objectContaining({
hash: {
"gatsby-source-filesystem": `1.0.0`,
"gatsby-remark-prismjs": `1.0.0`,
},
})
)
})

it(`includes hash of additional file(s)`, () => {
const additional = [`gatsby-config.js`, `gatsby-node.js`, `package.json`]

expect(getHash({ additional })).resolves.toEqual(
expect.objectContaining({
hash: {
"gatsby-config.js": `gatsby-config.js`,
"gatsby-node.js": `gatsby-node.js`,
"package.json": `package.json`,
},
})
)
})

it(`uses empty string as key if file does not exist`, () => {
md5File.mockRejectedValueOnce(() => new Error(`File does not exist`))
const additional = [`gatsby-config.js`]

expect(getHash({ additional })).resolves.toEqual(
expect.objectContaining({
hash: {
"gatsby-config.js": ``,
},
})
)
})
})
18 changes: 18 additions & 0 deletions packages/gatsby/src/bootstrap/cache/plugin-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const _ = require(`lodash`)

module.exports = async function getPluginHash({ plugins, existing }) {
const pluginsHash = plugins.reduce((merged, plugin) => {
merged[plugin.name] = plugin.version
return merged
}, {})

return {
changes: _.uniq(
Object.keys({ ...pluginsHash, ...existing }).filter(name => {
const hash = pluginsHash[name]
return hash !== existing[name]
})
),
hash: pluginsHash,
}
}
66 changes: 66 additions & 0 deletions packages/gatsby/src/bootstrap/cache/remove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const path = require(`path`)
const fs = require(`fs-extra`)
const _ = require(`lodash`)

const getPluginHash = require(`./plugin-hash`)

const CACHE_FOLDER = `caches`
const DONT_DELETE = [`redux.state`, `loki`]
/*
* This function will first calculate a diff of plugin/file changes (e.g. if gatsby-node.js changes)
* From this, it will then selectively invalidate files in .cache/caches
* the location where Gatsby stores long-lived cache files that can be safely kept around
*/
module.exports = async function safeRemoveCache({
directory,
plugins,
existing,
report,
}) {
const cacheDirectory = path.join(directory, `.cache`)

const { changes, hash } = await getPluginHash({
plugins,
existing,
})

const equalHashes = _.isEqual(existing, hash)
const hasExistingCache = Object.keys(existing).length > 0

await fs.ensureDir(cacheDirectory)

if (hasExistingCache && !equalHashes) {
report.info(report.stripIndent`
The following plugins were changed, added or removed since the last time you ran Gatsby:
${changes.map(change => ` - ${change}`).join(`\n`)}
As a precaution, we're invalidating parts of your cache to ensure your data is sparkly fresh.
`)
}

if (!equalHashes) {
try {
const filesToRemove = await fs.readdir(cacheDirectory).then(allFiles =>
Copy link
Contributor

@pieh pieh Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self - this won't work for plugins that have / in name - for example:

  • @scope/name
  • nested/local/plugin

(it assumes flat directory structure of .cache/caches)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ops, it should be comment to line 50, but same thing applies

allFiles.reduce((merged, file) => {
if (file === CACHE_FOLDER) {
return merged.concat(
changes
.map(pluginName =>
path.join(cacheDirectory, CACHE_FOLDER, pluginName)
)
.filter(absolutePath => fs.existsSync(absolutePath))
)
} else if (DONT_DELETE.includes(file)) {
return merged
}
return merged.concat(path.join(cacheDirectory, file))
}, [])
)

await Promise.all(filesToRemove.map(file => fs.remove(file)))
} catch (e) {
report.error(`Failed to remove .cache files.`, e)
}
}

return { cacheDirectory, changes, hash }
}
Loading