Startup performance improvement exploration #16845
DanRibbens
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
I did an exploration around creating a build artifact of Payload for performance and concluded that this is NOT worth pursuing.
Question
Payload sanitizes the user config at runtime on every cold start. In serverless environments (e.g. Lambda) this was hypothesized to hurt startup time. Could we precompute sanitization into a build artifact to cut cold-start cost? A secondary motivation was DX: the runtime representation is inconsistent (
payload.collections[slug]is keyed, but globals requirepayload.globals.config.find(({ slug }) => slug === globalSlug)), and a separateClientConfigis already built to strip functions for client bundling.TL;DR
A build artifact is not worth pursuing for performance. Config sanitization is ~10 ms even for large configs — noise next to the real cold-start cost, which is loading the Lexical editor library and its default feature set (~400 ms), code that any richText-using app genuinely needs and that prod already tree-shakes correctly. The DX normalization (keyed global lookups) is worthwhile but stands on its own merits, independent of performance.
Why a full artifact is structurally infeasible anyway
A sanitized config is not serializable. Live functions are interleaved throughout the field tree —
access,validate,hooks,filterOptions,defaultValue, admin React components, editor adapters — and are frequently inline/anonymous (access: () => true), so they cannot be emitted as importable references. Configs are also built programmatically (plugins inject collections, fields, and even field types;NODE_ENVbranches; loops/spreads), so an artifact computed at build time can silently diverge from the live config at runtime. Sanitization also executes some functions (editor(), richTexteditor(),jobsCollectionOverrides(),timezone.supportedTimezones()), whose output is non-serializable and must re-run at runtime regardless.The best a "build artifact" could be is a precomputed data skeleton re-hydrated with live functions at runtime — but re-attaching functions requires walking the live tree, which is the same traversal sanitization already does, plus a drift-detection guard that falls back to full sanitize on mismatch (so all the sanitize code stays).
Measurements
All numbers from this repo (Node 24.15.0). Sanitize/eval runs are medians. Module-load runs are single cold imports; the dev (tsx) numbers include on-the-fly TS transpilation that production does not pay.
Config sanitization scales fine and is cheap
sanitizeConfig()median (with richText)Cold (un-JITted) first call at 100 collections: ~11 ms. Even a 400-collection config sanitizes in ~10 ms.
Module load dominates, and it's the editor library
lexicalEditor()executionKey structural fact:
@payloadcms/richtext-lexicalsets"sideEffects": ["*.scss","*.css"], so webpack/turbopack tree-shake the 49-feature barrel in a production build. The fat server barrel is therefore largely a dev-mode (tsx, no tree-shaking) tax, not a prod one.Conclusions
.js; precompiling does not help prod cold start.sideEffectsflag.Recommendations
payload.globals[slug]) consistent withpayload.collections[slug], so call sites stop doing O(n).find()scans. This is a non-breaking runtime-shape cleanup, unrelated to startup time.sanitizeConfigexecuteseditor()eagerly and most richText data operations need it, so the payoff is narrow (only cold starts that never touch richText) and the refactor is non-trivial. Would require its own investigation before committing.How these numbers were produced
Synthetic configs (N collections × mixed field types incl. group/array/tabs and an optional richText field) timed around
sanitizeConfig()with a non-connecting DB stub. Module-load numbers came from timing dynamicimport()of the lexical entry (both all-source via tsx and swc-precompileddist), and from importing the default-feature graph (dist/lexical/config/server/default.js) versus the full barrel to approximate the prod tree-shaking boundary. Benchmark scripts were temporary and removed after measurement.Beta Was this translation helpful? Give feedback.
All reactions