Skip to content

feat(autoresearch): persistent build caches — minimal-mode gatsby build ~477s → ~82s#18661

Open
pauldambra wants to merge 2 commits into
posthog-code/autoresearch-gatsby-build-speedfrom
posthog-code/autoresearch-gatsby-build-speed-2
Open

feat(autoresearch): persistent build caches — minimal-mode gatsby build ~477s → ~82s#18661
pauldambra wants to merge 2 commits into
posthog-code/autoresearch-gatsby-build-speedfrom
posthog-code/autoresearch-gatsby-build-speed-2

Conversation

@pauldambra

Copy link
Copy Markdown
Member

Created with Autoresearch.

Stacked on #18650 (sourcing-phase optimizations). This run targeted the whole GATSBY_MINIMAL=true pnpm build wall time — the command the CI preview job runs — measured locally with pnpm clean between every change.

Results (local, quiet host, steady state)

Iteration Change Total
baseline (on top of #18650) 476.8s
2 Relocate site webpack stage caches outside .cache/ 217.8s
3 Persist gatsby-plugin-mdx genMDX payloads (config-fingerprinted) 177.2s
4 Compile the feed html renderer once per process (was per DataLoader batch) 123.9s
5 Relocate gatsby core's query-engine/page-ssr webpack caches (patch) 100.6s
6 Fix renderer cache never flushing (missing compiler.close()) (contaminated reads)
7 Sitemap question fetch: fields=[permalink] instead of populate=* onPostBuild 33→17s
8 GATSBY_TELEMETRY_DISABLED=1 in build scripts
9 Wire persistent caches into CI + first clean read of the full stack 82.4s
10 NODE_COMPILE_CACHE for the build process tree (measuring)

Phase-level: webpack trio 255s → 18s, createPages 57s → 1.5s, onPostBuild 67s → 13s, run-queries 17s → 2.5s.

Why output is unchanged

  • Webpack cache relocations (site stages, gatsby's two engine bundlers, the MDX feed renderer): webpack revalidates every cache entry against module contents and build dependencies (config + every plugin's gatsby-node closure) — stale entries miss and recompile. Relocating only changes where the cache lives so gatsby clean stops deleting it. The repo's CI comment about .cache restores causing MDX incoherence doesn't apply: that was Gatsby's data-layer cache, not webpack's content-keyed packs.
  • genMDX payload persistence: keyed by node contentDigest plus a fingerprint of everything that changes compile output (plugin version, each gatsby remark plugin's entry-file sha1 and options) — bypassing gatsby's wipe-on-config-change means the key must carry the config, and it does. Verified headings/anchors render identically.
  • Renderer compile memoization: the compiled bundle never depends on the MDX bodies (they're passed to renderMdxBody at runtime); one compile per process renders byte-identical html to one per batch. The compiler.close() addition matches gatsby core's own engine bundlers and is required for webpack 5 to flush its filesystem cache at all.
  • Sitemap trim: the sitemap reads only attributes.permalink; verified the trimmed query returns identical permalinks (76 pages, 7565 questions).
  • Telemetry/compile cache: no output surface; NODE_COMPILE_CACHE is Node 22's supported V8 bytecode cache, content-validated.

Verified after every measured change: 2009 HTML pages, RSS feed 65 items, spot-checked docs heading anchors.

Operational notes

  • First build after changing dependencies, patches, or gatsby config files re-primes the invalidated webpack caches (they're buildDependencies, transitively including package.json via browserslist) and runs at roughly pre-cache speed; steady state resumes on the next build. This bit us twice during measurement (iterations 4 and 7) before we learned to prime first.
  • Cache footprint: ~2.9GB under node_modules/.cache/ locally; the CI artifact (saved daily by cache-warmup.yml, restored by deploy-preview.yml, keyed on lockfile+patches with prefix fallback) compresses to well under GitHub's 10GB repo quota.
  • Measurement discipline mattered: three iterations produced contaminated or crashed reads under host load 30-80 (documented in the run log); all conclusions above come from quiet-host, primed, pnpm clean'd runs with per-phase timer attribution.

Created with PostHog Code

Persistent, content-revalidated caches plus targeted fixes, measured with
pnpm clean && GATSBY_MINIMAL=true pnpm build between every change:

- relocate webpack's filesystem caches outside .cache so `gatsby clean`
  stops discarding them: site build-javascript/build-html stages
  (gatsby-node.ts), gatsby core's query-engine and page-ssr engine
  bundlers (patches/gatsby+4.25.9.patch), and gatsby-plugin-mdx's feed
  html renderer (patch, incl. the missing compiler.close() without which
  webpack 5 never flushes that cache to disk). Webpack revalidates every
  entry against module contents and build dependencies, so stale entries
  miss instead of misresolving.
- persist gatsby-plugin-mdx genMDX payloads (headings/body/html source
  data) outside .cache, keyed by node contentDigest plus a fingerprint of
  compile-affecting config (plugin version, each gatsby remark plugin's
  entry-file hash and options)
- compile the feed html renderer once per process instead of once per
  DataLoader batch (was dozens of identical near-full-app webpack
  compiles during onPostBuild)
- trim the sitemap's community-question fetch to fields=[permalink]
  (was populate=* — ~50x the transfer for identical output)
- disable gatsby telemetry in build scripts and enable Node 22's
  NODE_COMPILE_CACHE for the build process tree
- wire the persistent caches into CI following the existing
  warm-on-master/restore-on-PR pattern (cache-warmup.yml saves daily,
  deploy-preview.yml restores keyed on lockfile+patches)

Local steady state: 476.8s -> 82.4s (phases: webpack trio 255s -> 18s,
createPages 57s -> 1.5s, onPostBuild 67s -> 13s). First build after a
dependency, patch, or config change re-primes the invalidated caches and
runs slower; steady state resumes on the next build.

Generated-By: PostHog Code
Task-Id: 0cdc3a81-9997-4d76-91d3-03e2b599033c
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Deploy preview

Status Details Updated (UTC)
🟢 Ready View preview Jul 17, 2026 05:11PM

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef3d443ce5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread patches/gatsby-plugin-mdx+3.20.0.patch Outdated
Comment thread patches/gatsby-plugin-mdx+3.20.0.patch
@github-actions

Copy link
Copy Markdown
Contributor

Bundle report

Total JS (gzip)

6.73 MiB (-5.9 KiB / -0.1%)

Largest changed named chunks
Chunk Gzip Δ vs master
app.js 2119.9 KiB -6.1 KiB / -0.3%

Eager graph (modules shipped in each entrypoint's initial chunks)

Entrypoint Eager size Budget Modules
app 16.11 MiB (-17.3 KiB / -0.1%) report-only 1956
Largest modules in the app closure
Module Size
./src/data/mcp-tools.json 839.7 KiB
css ./node_modules/.pnpm/css-loader@5.2.7_webpack@5.101.3/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[8].oneOf[1].use[1]!./node_modules/.pnpm/postcss-loader@4.3.0_postcss@8.5.6_webpack@5.101.3/node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[8].oneOf[1].use[2]!./src/styles/global.css 738.5 KiB
./src/components/Stickers/Stickers.tsx 696.4 KiB
./node_modules/.pnpm/@radix-ui+react-icons@1.3.2_react@18.3.1/node_modules/@radix-ui/react-icons/dist/react-icons.esm.js 481.4 KiB
./node_modules/.pnpm/rehype-raw@7.0.0/node_modules/rehype-raw/lib/index.js + 29 modules 395.1 KiB
./node_modules/.pnpm/@posthog+icons@0.36.6_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.cjs.js 364.8 KiB
./src/hooks/useCustomers.tsx + 54 modules 355.1 KiB
./node_modules/.pnpm/@posthog+icons@0.36.6_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js 354.8 KiB
./node_modules/.pnpm/react-markdown@8.0.7_@types+react@16.14.66_react@18.3.1/node_modules/react-markdown/lib/react-markdown.js + 88 modules 351.4 KiB
./node_modules/.pnpm/cloudinary-core@2.14.0_lodash@4.17.21/node_modules/cloudinary-core/cloudinary-core.js 281.9 KiB
./src/components/ProductComparisonTable/index.tsx + 120 modules 278.6 KiB
./src/components/SearchUI/index.tsx + 87 modules 271.8 KiB
./gatsby-browser.tsx + 45 modules 248.0 KiB
./node_modules/.pnpm/d3@7.9.0/node_modules/d3/src/index.js + 208 modules 247.4 KiB
./src/components/Pricing/PricingSlider/Slider.tsx + 87 modules 239.9 KiB

Eager-graph budgets are report-only until a baseline is established. Sizes are gzip of public/**/*.js; eager size is webpack module source bytes for the modules actually shipped in the entrypoint's initial chunks (post-tree-shake).

- Fingerprint gatsby remark plugins by their runtime pluginOptions (the
  field get-source-plugins-as-remark-plugins.js actually passes to each
  plugin) falling back to the raw config options, and hash the resolved
  modulePath when present. Previously an options-only change (e.g.
  gatsby-remark-autolink-headers { icon }) would not rotate the
  fingerprint, letting restored payload caches serve stale body/html.
  Bumped the fingerprint version to invalidate previously saved entries.
- Preserve gatsby's stage buildDependencies (its webpack config plus
  every plugin's gatsby-node closure) in the mdx html renderer's cache
  config instead of replacing them with just the patched file, so site
  webpack config changes (aliases/loaders) still invalidate the
  renderer's persistent cache across restored builds.

Generated-By: PostHog Code
Task-Id: 0cdc3a81-9997-4d76-91d3-03e2b599033c
@pauldambra

pauldambra commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Note

🤖 Bot comment — written by PostHog Code (AI agent) on Paul's behalf.

Both Codex findings verified against the code and fixed in f09e655:

  1. gatsbyRemarkPlugins fingerprint used the wrong options field — confirmed get-source-plugins-as-remark-plugins.js invokes each plugin with plugin.pluginOptions || {}, while the fingerprint hashed plugin.options (the raw config shape). The fingerprint now hashes pluginOptions || options, uses modulePath for the entry-file hash when present, and its version marker is bumped (ph-mdx-persist-2) so all previously persisted payloads are invalidated rather than trusted under the old key shape.

  2. Renderer cache dropped gatsby's buildDependencies — the mdx-html-renderer cache config now merges the cloned stage config's buildDependencies (gatsby's webpack config + every plugin's gatsby-node closure, which covers site onCreateWebpackConfig alias/loader changes) and appends the patched file, instead of replacing them with only the patched file.

Verified the updated patch applies cleanly from the pristine tarball with all modification markers present.

@pauldambra

Copy link
Copy Markdown
Member Author
CleanShot 2026-07-17 at 18 14 22@2x

@pauldambra

Copy link
Copy Markdown
Member Author

Note

🤖 Bot comment — written by PostHog Code (AI agent) on Paul's behalf.

Can the two patches in this PR be retired by dependency updates?

No — checked against the registry and upstream source rather than assumed:

patches/gatsby+4.25.9.patch (engine-bundler cache relocation)

  • 4.25.9 is npm's latest-v4 — the final v4 release; there is no v4 update to take.
  • The latest stable gatsby@5.16.1 still hardcodes both engine caches inside .cache/ (dist/schema/graphql-engine/bundle-webpack.js:27.cache/webpack/query-engine, dist/utils/page-ssr-module/bundle-webpack.js:20.cache/webpack/page-ssr; verified by tarball inspection). Even a full Gatsby 5 migration would not obsolete this patch — it would need re-basing onto 5.16.1. Upstreaming a configurable cache location is plausible but Gatsby is in maintenance mode under Netlify.

patches/gatsby-plugin-mdx+3.20.0.patch (genMDX payload persistence, renderer compile-once/close, parse fast-path)

  • 3.20.0 is the effective end of the v3 line — the 3.21.0-next.* prereleases have byte-identical source in the patched files (verified during feat(autoresearch): cut gatsby source-and-transform time from ~298s to ~10s #18650's review).
  • The v4/v5 rewrite (MDX v2) removes both patched subsystems outright: genMDX doesn't exist (compilation moves into webpack loaders with different caching) and utils/render-html.js is deleted because the Mdx.html field itself was removed. So this patch disappears under that upgrade — but the migration is the ~3,000-file MDX v1→v2 content migration discussed on feat(autoresearch): cut gatsby source-and-transform time from ~298s to ~10s #18650, plus this site's RSS feed config queries html on Mdx nodes, so gatsby-config.js's feed serialization would need rewriting as part of the same effort.

Practical upshot

Both patches are the right mechanism for the current stack. The mdx patch has a defined (if expensive) retirement path via the MDX v2 migration; the gatsby patch survives even that. Both are pinned to exact versions, so any future dependency bump fails loudly at patch-package time rather than silently dropping the optimizations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant