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

Performance degradation when using many routes in comparison to Nuxt 2 #25612

Open
BobbieGoede opened this issue Feb 4, 2024 · 6 comments
Open

Comments

@BobbieGoede
Copy link
Member

BobbieGoede commented Feb 4, 2024

Environment

n/a

Reproduction

I adapted and simplified reproduction from #25362 to generate ~1100 routes, all routes share the same component/page (index). I have kept the projects pretty much empty or unchanged other than that.

Nuxt 2
https://stackblitz.com/edit/github-4f1tg8?file=nuxt.config.js

Nuxt 3
https://stackblitz.com/edit/github-ibrygb?file=nuxt.config.ts

Build each project and test the performance of each project using ab or siege and compare the results. The projects can be built again while passing PATH_MULTIPLIER (default: 100), this way you can rebuild the projects with more or fewer routes to see how that changes the results.

Describe the bug

111 routes (PATH_MULTIPLIER=10)

  • Running siege -t 10s -c 100 "http://localhost:3000/"
  • Results here are comparable
Nuxt 2 Nuxt 3
Transactions 13634 hits 10573 hits
Availability 100.00 % 100.00 %
Elapsed time 9.82 secs 9.54 secs
Data transferred 246.55 MB 827.06 MB
Response time 0.07 secs 0.09 secs
Transaction rate 1388.39 trans/sec 1108.28 trans/sec
Throughput 25.11 MB/sec 86.69 MB/sec
Concurrency 99.51 99.27
Successful transactions 13634 10573
Failed transactions 0 0
Longest transaction 0.33 0.43
Shortest transaction 0.01 0

1101 routes (PATH_MULTIPLIER=100)

  • Running siege -t 10s -c 100 "http://localhost:3000/"
  • Nuxt 2 requests took ~2x longer than previous scenario
  • Nuxt 3 requests took >10x longer than previous scenario
Nuxt 2 Nuxt 3
Transactions 7047 hits 668 hits
Availability 100.00 % 100.00 %
Elapsed time 9.09 secs 9.27 secs
Data transferred 138.23 MB 85.60 MB
Response time 0.13 secs 1.28 secs
Transaction rate 775.25 trans/sec 72.06 trans/sec
Throughput 15.21 MB/sec 9.23 MB/sec
Concurrency 98.71 92.08
Successful transactions 7047 668
Failed transactions 0 0
Longest transaction 0.64 2.66
Shortest transaction 0.01 0.04

I also ran tests using ab with ab -n 1000 -c 50 http://localhost:3000/, leaving results out for legibility (and formatting is tedious).

Additional context

Users migrating from Nuxt 2 to Nuxt 3 while using the Nuxt I18n module experience memory and performance issues, due to the way routes are generated for i18n it results in the amount of routes in a project multiplied by the amount of configured languages.

Of course I will be looking into ways to improve this on the i18n module side, but as it is, the amount of routes in a project has a large impact on its performance, much more so in Nuxt 3 than in Nuxt 2.

Logs

No response

Copy link

stackblitz bot commented Feb 4, 2024

@obulat
Copy link

obulat commented Feb 4, 2024

This issue is affecting the Openverse Nuxt 3 migration. We have around 20 routes multiplied by the number of locales, which is more than 50.

It seems that Nuxt creates a new router per user request.

const router = createRouter({

Each call to createRouter in this plugin also makes vue-router to create a new matcher

https://github.com/vuejs/router/blob/4d3cbf58295055ce045efda13db6a1afd144d172/packages/router/src/matcher/index.ts#L349

When I was researching the memory leak in Openverse Nuxt 3 migration branch, I often saw the matcher object retained.
When would each router created in runtime/plugins/router be garbage collected?

@danielroe
Copy link
Member

We do need to create a router per-request. But if there's something preventing it from being garbage collected that should definitely be investigated. Would be worth trying to test without Nuxt via https://stackblitz.com/github/nuxt-contrib/vue3-ssr-starter to see if we can rule out vue-router itself as the issue.

For what it's worth, I'm not seeing a huge memory leak when running the reproduction provided by @BobbieGoede. It basically stays at around ~18Mb heap size on my machine for each siege batch. But happy to look into it a bit more...

@BobbieGoede
Copy link
Member Author

BobbieGoede commented Feb 5, 2024

I have updated your reproduction without Nuxt to generate 1101 pages, you can find it here, note this doesn't accept PATH_MULTIPLIER env variable to adjust the amount of routes generated, change dirCount instead.

Without Nuxt the performance also deteriorates in the same way in proportion to the number of routes, so I think we can assume that changes made between vue-router v3 and v4 have had a large impact on its performance (unless Nuxt 2 didn't recreate a new router on each request).

To me it looks like the main suspect would be insertMatcher as it compares a route matcher with all other matchers for every added route, up until its sorted position has been found which is when it is spliced into place. This happens during router creation while adding all initial routes, it doesn't sound like it would scale well with large amounts of routes (I couldn't find its equivalent in Vue Router v3).

Is it possible for routes to be different at router creation in Nuxt or does this essentially stay the same at the start of each request (route changes happen after creation)? I'll look into potential ways of caching some expensive calculations at router creation.

@danielroe
Copy link
Member

danielroe commented Feb 5, 2024

Thank you - great investigation ❤️

We should probably raise this in vue-router or see if we can pre-calculate any of this. A new router does need to be added as it's possible to manipulate the routes per-request, or add new routes. This was also what we did in Nuxt 2:

https://github.com/nuxt/nuxt/blob/2.x/packages/vue-app/template/index.js#L94-L96

@BobbieGoede
Copy link
Member Author

An update on this issue, I raised the issue in vue-router (vuejs/router#2132) and did an isolated performance comparison (vuejs/router#2132 (comment)) between Vue Router v3 and v4 which confirmed the performance difference.

The maintainer has suggested ways on how the performance can be improved (custom matcher and matcher pre-compilation) so I'm going to do some research and (hopefully) work on a solution soon 😄. If you have thoughts or suggestions on a good way to implement these, feel free to leave them on the issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants