Previous chapter
Productionizing ShinyShiny in Production
Next chapter

Shiny in Production

Question: Is it possible to put Shiny into production?

2015 answer: Yes, it’s quite possible

2019 answer: Yes, it’s quite easy

From Shiny in Production Talk, RStudio Conf 2019, Joe Chen

Shiny is a great framework to quickly develop powerful, data-driven applications in the browser. It enables R users, even without any knowledge of web development, to get started quickly. However, putting Shiny applications into production might need a different focus from an application perspective. Most Shiny developers tend to focus more on integrating the latest and greatest features into the app instead of thinking of stability, packages used, performance and memory usage.

Suggested Workflow

A good balance is therefore needed to have great features as well as performance and stability ensured. We thus suggest an agile, iterative workflow where new features are continously put into production.

Prototyping Shiny

Often it is required to rapidly come up with a prototype before handling the heavy-lifting in the backend. Of course, there exist packages for this task:

Reading Materials

Please find below the suggested (optional) reading materials for this section:

Challenges for Shiny in Production

Deploying Shiny apps into production in an organisation can be a challenge for multiple reasons: Cultural, Organizational and Technical.

Cultural

Cultural reasons exist because most Shiny apps are deveoped by R users who aren’t professional software engineers. Most importantly, they

  1. Don’t realize they’re creating production apps
  2. Don’t know best practices for writing and deploying production apps
  3. Don’t anticipate/budget for the effort required for production readiness
  4. Don’t come from a culture that worries about runtime efficiency.

There are some strategies to overcome these misconceptions:

  1. Create a workflow to put applications into production early. Make sure that Shiny developers step out of the “development bubble” and test the deployed app from the user perspective very early on.
  2. Create a stable development environment for your app since many Shiny developers do not
  • have version control in place for their code (e.g. Git, SVN)
  • put code into packages/modules
  • have unit tests in place
  • deploy applications on a continous basis (e.g. using Jenkins)
  1. Create a workflow to put applications into production early.
  2. Test and monitor your app with multiple users, use the package shinyloadtest

Organizational

On the organizational side there can be some hurdles from IT departments. The IT function naturally skews towards conservatism:

  • Skeptical of data scientists creating production artifacts. Does it work? Who cares about maintenance?
  • Skeptical of technologies they haven’t heard of
  • Engineering department may not be on your side

R is a DSL for statistics, not a Real Programming Language.

Some strategies to overcome the skepticism:

  • Make sure to create an environment where data scientists can create Shiny apps end-to-end: from conception to deployment.
  • Inform IT and engineering departments what Shiny and R is and that it is possible to develop applications rapidly. Show some prototypes of your app(s).

Technical challenges

Last but not least there are some technical challenges to be mastered:

  • Shiny dramatically lowers the effort involved in creating a web app.
  • However, shiny doesn’t lower (or even increases) the effort involved in: automated testing, load testing, profiling and deployment (until now).
  • R can be slow, and is single threaded. (But it’s probably a lot less slow than you think)

Tools for Shiny in Production

There exist some tools to make sure that Shiny apps will run smoothly in production.

shinytest

From https://rstudio.github.io/shinytest/articles/shinytest.html

The shinytest package provides tools for creating and running automated tests on Shiny applications.

Shinytest uses snapshot-based testing strategy. The first time it runs a set of tests for an application, it performs some scripted interactions with the app and takes one or more snapshots of the application’s state. These snapshots are saved to disk so that future runs of the tests can compare their results to them.

To create tests, the easiest method is to use the recordTest() function. This launches the application in a web browser and records your interactions with the application. These interactions are saved in a .R file, and are run using the strategy described above.

Alternatively, you can also select Record Test within RStudio:

Recording tests

This is the general procedure for recording tests:

  • Run recordTest() to launch the app in a test recorder.
  • Create the tests by interacting with the application and telling the recorder to snapshot the state at various points.
  • Quit the test recorder. When you do this, three things will happen:
  • The test script will be saved in a .R file in a subdirectory of the application named tests/.
  • If you are running in the RStudio IDE, it will automatically open this file in the editor.
  • The test script will be run, and the snapshots will be saved in a subdirectory of the tests/ directory.
recordTest("https://shiny.rstudio.com/gallery/widget-gallery.html")

shinyloadtest

Use shinyloadtest to generate large amounts of realistic traffic to your app, then analyze latency.

  1. Run or deploy your app
  2. Record an archetypal user session using shinyloadtest
  3. Playback the recording with your desired level of concurrency using shinycannon
  4. Analyze the results using shinyloadtest’s reporting feature or perform your own analysis using R.

