The shimmer
package contains a discrete event simulation that explores
how shiny
processes behave at scale, typically orchestrated by
RStudio Connect or Shiny Server Pro.
The underlying infrastructure of the simulation is provided by the
simmer
package (for discrete event simulations). In other words,
shimmer
simulates how Shiny apps scale by using the simmer
simulation framework.
The package is not yet on CRAN…
… but you can install the development version from GitHub using:
# install.packages("devtools")
devtools::install_github("andrie/shimmer")
The shimmer
package uses discrete event simulation to help answer the
questions:
- How big should my Shiny server be to handle
n
number of users? - For a given size of server, how many users can Shiny handle?
- How should I tune the runtime parameters in RStudio Connect for my app?
The robust answer to this question is to use shinyloadtest
, but for
planning purposes you may want to rapidly develop some hypotheses and
intuition about the problem, prior to building and testing an app.
The simmer
package makes it easy to build discrete event simulations
in R. The shimmer
package uses simmer
under the hood for defining
and running the simulation.
The shimmer()
function reads a configuration file using the
config::get()
function. The package contains a default configuration
file at:
system.file("config.yml", package = "shimmer")
#> [1] "C:/Users/apdev/Documents/R/win-library/3.4/shimmer/config.yml"
The contents of this file:
default:
runtime:
comment: The app runtime settings that are available in RStudio Connect
max_processes: 3
min_processes: 0
max_connections_per_process: 20
load_factor: 0.5
idle_timeout_per_process: 5.0
initial_timeout: 300
connection_timeout: 3600
read_timeout: 3600
app:
comment: Describes the app startup time and response time per click
startup_time: 5.0
response_time: 2.0
user:
arrival:
comment: Arrival time between users (seconds)
mean: 10.0
shape: 5.0
number_of_requests_per_user: 20.0
request:
comment: Mean arrival time between requests for a given user (seconds)
mean: 10.0
shape: 5.0
idle:
comment: Time in seconds that connection remains live after last request
mean: 1800
sd: 600
system:
cpu: 4.0
library(magrittr)
library(shimmer)
By default, the simulation runs for an hour (3,600 seconds) of simulation time:
env <- shimmer()
#> You must specify either config or a valid config_file.
#> Using the built-in config file.
env
#> simmer environment: Shiny | now: 3600 | next: 3600
#> { Monitor: in memory }
#> { Resource: connection_request | monitored: TRUE | server status: 60(60) | queue status: 0(0) }
#> { Resource: total_connections | monitored: TRUE | server status: 123(Inf) | queue status: 0(0) }
#> { Resource: rejections | monitored: TRUE | server status: 243(Inf) | queue status: 0(0) }
#> { Resource: connection | monitored: TRUE | server status: 60(60) | queue status: 0(Inf) }
#> { Resource: cpu | monitored: TRUE | server status: 1(4) | queue status: 0(Inf) }
#> { Resource: process_1 | monitored: TRUE | server status: 20(20) | queue status: 0(0) }
#> { Resource: request_queue_1 | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
#> { Resource: process_2 | monitored: TRUE | server status: 20(20) | queue status: 0(0) }
#> { Resource: request_queue_2 | monitored: TRUE | server status: 1(1) | queue status: 1(Inf) }
#> { Resource: process_3 | monitored: TRUE | server status: 20(20) | queue status: 0(0) }
#> { Resource: request_queue_3 | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
#> { Source: controller | monitored: 1 | n_generated: 1 }
#> { Source: user_accounting | monitored: 1 | n_generated: 367 }
You can generate several plots from the simulation:
- CPU usage
- Response histogram
- Rejected connections (because the system was too busy)
env %>%
plot_shimmer_cpu_usage()
env %>%
plot_shimmer_response_histogram()
env %>%
plot_shimmer_rejection_usage()
In addition, you can also get more detail of the underlying system behaviour:
- Connections
- Connections per process
env %>%
plot_shimmer_connection_usage()
env %>%
plot_shimmer_process_usage()