Shiny 1.2.0: Plot caching
This article is originally published at https://www.rstudio.com/blog/
We’re pleased to announce the CRAN release of Shiny v1.2.0! This release features Plot Caching, an important new tool for improving performance and scalability in Shiny apps.
If you’re not familiar with the term “caching”, it just means that when we perform a time-consuming operation, we save (cache) the results so that the next time that operation is requested, we can skip the actual operation and instantly fetch the previously cached results. Shiny’s reactive expressions do some amount of caching for you already, and you can use more explicit techniques to cache the various operations you might do to your data (using the memoise package, or manually saving intermediate data frames to disk as CSV or RDS, for two examples).
Plots, being very common and (potentially) expensive-to-compute outputs, are great candidates for caching, and in theory you can use renderImage
to accomplish this. But because Shiny’s renderPlot
function contains a lot of complex infrastructure code, it’s actually quite a difficult task. Despite some valiant attempts, all of the examples we’ve seen in the wild have had serious limitations we wanted to overcome.
Shiny v1.2.0 introduces a new function, renderCachedPlot
, that makes it much easier to add plot caching to your app.
Using renderCachedPlot
Let’s take a simple, but expensive, plot output:
output$plot <- renderPlot({
ggplot(diamonds, aes(carat, price, color = !!input$color_by)) +
geom_point()
})
The diamonds
dataset has 53,940 rows. On my laptop, this takes about 1580 milliseconds (1.58 seconds). Perhaps that’s fast enough for doing exploratory data analysis, but it’s slower than we’d like for a high traffic Shiny app.
We can tell Shiny to cache this plot in two steps.
- Change
renderPlot
torenderCachedPlot
. - Provide a suitable
cacheKeyExpr
. This is an expression that Shiny will use to determine which invocations ofrenderCachedPlot
should be considered equivalent to each other. (In this case, two plots with differentinput$color_by
values can’t be considered the “same” plot, so thecacheKeyExpr
needs to haveinput$color_by
.)
output$plot <- renderCachedPlot({
ggplot(diamonds, aes(carat, price, color = !!input$color_by)) +
geom_point()
}, cacheKeyExpr = { input$color_by })
With these code changes, the first time a plot with a particular input$color_by
is requested, it will take the normal amount of time. But the next time it is requested, it will be almost instant, as the previously rendered plot will be reused.
Benchmarking the results
To quantify the performance difference between the cached and uncached versions, I turned each into a minimal Shiny app (source). This app simply provides the color_by
input using the new varSelectInput
control, and then renders the output using either of the two code examples above. Then I used our (still-in-development) Shiny load testing tools to record a test script, and “replay” it against both versions of the app, each running in a single R process.
I tested the renderPlot
version of the app with 5 concurrent users, and the renderCachedPlot
version with 25, 50, and 100 concurrent users. The difference in performance is as dramatic as we’d expect:

With only five concurrent users, the latency is already pretty bad with the renderPlot
version. (Note that this isn’t intended to represent typical performance with Shiny apps in general! We chose a particularly torturous ggplot on purpose.)
On the other hand, the renderCachedPlot
version doesn’t break a sweat with 50 concurrent users; and even at 100 concurrent users, the latency is still acceptable.
When to use plot caching
A Shiny app is a good candidate for plot caching if:
- The app has plot outputs that are time-consuming to generate,
- These plots are a significant fraction of the total amount of time the app spends thinking, and
- Most users are likely to request the same few plots.
Since our test has a pretty expensive plot as its only output, and our load testing tools simulate n users all doing the same exact thing, this these numbers reflect a best-case scenario for plot caching.
Shiny can store your cached plots in memory, on disk, or with another backend like Redis. You also have a number of options for limiting the size of the cache. Be sure to read this article to get the most out of this feature!
In future releases of Shiny, we hope to build on this foundation we’ve laid to dramatically speed up other areas of Shiny apps, like reactive expressions and non-plot outputs. In the meantime, we hope you find plot caching to be a useful addition to your performance toolkit!
Other changes in Shiny v1.2.0
Upgrade FontAwesome from 4.7.0 to 5.3.1 and made
icon
tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in Upgrade from Version 4. Font Awesome icons can also now be used in static R Markdown documents.Address #174: Added
datesdisabled
anddaysofweekdisabled
as new parameters todateInput()
. This resolves #174 and exposes the underlying arguments of Bootstrap Datepicker.datesdisabled
expects a character vector with values inyyyy/mm/dd
format anddaysofweekdisabled
expects an integer vector with day interger ids (Sunday=0, Saturday=6). The default value for both isNULL
, which leaves all days selectable. Thanks, @nathancday! #2147
See the full changelog for Shiny v1.2.0 for other minor improvements and bug fixes we’ve made in this release.
Thanks for visiting r-craft.org
This article is originally published at https://www.rstudio.com/blog/
Please visit source website for post related comments.