Skip to content
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

fix: prevent excessive requests to access endpoint #7597

Merged
merged 8 commits into from
Oct 15, 2024

Conversation

rexxars
Copy link
Member

@rexxars rexxars commented Oct 5, 2024

Description

This one needs a proper read-through, I am not super confident with these changes - especially the last commit. There might be a lot easier ways to solve the problem outlined in the title here, but let me explain:

When researching why there were constant requests being fired to the /access API, I saw that it was due to a hook being run inside of the <PermissionCheckBanner />. Upon further inspection, I saw that this component remounts (unmounts and mounts) constantly while edits are going on, due to the continuous evaluation of permissions as the document changes. When this happens, the isPermissionsLoading state gets set to true, which triggers the banner to unmount. Once it's calculated if it should be allowed/disallowed, it re-mounts. My hunch is that this is not only causing the additional requests that we want to get rid of, but potentially also affects performance negatively.

While reading the code of the useDocumentValuePermissions and the underlying createHookFromObservableFactory and friends, it seems like the intention is to keep the old state while recalculating - but because the document being passed as an argument is recreated on any edit, the observable factory gets called again and the state resets. In my mind it would be better (for the permission check in particular) to assume that the grants don't change that quickly, and keep the old cached value around. I refactored the code to do this, but the code is a lot simpler/more naive than createHookFromObservableFactory + useObservable, so I'd be happy to have some more eyes on this and see if there are edge cases it wouldn't account for.

Note

While writing this, I realized that we should probably throw away that cached value on document ID change.

Running the new eFPS suite, the eFPS looks about the same, while blocking time seems lower - lets see what another run says once I open the PR.

Aside from the above, I also refactored some code a bit - using observable requests for cancellation, minimizing state updates with predefined constants and preferring to "return early" from functions for less indentation (easier to read in my book).

What to review

I'd do commit-by-commit - most of the changes should be fairly straightforward, except the last two. In particular, the last change needs a very thorough read-through.

Testing

I want to add some tests for useDocumentValuePermissions, but figured I would open this up for an early look since it needs a proper read-through.

Notes for release

  • Fixed an issue where unnecessary requests to the API would be triggered while editing a document

@rexxars rexxars requested a review from a team as a code owner October 5, 2024 00:34
@rexxars rexxars requested review from juice49 and removed request for a team October 5, 2024 00:34
Copy link

vercel bot commented Oct 5, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
page-building-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2024 9:58am
performance-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2024 9:58am
test-compiled-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2024 9:58am
test-next-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2024 9:58am
test-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 9, 2024 9:58am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
studio-workshop ⬜️ Ignored (Inspect) Visit Preview Oct 9, 2024 9:58am

Copy link
Contributor

github-actions bot commented Oct 5, 2024

No changes to documentation

Copy link
Contributor

github-actions bot commented Oct 5, 2024

Component Testing Report Updated Oct 9, 2024 10:03 AM (UTC)

