Repository navigation
perf(api): speed up the first GraphQL request on a cold Lambda - #5899
Conversation
- GraphQLEngine attaches resolvers to the schema it just built instead of letting graphql-tools copy the whole schema to add them (updateResolversInPlace). That copy was about half of the schema build. - Asset delivery no longer builds an S3 client in register(), which runs on every request. The client is now created when something resolves it, and reused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
🚓 Slop Cop ✅ Nothing worth flagging. The diff looks consistent with the PR's stated intent and the code-style rules. Small, focused perf PR that matches its stated intent; no integrity or style issues found. Automated, non-blocking heads-up from an LLM. It can be wrong — use your judgment. Regenerates on every push. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review. 📝 WalkthroughWalkthroughThe pull request changes S3 client registration to use a factory and configures static GraphQL schema creation to update resolvers in place. ChangesS3 asset delivery
GraphQL schema
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Change: Refactor Suggested reviewers: Merge Risk: ⚪ Minimal · up to No actionable issue is established for asset delivery or GraphQL schema creation; the change is mergeable after normal checks. Architecture SummaryArchitecture risk: 🔵 Low · up to The change affects 2 systems. Changed systems: Architecture concerns Review detailsSystems and components
Before / after behavior
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…oles-ui Brings in 21 commits from next, among them the encryption key cache (#5900), the cold-Lambda GraphQL speed-up (#5899), the audit logs use-case split (#5877, #5882) and access check (#5886), scheduler access gated on each app's permissions (#5907) and the release/6.5.0 entry form fixes (#5865). No conflicts. api-event-handler-standalone/src/createWebinyApiHandler.ts changed on both sides and merged on its own. It keeps the NodeHttpAssumePermissionsDecorator registration and next's new root EncryptionKeyCacheFeature, which are unrelated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What changed
GraphQLEnginepassesupdateResolversInPlace: truetomakeExecutableSchema. Without it,addResolversToSchemarunsmapSchema, which builds a full copy of the schema just to attach the resolvers. On a cold Lambda that copy took about 164 ms of the 323 ms schema build. The schema built fromtypeDefsis brand new and nothing else holds a reference to it, so attaching the resolvers in place is safe. graphql-tools rebuilds enum types and runshealSchemaafterwards, as designed.new S3(...)inregister(). That runs in the per-request container, so every GraphQL request built an S3 client, even ones that never touch files. It now usesregisterFactory(S3Client, () => createS3()): the client is created when something resolves it and cached per process.What
updateResolversInPlacedoesmakeExecutableSchemaworks in two steps:addResolversToSchema).By default, step 2 copies. graphql-tools treats schemas as immutable, so
mapSchemawalks every type and field and builds a newGraphQLObjectTypefor each one, with the resolvers set on the new fields, then a newGraphQLSchemaaround them. The step 1 schema is thrown away, so the schema is effectively built twice.With
updateResolversInPlace: true, step 2 sets the resolvers directly on the existing field objects (field.resolve = resolverFn). It also swaps in the custom scalar functions, rebuilds the enum types and repairs references to them withhealSchema. Nothing else is rebuilt.The copy only protects a schema that other code also holds, where changing it in place would change it for them. Here the step 1 schema is created inside the same
makeExecutableSchemacall from our own SDL, and nothing else ever sees it.Results
Measured on a 1024 MB Lambda with a
{ __typename }request: 8 forced cold starts and 25 warm requests per run, original and fixed builds alternating twice. The function ran without--enable-source-maps, which is how production runs (see below).Medians from each of the two runs. A cold start is about 120 ms faster, almost all of it in the first request. Warm p90 also dropped.
Testing
api-graphql: 50 of 50 pass.api-core: 228 of 228 pass.api-headless-cms: 975 pass and 16 are skipped, the same result as onnext.api-file-manager-s3: 9 of 9 pass.?width=128resize and a cache-busted request all returned 200 with a PNG, all CloudFront misses, and the function logged no errors.Found while measuring, not changed here
--enable-source-mapscosts about 250 ms of init.lambdaEnvVariables.tsonly sets it whenDEBUG=trueat deploy time. That applies to dev deploys, not production. So comparisons with 6.4.11 that came from aDEBUG=truedeploy overstated the gap. With the flag off,nextinit is about 1175 ms against 970 ms for 6.4.11.cheerioand its bundledundici, whichai-powerupsonly needs to translate pages (about 53 ms of module evaluation on Lambda, about 600 KB). The AI SDK (ai,@ai-sdk/*,@vercel/oidc,@workflow/serde, about 40 ms).nodemailerandiconv-lite(about 320 KB). These are candidates for loading on first use.Workspace: wby-next4 · ✨ Event Van Handler 🎸
🤖 Generated with Claude Code
Summary by CodeRabbit