IceCreamToucan
IceCreamToucan

Reputation: 28675

Is there any way to pre-cache output in Shiny?

I have the Shiny app below. The first time I select any given number it takes 3 seconds to load the result. Due to bindCache, I get the result instantly if I select the same number later.

However, I don't want to select all 10 numbers manually just to make my app responsive before I present it. Is there any way to cache a set of inputs in advance? In this example I'd want to cache the results for input$num values 1 through 10. In the real app there's approximately 5 inputs each with 5 possible values for 25 possible results I'd want to cache.

library(shiny)

ui <- fluidPage(
  sliderInput('num', 'Pick a number:', min = 1, max = 10, value = 1),
  textOutput('out')
)

server <- function(input, output, session) {
 output$out <- reactive({
   Sys.sleep(3)
   paste("Your number is:", input$num)
 }) %>% bindCache(input$num)
}

shinyApp(ui, server)

Note:

One response might be that I should manually pre-compute these results. In the real app most of the time comes from gt::render_gt and gt::gt_output on gt tables that I've created in advance. I believe these functions can only be used in a reactive context (meaning only in a Shiny app?)

Edit:

As a side-note, my original problem was solved by using gt::as_raw_html to render the tables in a step before the shiny app. Still leaving the question though, since it's sometimes a problem in other situations.

Upvotes: 5

Views: 1163

Answers (2)

ManderX
ManderX

Reputation: 41

Another idea, when you need to pre-cache output, especially plots, is to automatically simulate the activity of FIRST user (on whom the cache is created) every time the initial data changes. So the real users will interact with already cached output. I got the solution from the video of Joe Cheng, thanks to user Pedro Faria for the link.

  1. First, using the shinyloadtest package, you MANUALLY record the web activity on the application through browser (you open all plots, change inputs, do all you need to be cached). Yes, you do it manually, but you do it only once.
devtools::install_github('rstudio/shinyloadtest') 
library(shinyloadtest) 
shinyloadtest::record_session("http://shinyapp.address:3838",host="127.0.0.1",port
= 8600, open_browser = TRUE) 

record_session() will open a browser displaying the app. You do all user activity, then close the browser. After closing, a file (recording.log by default), that contains a recording of the session, will be created in the R user directory. ( use getwd() to know)

  1. Then, using the shinycannon command-line tool, you can automatically simulate the recorded activity. In my case, i run that simulation directly on the server once a day, on reboot (using cron) or when the data is updated.

Command to use in system’s terminal:

shinycannon /path/to/recording.log http://localhost:3838 --workers 1 --loaded-duration-minutes 1

Shinycannon is able to run on whatever platforms Java virtual machine is. For example in Armbian, after building packages, terminal command will look like:

java -jar /path/to/shinycannon-1.1.3.9000-8e10a8e.jar /path/to/recording.log http://localhost:3838 --workers 1 --loaded-duration-minutes 1

Upvotes: 4

thothal
thothal

Reputation: 20329

How about using a persistent cache, running the app once, where you manually change all inputs as needed (I also included an automated version, which I am not totally happy with, b/c race conditions could occur) and then in subsequent runs you have all the values properly cached?

library(shiny)
library(magrittr)
## change path to a non temp diretcory to keep that even after reboot
shinyOptions(cache = cachem::cache_disk(file.path(dirname(tempdir()), 
                                        "myapp-cache")))

xl <- 1:3
yl <- 1:3

ui <- fluidPage(
   sliderInput("x", "x", min(xl), max(xl), min(xl), 1),
   sliderInput("y", "y", min(yl), max(yl), min(yl), 1),
   verbatimTextOutput("z"),
   actionButton("fill", "Fill Cache")
)

server <- function(input, output, session) {
   idx <- idy <- 1
   r <- reactive({
      message("Doing  expensive computation...")
      Sys.sleep(2) ## simulate expensive op
      input$x + input$y
   }) %>% bindCache(input$x, input$y)

   observe({
      req(input$fill)
      if (idx != length(xl) + 1 || idy != length(yl)) {
         ## need the invalidateLater approach 
         ## to allow shiny reacting on the change
         ## not sure whether we cannot trip over race conditions
         ## recommendation: do it once by hand (it's persistent anyways ;)
         invalidateLater(500, session) 
         if (idx == length(xl) + 1) {
            message("Updating y:", idy)
            idx <<- 1
            idy <<-  idy + 1
            updateSliderInput(session, "y", value = yl[[idy]])
         } else {
            message("Updating x:", idx)
            updateSliderInput(session, "x", value = xl[[idx]])
            idx <<- idx + 1
         }
      }
   })

   output$z <- renderText(r())
}

## Start app and set all values
shinyApp(ui, server)

## Close app and restart
## Cache is now filled
shinyApp(ui, server)

Upvotes: 6

Related Questions