aleksilassila / reiverr

Reiverr is a clean combined interface for Jellyfin, TMDB, Radarr and Sonarr, as well as a replacement to Overseerr
GNU Affero General Public License v3.0
1.45k stars 44 forks source link

[PERF] Progressive Rendering #102

Closed jordanf97 closed 8 months ago

jordanf97 commented 9 months ago

Forgive me this is my first time using Svelte 😅

TL;DR

The homepage, movie page & series page have been minimally refactored to support faster load times.

By refactoring these API response promise chains we can load pieces of the UI progressively rather than waiting for all parts to finish.

Context

Currently the Reiverr app can be sluggish when navigating to new content, especially TV series with lots of seasons.

This PR seeks to improve the perceived performance of the overall app by progressively rendering various page sections.

Render Comparison

Homepage
Production Progressively Rendered
(*)
* the showcase trailer being sped up must have been a strange buffering thing when i started screen recording
TV Series
Production Progressively Rendered
(*)
* the genres rendering in after a delay on the showcase has been fixed
Movies
Production Progressively Rendered

Change Summary

Implementation

A rough explanation of the changes is as follows.

Homepage

When loading the homepage we show a list of 10 popular movies. These movies are returned from a single API request. For each movie we make an additional request to collect additional data(trailer, runtime minutes, etc).

Now we present the showcase to the user immediately after the first request is resolved, and display runtime / trailer once the subsequent request is resolved. This has a relatively significant impact on render times.

Movies

In production when viewing a movie the whole page is blocked from rendering until all associated requests have been resolved. By isolating the movie details request from requests for recommendations, cast, similar etc we can begin rendering much earlier.

Series

Similarly to movies, the series page blocks rendering until all associated requests have finished. Notable here as some series have upwards of 30 seasons, which is 30 requests to await. Series data, Episode data and recommendation data are now rendered concurrently.

Future Improvements

Latency could be reduced further by using an internal API to implement a short-lived cache on TMDB requests, given the output of these discovery endpoints seems to be stable within at least a 24 hour period.

This would also significantly reduce overall API utilisation.

The API rate limits sit somewhere in the 50 requests per second range.

paraphrased from the docs

As a concrete example, loading the simpsons series page currently sends at least 35 individual requests on page load. IIUC this token is shared application-wide meaning 10 people loading the simpsons page, on their locally installed instance would mean 350 concurrent requests tagged against the API token.

jordanf97 commented 9 months ago

Also big kudos to you @aleksilassila this was only possible with such little effort because you built the skeleton loading placeholders to support it.

aleksilassila commented 9 months ago

Thank you, I'll go through this asap!

jordanf97 commented 8 months ago

Hey @aleksilassila I appreciate you're busy and the web app is not currently your priority. Is there anything I can do to speed this implementation along?

aleksilassila commented 8 months ago

Sorry, currently no. But I will try to get this merged today 👍

aleksilassila commented 8 months ago

Looks good, great work and thank you! One caveat I noticed was that for example in the series page the episode cards are re-rendered after the initial page load, before the episodes are fetched. That causes the load animation to start over, causing a bit of twitching if that makes sense. Not a big deal and probably not an easy fix with the current design, I gotta think about it next time I touch the series page loading placeholder page / system.

jordanf97 commented 8 months ago

Ahh I see what you mean, it's a bit hard to reason about the control flows and async data here.

I've prototyped a messy solution which seems to do the trick, need to head out now but I'll put up a PR once I clean it up and look into why it's now auto scrolling to the end of the carousel 😅