-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Parallelize rendering of sibling components to avoid async waterfalls #7071
Parallelize rendering of sibling components to avoid async waterfalls #7071
Conversation
🦋 Changeset detectedLatest commit: 663cdd6 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
packages/astro/test/parallel.js
Outdated
let firstStart = Number($('.start').first().text()); | ||
let lastStart = Number($('.start').last().text()); | ||
let timeTook = lastStart - firstStart; | ||
expect(timeTook).to.be.lessThan(50, 'the components in the list were kicked off more-or-less at the same time.'); |
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.
I know this was written this way in the original PR as well, but this message will be shown if the test fails.
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.
Thanks for the heads up, I've seen both interpretations (putting the expected behaviour or the error message in there), but I'm happy to make it more explicit 👍
This addresses a major concern. Thanks for working on this. |
packages/astro/test/parallel.js
Outdated
let firstStart = Number($('.start').first().text()); | ||
let lastStart = Number($('.start').last().text()); | ||
let timeTook = lastStart - firstStart; |
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.
All components are kicked off at the same time, so timeTook here will be always be 0 or 1.
I think the test should measure the longest time difference instead.
let firstStart = Number($('.start').first().text()); | |
let lastStart = Number($('.start').last().text()); | |
let timeTook = lastStart - firstStart; | |
const startTimes = Array.from($('.start')).map(element => Number(element.children[0].data)) | |
const finishTimes = Array.from($('.finished')).map(element => Number(element.children[0].data)) | |
const timeTook = Math.max(...finishTimes) - Math.min(...startTimes); |
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.
For my understanding, the fact that all kick off at the same time is the result of this change. Otherwise, they would render in order and the start times would differ by more than 50ms. The big threshold of 50ms is a bit misleading though (I just copied that from the old pr as well)
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.
Yeah I agree that the number is misleading. It's what made me think the intention is to measure the render time. Maybe the duration limit could be shortened and renamed to kickedOffWithin
?
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.
I also liked your idea, so I added the check as well. It's maybe not the cleanest unit test, as it tests more than one thing, but I think it's still OK.
… once a component doesn't resolve immediatly
I changed the behavior a bit to not start buffering right away, but only when an async component is reached (e.g. the event loop picks up the I ran benchmarks with
|
Thank you very much, when this gets merged the world becomes a lot better! This would close the discussion withastro/roadmap#577
If I get it right, you mean every async subcomponent would have its own |
packages/astro/src/runtime/server/render/astro/render-template.ts
Outdated
Show resolved
Hide resolved
To add a bit of context: the generic rendering operation is async, so it can either return a promise, or return an immediately resolved promise, that will basically synchronously continue the code execution. Caching a resolved promise is not only superfluous, but adds performance overhead, as we (synchronously) write everything into a buffer, just to read it again later, but we could have as well just streamed the output to the client. In this scenario, we don't know whether a rendering operation will immediately resolve or not. So I opted for a middleground. We will instantiate the Say the layout has 4 components:
We start by wrapping all in the
I hope this explains the process and also why the Note also that this happens recursively in every child component. |
@ematipico Thanks for the detailed review. I added more documentation. I find it kinda hard to estimate what is understandable to somebody who didn't think about this for hours 😅 so please keep bugging me if there still is something not clear from the comments. |
On top of Ema's review, I did some perf runs using I guess a tiny concern is that running many async stuff in parallel could pressure the CPU and memory a bit, but I think in practice pages/components wouldn't be doing many intensive stuff at once. |
True, it might a bit of overhead to the internal event loop etc. as we need to keep track of the different rendering states, but still JavaScript is single-threaded, so it can't really do things in parallel. It can only do other stuff while we're waiting for external I/O (mostly network I assume). |
@johannesspohr It seems like we are pretty close on this one. We are doing the 2.5 release tomorrow. If you think you can address the review comments this could possibly go out then. Otherwise 2.6 will be in a couple of weeks. |
@matthewp I was able to resolve everything except for that one thing where I'm unsure if this needs further documentation or tests. However, from a functionality standpoint, we should be good to go. |
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 addressing my pedantic feedback! 😅 💪 🚀
Thanks so much @johannesspohr, happy to have this in. |
Hi guys, just tested the solution, it works for single components. However, it doesn't for fragment arrays, here is the follow-up issue #7127 |
Changes
This changes the rendering behavior of Astro components to render child components in parallel.
In cases where multiple components in different subtrees fetch data, this data fetching can be done in parallel and will improve loading times in those scenarios.
Testing
A test is added to confirm the parallel execution of the artificial delays. Also, an existing test (number of stream chunks) had to be changed, because the parallel execution lead to 2 chunks instead of the expected 3.
Docs
Docs & changeset will be added as this is just a draft