✅ All Tests Passed -- expand for details
File Status Duration Passed Skipped Failed
comments/CommentInput.spec.tsx ✅ Passed (Inspect) 44s 15 0 0
formBuilder/ArrayInput.spec.tsx ✅ Passed (Inspect) 9s 3 0 0
formBuilder/inputs/PortableText/Annotations.spec.tsx ✅ Passed (Inspect) 31s 6 0 0
formBuilder/inputs/PortableText/copyPaste/CopyPaste.spec.tsx ✅ Passed (Inspect) 38s 11 7 0
formBuilder/inputs/PortableText/copyPaste/CopyPasteFields.spec.tsx ✅ Passed (Inspect) 0s 0 12 0
formBuilder/inputs/PortableText/Decorators.spec.tsx ✅ Passed (Inspect) 18s 6 0 0
formBuilder/inputs/PortableText/DisableFocusAndUnset.spec.tsx ✅ Passed (Inspect) 11s 3 0 0
formBuilder/inputs/PortableText/DragAndDrop.spec.tsx ✅ Passed (Inspect) 3m 0s 0 0 0
formBuilder/inputs/PortableText/FocusTracking.spec.tsx ✅ Passed (Inspect) 45s 15 0 0
formBuilder/inputs/PortableText/Input.spec.tsx ✅ Passed (Inspect) 1m 42s 21 0 0
formBuilder/inputs/PortableText/ObjectBlock.spec.tsx ✅ Passed (Inspect) 1m 18s 18 0 0
formBuilder/inputs/PortableText/PresenceCursors.spec.tsx ✅ Passed (Inspect) 9s 3 9 0
formBuilder/inputs/PortableText/RangeDecoration.spec.tsx ✅ Passed (Inspect) 26s 9 0 0
formBuilder/inputs/PortableText/Styles.spec.tsx ✅ Passed (Inspect) 19s 6 0 0
formBuilder/inputs/PortableText/Toolbar.spec.tsx ✅ Passed (Inspect) 36s 12 0 0
formBuilder/tree-editing/TreeEditing.spec.tsx ✅ Passed (Inspect) 0s 0 3 0
formBuilder/tree-editing/TreeEditingNestedObjects.spec.tsx ✅ Passed (Inspect) 0s 0 3 0

Copy link
Contributor

github-actions bot commented Oct 5, 2024

⚡️ Editor Performance Report

Updated Wed, 09 Oct 2024 10:14:49 GMT

Benchmark reference
latency of sanity@latest
experiment
latency of this branch
Δ (%)
latency difference
article (title) 18.5 efps (54ms) 16.8 efps (60ms) +6ms (+10.2%)
article (body) 59.9 efps (17ms) 56.2 efps (18ms) +1ms (+6.6%)
article (string inside object) 19.2 efps (52ms) 17.5 efps (57ms) +5ms (+9.6%)
article (string inside array) 15.4 efps (65ms) 13.9 efps (72ms) +7ms (+10.8%)
recipe (name) 30.3 efps (33ms) 30.3 efps (33ms) +0ms (-/-%)
recipe (description) 34.5 efps (29ms) 34.5 efps (29ms) +0ms (-/-%)
recipe (instructions) 99.9+ efps (6ms) 99.9+ efps (6ms) +0ms (-/-%)
synthetic (title) 15.0 efps (67ms) 14.5 efps (69ms) +3ms (+3.8%)
synthetic (string inside object) 15.9 efps (63ms) 14.7 efps (68ms) +5ms (+7.9%)

efps — editor "frames per second". The number of updates assumed to be possible within a second.

Derived from input latency. efps = 1000 / input_latency

Detailed information

🏠 Reference result

The performance result of sanity@latest

Benchmark latency p75 p90 p99 blocking time test duration
article (title) 54ms 59ms 70ms 193ms 978ms 14.3s
article (body) 17ms 19ms 25ms 157ms 399ms 5.6s
article (string inside object) 52ms 56ms 62ms 176ms 810ms 8.7s
article (string inside array) 65ms 73ms 82ms 187ms 1605ms 9.8s
recipe (name) 33ms 36ms 68ms 110ms 110ms 10.6s
recipe (description) 29ms 31ms 37ms 68ms 27ms 6.3s
recipe (instructions) 6ms 8ms 9ms 10ms 0ms 3.2s
synthetic (title) 67ms 71ms 85ms 239ms 1930ms 16.4s
synthetic (string inside object) 63ms 69ms 81ms 423ms 2157ms 10.2s

🧪 Experiment result

The performance result of this branch

Benchmark latency p75 p90 p99 blocking time test duration
article (title) 60ms 65ms 114ms 351ms 454ms 15.3s
article (body) 18ms 19ms 25ms 130ms 211ms 6.0s
article (string inside object) 57ms 63ms 70ms 237ms 273ms 8.6s
article (string inside array) 72ms 75ms 84ms 289ms 1089ms 10.2s
recipe (name) 33ms 36ms 57ms 88ms 8ms 10.3s
recipe (description) 29ms 31ms 41ms 122ms 1ms 6.2s
recipe (instructions) 6ms 9ms 9ms 10ms 0ms 3.2s
synthetic (title) 69ms 73ms 115ms 532ms 1513ms 19.6s
synthetic (string inside object) 68ms 72ms 81ms 365ms 1661ms 10.7s

