Alternatives to Scaling Shiny with RStudio Connect or Custom Architecture

Shiny is a great tool for fast prototyping. When a data science team creates a Shiny app, sometimes it becomes very popular. From that point this app becomes a tool used on production by many people, that should be reliable and work fast for many concurrent users. There are many ways to optimize a Shiny app like using promises for non-blocking access, profvis for finding bottlenecks, and shiny modules for well-structured code, etc. Regardless of this, the main aspect you should focus on is how you serve the application. Continue reading to learn a few alternatives to scaling Shiny apps.

Why is scaling Shiny tricky?

R is single-threaded

This means all users connected to one R process will block each other. Multithreading allows for application responsiveness, and by design, R doesn’t. You have to use workarounds to provide it.

For example, you can do this by serving multiple instances of the app. Also, a promises package can be used to improve responsiveness, but it is still fundamentally different than promises in JavaScript. JavaScript promises are different thanks to the event loop mechanism, which features fully asynchronous I/O and worker threads.

R is slow. It was designed for convenient data analysis, not for webapps.

R language was created to make data analysis faster. It proved its power, and that’s why it is the tool of choice for many data scientists. However, faster analysis doesn’t mean better performance.

Data analysis in R is fast because of convenient syntax and the amazing amount of useful statistical packages, but the language execution time itself is slow. A detailed explanation of this is covered by Hadley Wickham in his article about R performance. There are also many benchmarks on what the speed of R is.

Personally, I am excited by alternative implementations of the R language, which aim to improve performance. One of them is Oracle FastR based on GraalVM, and another is JVM-based Renjin. Currently, they are still evolving, but I believe their time will come soon.

Four (or five) alternatives to serve Shiny

Many people ask us what the effective options are for serving a Shiny app, especially for a large number of users. The industry-standard tools for enterprise Shiny deployment are RStudio products. These are what we recommend to our clients, as we believe them to be the best option. You can go either with RStudio Shiny Server Pro and it costs $11,950/year for 20 concurrent users or R Studio Connect for $14,995/year. Both of them are mature, powerful solutions, which we often recommend to our clients where R Studio Connect is our first choice due to easy setup and push-button deployment.

Before you make a decision on how to scale your app to multiple users, it is important to understand what your needs are:

  • What is the app initialization time?
  • Does it load a lot of data into memory on startup?
  • What is the app complexity?
  • How heavy are the calculations?
  • What is the expected behavior of users?

Answering these questions will help you choose the best fit for your use case.

Now, let’s see what options are available to you:

(1) Shiny Server Open Source

Shiny Server Open Source is limited to one R process per app, which potentially can serve multiple user sessions (connections to the app). This is totally fine for apps that don’t have many users, but it doesn’t work well for apps that will have large amounts of users. When you have many users, they all will be served by one process and will inevitably block each other.

This architecture is visualized by the following diagram:

Shiny Server Open Source scaling

(2) Shiny Server Pro / RStudio Connect

In contrast, on Shiny Server Pro or RStudio Connect, you can have multiple R processes per app. This means that many concurrent users can be distributed between separate processes and are served more efficiently. As there is no limitation on the number of processes, you can make use of all your machine resources.

With both paid solutions from RStudio, you can configure a strategy on how resources should be handled with the `utilization_scheduler` parameter.

For example, you can set:

  • The maximum R process capacity, i.e. the number of concurrent users per single R process;
  • The maximum number of R processes per single app;
  • When the server should spawn a new R process, e.g. when existing processes reach 90% of their capacity.

Below you can see an example situation:

In this scenario, five users want to access the app. Shiny Server Pro initializes three worker processes, and users are distributed between them.

R Studio Connect is a go-to solution as it offers multiple features with a click of a button, managing apps. authorization, scheduling, distribution, and security options that are unavailable anywhere else.


(3) ShinyProxy

ShinyProxy from OpenAnaltyics is an open-source alternative for serving Shiny apps. Its architecture is based on docker containers, which isolate the app’s environment. The key difference is that ShinyProxy starts a new app instance for each new user. The architecture is straightforward but requires additional support to maintain which may prove costly down the line. This is how it looks on a diagram:

ShinyProxy scaling

(4) Custom architectures

Sometimes deploying a Shiny app requires a custom solution. Let’s say we have a Shiny app that takes 60 seconds to initialize, and that time amount is deemed as an unacceptable wait time for a user. In this situation, starting an app instance for each user is not the way to go.

At Appsilon, we create custom architectures for Shiny apps. In most cases, we recommend RStudio Connect because it is often the right tool for a given task. However, there are exceptions that require bespoke solutions.

We developed such a solution for one of our clients and have used it to deploy several production apps. To do this we created a scalable architecture for a Shiny app that takes a long time to initialize and performs a lot of heavy computations.

Here is how it looks:



This approach uses docker containers that serve the application using Shiny Server Open Source. In front of application instances, there is a load balancer that distributes the traffic.

The difference between our product and ShinyProxy is that there are N pre-initialized containers that wait for user connections. The number of containers is configured by the app admin and can be auto-adjusted. The advantage of this approach is that the app is served instantly. Users do not have to wait for the app initialization.

It is also worth noting that this custom architecture supports SSL connection, authentication, and other enterprise requirements.

Cheat Sheet: alternatives to scaling Shiny apps

I hope this article on the possible solutions for scaling Shiny apps to many concurrent users was helpful to you. If you’d like to chat about a specific use case, you can contact us through our Shiny page.

Below, we have compiled the information shared above into a comparison cheat sheet diagram. Again, I hope it helps!