Performance Workflow

  1. Use shinyloadtest to see if it’s fast enough
  2. If not, use profvis to see what’s making it slow
  3. Optimize
  4. Move work out of Shiny (often)
  5. Make code faster (often)
  6. Use caching (sometimes)
  7. Use async (occasionally)
  8. Repeat!

Record session archetype

In one R session:

shiny::runApp(port = 1234)

In a second R session:

shinyloadtest::record_session(http://127.0.0.1:1234)

Playback sessions

$ shinycannon recording.log http://127.0.0.1:1234 --workers 5 --loaded-duration-minutes 2 --output-dir run1
2019-08-29 15:06:14.191 INFO [progress] - Running: 0, Failed: 0, Done: 0
2019-08-29 15:06:14.193 INFO [thread01] - Warming up
2019-08-29 15:06:14.195 INFO [thread00] - Waiting for warmup to complete
2019-08-29 15:06:19.193 INFO [progress] - Running: 1, Failed: 0, Done: 0
2019-08-29 15:06:24.194 INFO [progress] - Running: 1, Failed: 0, Done: 0
2019-08-29 15:06:29.083 INFO [thread02] - Warming up
2019-08-29 15:06:29.195 INFO [progress] - Running: 1, Failed: 0, Done: 0
2019-08-29 15:06:34.195 INFO [progress] - Running: 2, Failed: 0, Done: 0
2019-08-29 15:06:39.196 INFO [progress] - Running: 2, Failed: 0, Done: 0
2019-08-29 15:06:43.973 INFO [thread03] - Warming up
2019-08-29 15:06:44.196 INFO [progress] - Running: 2, Failed: 0, Done: 0
2019-08-29 15:06:49.196 INFO [progress] - Running: 3, Failed: 0, Done: 0
2019-08-29 15:06:54.201 INFO [progress] - Running: 3, Failed: 0, Done: 0
2019-08-29 15:06:58.862 INFO [thread04] - Warming up
2019-08-29 15:06:59.201 INFO [progress] - Running: 3, Failed: 0, Done: 0
2019-08-29 15:07:04.201 INFO [progress] - Running: 4, Failed: 0, Done: 0
2019-08-29 15:07:09.202 INFO [progress] - Running: 4, Failed: 0, Done: 0
2019-08-29 15:07:13.751 INFO [thread05] - Warming up
2019-08-29 15:07:13.752 INFO [thread00] - Maintaining for 2 minutes (120000 ms)
2019-08-29 15:07:14.202 INFO [progress] - Running: 4, Failed: 0, Done: 0
2019-08-29 15:07:19.202 INFO [progress] - Running: 5, Failed: 0, Done: 0
2019-08-29 15:07:24.202 INFO [progress] - Running: 5, Failed: 0, Done: 0
...

Shiny in a Package

As Shiny applications grow in size and complexity it pays off to spend more time managing the code. As for any R-project of higher complexity it is highly recommended to create an R-package out of it since it ensures proper documentation and code (through R CMD check or devtools::check()) and can include tests. It also plays nicely with the previously introduced package shinytest.

For Shiny apps we suggest the following package structure:

  • data/: Static- or test data of the app
  • inst/: This is where the Shiny apps go into!!
  • R/: This is where modules and

To make things easier, you can also use the package golem by ThinkR which is an opinionated framework for building production-ready Shiny Applications. This framework starts by creating a package skeleton waiting to be filled, see also https://github.com/ThinkR-open/golem and read the accompanying blog post at https://rtask.thinkr.fr/blog/building-a-shiny-app-as-a-package.

remotes::install_github("Thinkr-open/golem")

Exercise: Shiny package

  • Use the package golem to create a Shiny package prototype.
  • Follow the steps as described in https://github.com/ThinkR-open/golem to create a new package.
  • Create a new package using File | New Project | Package for Shiny App using golem (or run the function golem::create_shiny_template() from the console).
  • Edit the file dev/01.start.R in your newly created package folder. Add recommended packages using golem::use_recommended_dep(). Execute other lines as needed and see how your package files change. Important: Make sure that your working directory is set to the package folder. This should be the case if you have used the RStudio form Package for Shiny App using golem.
  • Add a browser button for debugging golem::browser_button()
  • Add recommended tests: golem::use_recommended_tests()
  • Hit the Button Install & Reload App.
  • Run mypkg::run_app() where mypkg is the package name you selected previously.

Scaling Shiny

Slow Shiny applications are often the result of avoidable performance bottlenecks and lead to the common misconception that “Shiny doesn’t scale.” In actuality, properly-architected Shiny applications can be scaled horizontally, a fact which Sean Lopp was able to demonstrate at rstudio::conf 2018. shinycannon was used to simulate 10,000 concurrent users interacting with an application deployed to AWS. You can see a recording of Sean’s talk and the load test demonstration here: Scaling Shiny