-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Remove unnecessary null values from metadata #3626
Conversation
Thanks for the PR! We have also considered this as a potential optimization. The reason we haven't shipped this yet though is a hunch that this will not play well with JavaScript engines, which depend quite a bit on hidden classes for performance. The current output ensures that each node kind has a stable set of fields and field ordering, which means that each "instance" of a given node kind will (should) always get the same hidden class. This in turn allows the engine to more effectively monomorphize (specialize) code consuming those objects, ie Relay Runtime). As I mentioned though this is definitely a hunch - we don't currently have an easy way to test the performance impact of changes to the AST format at a scale representative of our production workloads. We're currently in the middle of building out a benchmark suite to help answer questions like this though, and we will definitely look at the perf impact of this change. In terms of next steps: we're supportive of adding an off-by-default option to the compiler that enables omitting null fields. We are not actively using the JavaScript compiler, so our recommendation would be to make this change only in the Rust compiler, which we are hoping to make a supported option in OSS relatively soon. Thoughts? |
In terms of next steps on implementation:
This should dramatically reduce the size of the PR :-) |
RE: JS Changes. I think we can just ignore JS compiler at this point. The release of Rust-based version is coming soon: #3180 |
463ae15
to
e2681c4
Compare
Thanks for the feedback. I've updated this PR to only make this change to the rust compiler. Feature flagged under I agree this might be a bytesize vs runtime tradeoff - interested to see the result. Nice to hear the new compiler is getting close to release. Some small contribution meta:
|
Thanks for updates to the PR @tomgasson and for contributors feedback! I think we'll need to figure out some of these steps (as we're don't really know some of them): correct rust version, fmt, running tests, etc. I've a task for that to update the contributors guide to reflect some of these steps: #3630 |
@alunyov has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator. |
) -> String { | ||
Printer::without_dedupe(js_module_format).print_operation(schema, operation) | ||
Printer::without_dedupe(js_module_format).print_operation(schema, operation, enable_reduced_metadata) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add this flag to the Printer
itself, so we do not pass it around all methods?
@@ -436,7 +437,7 @@ fn generate_split_operation( | |||
&mut content, | |||
"node", | |||
"NormalizationSplitOperation", | |||
&printer.print_operation(schema, normalization_operation), | |||
&printer.print_operation(schema, normalization_operation, &project_config.feature_flags.enable_reduced_metadata), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can create an instance printer
with the enable_reduced_metadata
option, so we don't have to pass it around?
Hey @tomgasson sorry for delay with this. So, I could not land this internally, some tests were failing, and once I start looking into these we have a big refactoring with the I think we can still add this PR, can you try rebasing it? |
287cfd9
to
9de3226
Compare
Sorry for the delay on coming back to this. Had to complete our move to the new compiler first. I've taken a slightly different approach now that a few things have changed like the config (and therefore flags) being easier to access.
|
@@ -176,9 +176,7 @@ return { | |||
"params": { | |||
"cacheID": "c13dedba1d0c8b17a192b735d141de99", | |||
"id": null, | |||
"metadata": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, why this has changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scripts/compile-tests.sh
was broken on main
for me.
I added "enable_provided_variables": {"kind": "enabled"}
to fix, and running it generated this change.
(maybe I was doing something wrong?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the follow up.
Can we figure out what happened with test artifacts? Is it something we need to fix separately? I don't think changes to metadata
in generated artifacts are expected here.
The fragment & query metadata contains many null values. This change adds a feature flag (`skip_printing_nulls`) to allow the rust compiler to avoid outputting those fields. The runtime will still operate on them, due to it's pervasive use of loose equality for null / undefined checks.
Fixed nits, and removed the commit which allowed |
@alunyov has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@tomgasson Thank you for the contribution! I'm really excited that we can provide this as an option. |
In `skip_printing_nulls` (facebook#3626) we change some `null` values to be undefined by skipping them in the output. Generally, the js code uses loose equality, either as * `field.X != null` * `field.X ?? default` * `if (field.X) {` which allows us to do this safely. The comparison this commit fixes was a rare exception to that showing up when using `@required`, mocking data and `skip_printing_nulls` is enabled.
Summary: In `skip_printing_nulls` (#3626) we change some `null` values to be undefined by skipping them in the output. Generally, the js code uses loose equality, either as * `field.X != null` * `field.X ?? default` * `if (field.X) {` which allows us to do this safely. The comparison this commit fixes was a rare exception to that showing up when using `required`, mocking data and `skip_printing_nulls` is enabled. Pull Request resolved: #3784 Reviewed By: captbaritone Differential Revision: D33965847 Pulled By: alunyov fbshipit-source-id: 98f414e4f90738fcb68c940d6ce7208734c49297
Hey folks 👋
We at Atlassian have some bundle size tooling that was flagging a larger query contributing about 200KB to our page load. That tooling isn't compression aware, so the actual size is more like 9KB when gzipped so it's not too big of a concern.
But it got me looking at what contributes to that size, and what we can do to reduce it. I noticed that the majority of metadata is null values. Whilst these compress really well, they still contribute a lot of unnecessary extra bytes over the wire. Removing these nulls reduces our gzipped metadata size by about 1/3.
The code in
relay-runtime
uses loose equality, either asfield.X != null
, orfield.X ?? default
, orif (field.X) {
, which means that we can safely remove these fields over the wire without runtime changes.As you can see by the stats on this PR, this removes a lot of LOC. Even though each individual impact is small, the number of queries / fragments involved mean it will probably have a decent production impact. For you at Meta, with persisted queries, it should look even better than our 1/3 reduction.
This PR addresses the following fields:
"alias": null
"args": null
"concreteType": null
"storageKey": null
"abstractKey": null
"metadata": null
"plural": false
. Most fields are non-plural, so we can use non-plural as a defaultRust compiler
In the rust compiler, this was quite easy to achieve. Instead of building objects as
object(items: Vec<ObjectEntry>)
, I've replaced these withobject_filter_none(items: Vec<Option<ObjectEntry>>)
which then filters out None values. ReplacingPrimitive::Null
values with a None just means inverting where the test happens.JS compiler
In the JS compiler, I've currently maintained the field order so that the snapshot diffs are clear. But I've also currently scattered a lot of $FlowFixMes there. Just want to check the approach first.
The JS compiler uses the same types as the runtime. Those types are all currently read-only, exact objects. This makes it a little difficult to "build" the fields / nodes
There's a few options here:
read-only
from field variance (+alias: ?string
) to the utility$ReadOnly<>
.$ReadOnly
annotations off the shared types and into the runtime (so the runtime can't write fields, but the shared types become writable forrelay-compiler
to use)$Shape<>
to build the objects inrelay-compiler
and then assert their output types after building them (don't let non-exact or writable types escape the functions in ReaderCodeGenerator, NormalizationCodeGenerator, but treat them as in-exact / writable within).Questions
This is a large PR right now. I'll break this down into clearer steps in order to ship it, but I wanted feedback before I go ahead with that. Questions:
null
s from the metadata sent to the client?plural
might be stretching it a bit far.