📚 Glossary

column definitions

  • benchmark — the name of the test, e.g. "article", followed by the label of the field being measured, e.g. "(title)".
  • latency — the time between when a key was pressed and when it was rendered. derived from a set of samples. the median (p50) is shown to show the most common latency.
  • p75 — the 75th percentile of the input latency in the test run. 75% of the sampled inputs in this benchmark were processed faster than this value. this provides insight into the upper range of typical performance.
  • p90 — the 90th percentile of the input latency in the test run. 90% of the sampled inputs were faster than this. this metric helps identify slower interactions that occurred less frequently during the benchmark.
  • p99 — the 99th percentile of the input latency in the test run. only 1% of sampled inputs were slower than this. this represents the worst-case scenarios encountered during the benchmark, useful for identifying potential performance outliers.
  • blocking time — the total time during which the main thread was blocked, preventing user input and UI updates. this metric helps identify performance bottlenecks that may cause the interface to feel unresponsive.
  • test duration — how long the test run took to complete.

@juice49
Copy link
Contributor

juice49 commented Oct 7, 2024

Hey @rexxars. I've been reading through the changes, and the premise seems good to me. I could do with spending a bit more time to give a definitive 👍/👎.

You mentioned you want to add some tests and that you opened this PR for an early look. Are you hoping to get this change merged by tomorrow?

@rexxars
Copy link
Member Author

rexxars commented Oct 7, 2024

Hey @rexxars. I've been reading through the changes, and the premise seems good to me. I could do with spending a bit more time to give a definitive 👍/👎.

You mentioned you want to add some tests and that you opened this PR for an early look. Are you hoping to get this change merged by tomorrow?

Might be a bit hopeful, I don't think realistically I/we will have much time for it this week :/

@juice49
Copy link
Contributor

juice49 commented Oct 7, 2024

Okay, thanks for confirming. I will come back to it, as I'm likely to run out of time before tomorrow.

Rendering the banner also triggers requests to APIs and calls other hooks. This is potentially expensive. The contents of the banner is never shown if the user has the right access to the document, and thus it is more efficient to do this check outside of the banner, before mounting it. Additionally, the name isn't quite right; it doesn't really _check_ for permissions, it just shows an error if told that it doesn't have the correct permissions. I've therefore chosen to rename it to `InsufficientPermissionsBanner` as I find that more precise.
@bjoerge bjoerge self-requested a review October 12, 2024 12:37
subscriptionRef.current = null
}
}
}, [grantsStore, document, permission])
Copy link
Contributor

Choose a reason for hiding this comment

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

This is one potential difference I can see between the createHookFromObservableFactory implementation and your implementation: the former uses useUnique, which in turn uses lodash.isEqual to compare the args. You instead have added an effect with object dependencies that will behave differently.

Looks like you may have mitigated this by using disctinctUntilChanged with lodash.isEqual in the permissions$ observable—and there doesn't seem to be any issues—but thought I'd mention it in case.

Copy link
Contributor

@juice49 juice49 left a comment

Choose a reason for hiding this comment

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

Conducted some testing and carefully compared to the previous implementation. It seems sound to me. Happy to re-review after tests added 👍.

Copy link
Member

@bjoerge bjoerge left a comment

Choose a reason for hiding this comment

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

This looks solid to me. I've read through carefully and also tested quite a bit manually, and have not seen any issues. Thanks for not only fixing the issue at hand, but also for cleaning up and improving the code along the way! <3

@rexxars rexxars added this pull request to the merge queue Oct 15, 2024
Merged via the queue into next with commit 45a74fc Oct 15, 2024
65 checks passed
@rexxars rexxars deleted the fix/excessive-access-requests branch October 15, 2024 17:00
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.

3 participants