Skip to content

perf: optimize content-type and schema parsing, fix grammar in warnings #6261

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

nitin-is-me
Copy link

Description

This PR optimizes content-type header parsing, schema iteration in the normalizeSchema function and rectifies few grammatical mistakes in warnings file.

Changes Made

  • Avoided repeated content-type header parsing in validation file
  • Replaced Object.keys(contentProperty) with for...in loop in body schema file
  • Replaced Object.keys(contentProperty) with for...in loop in response schema file
  • Rectified grammatical mistakes in warnings file

Checklist

Signed-off-by: Nitin <nitinjha2609@gmail.com>
Signed-off-by: Nitin <nitinjha2609@gmail.com>
Signed-off-by: Nitin <nitinjha2609@gmail.com>
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@nitin-is-me nitin-is-me changed the title Improvements perf: optimize content-type and schema parsing, fix grammar in warnings Jul 23, 2025
@nitin-is-me
Copy link
Author

Hey @mcollina, thanks for the review and approval! Looks like the PR is just waiting on workflow approval now. Let me know if anything else is needed!

@metcoder95 metcoder95 added the benchmark Label to run benchmark against PR and main branch label Jul 23, 2025
Copy link

Node: 20
PR: [1] 1331k requests in 30.06s, 250 MB read
MAIN: [1] 1246k requests in 30.06s, 234 MB read


Node: 22
PR: [1] 1173k requests in 30.05s, 220 MB read
MAIN: [1] 1180k requests in 30.05s, 222 MB read


Node: 24
PR: [1] 1329k requests in 30.06s, 250 MB read
MAIN: [1] 1400k requests in 30.04s, 263 MB read

Copy link

Node: 20
PR: [1] 1474k requests in 30.05s, 277 MB read
MAIN: [1] 1463k requests in 30.04s, 275 MB read


Node: 22
PR: [1] 1479k requests in 30.04s, 278 MB read
MAIN: [1] 1556k requests in 30.04s, 292 MB read


Node: 24
PR: [1] 1486k requests in 30.03s, 279 MB read
MAIN: [1] 1528k requests in 30.03s, 287 MB read

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jul 23, 2025
@nitin-is-me
Copy link
Author

I added the request.contentType || check primarily as a safety optimization, in cases where contentType is already parsed and available, this avoids unnecessarily re-parsing the header. In most scenarios this should help reduce overhead, but I understand that even a small conditional like this can affect performance due to branch prediction and V8 optimization patterns.

Happy to revert this if consistency or raw performance is preferred. Just wanted to ensure we're not doing redundant work if the value already exists.

Signed-off-by: Nitin <150040530+nitin-is-me@users.noreply.github.com>
@Fdawgs
Copy link
Member

Fdawgs commented Jul 23, 2025

This looks to be slower from the benchmarks?

From what I understand, for...in does prototype chain traversal which slows it down compared to an imperative for loop, and similar benchmarks back this up.

Better optimisation could be just caching the keys.length.

@nitin-is-me
Copy link
Author

@Fdawgs Totally get the benchmark concern, but just double-checked the actual context here:

  • We're explicitly guarding against custom prototypes (Object.getPrototypeOf(schema) !== Object.prototype)
  • The objects we're looping (schema.content, response[code].content) are plain user-defined configs
  • So for...in won't traverse any unexpected prototype chain

Plus, avoiding Object.keys() means less memory churn (no array alloc) in these loops. So in this very specific case, for...in should be safe and slightly leaner.
That said, I’m cool either way. Happy to switch for consistency if needed. Just wanted to clarify the reasoning.

Fdawgs
Fdawgs previously approved these changes Jul 23, 2025
@nitin-is-me nitin-is-me mentioned this pull request Jul 23, 2025
4 tasks
Copy link
Member

@gurgunday gurgunday left a comment

Choose a reason for hiding this comment

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

Sorry if I missed it, but where do we verify contentProperty's prototype chain?

AFAICT, we verify schema's proto chain an not schema.content's

@nitin-is-me
Copy link
Author

nitin-is-me commented Jul 23, 2025

Thanks for raising that, @gurgunday.
You're absolutely right that isCustomSchemaPrototype is directly called on schema (and routeSchemas.response[code]), not explicitly on schema.content. However, the key lies in the conditional execution:

  • The for...in loop on contentProperty (which is schema.content) is nested inside an if block that ensures its parent schema has already passed the !isCustomSchemaPrototype(schema) check. This means the code branch where schema.content is iterated only runs if schema itself is confirmed to be a plain JavaScript object (i.e., Object.getPrototypeOf(schema) === Object.prototype).
  • Within our application's design, schemas (and their sub-properties like content) are expected to be plain JavaScript objects, often deserialized from JSON or constructed via object literals ({}). When a property of a plain object is itself another direct plain object, it naturally inherits Object.prototype. We're not dealing with scenarios where schema.content would be an instance of a custom class or a built-in object (like Map or Date) that has enumerable properties on its prototype chain, as that would deviate from the expected schema structure.

Therefore, because the parent schema is guaranteed to be a plain object, and schema.content is a direct property within that structure, the for...in loop on contentProperty remains safe and efficient. It will only iterate over the own enumerable properties of contentProperty, just as Object.keys() would, but without the overhead of creating an intermediate array.

This approach leverages the existing checks to gain a small memory allocation benefit while maintaining correctness for the expected schema shapes.
Does that help clarify the reasoning behind it?

@Fdawgs Fdawgs dismissed their stale review July 28, 2025 10:13

Tentative on change after further discussions.

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.

5 participants