-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path07-deployment.qmd
329 lines (223 loc) · 14 KB
/
07-deployment.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
---
title: "Strengthen & Deploy"
---
```{r, include=FALSE}
library(profvis)
library(reactlog)
library(shinylive)
library(httpuv)
library(rsconnect)
```
Learning outcomes - Debug Shiny apps and understand them from the inside - Supervise and test Shiny apps for robustness (appendix) - Convert simple Shiny apps to static website using shinylive - Deploy any Shiny app to the web using shinyapps.io
# Deploying Shiny apps
- When running a Shiny app using [`runApp()`](https://shiny.posit.co/r/reference/shiny/1.7.0/runapp), you host it on a local server, i.e. it remains in your local network
- Deploying a Shiny app means making your app available to other users!
- Deployment is the last step of app development
## Strengthen
- Before it's time to deploy, it's a good idea to make sure your app passes the test of publicity
- Depending on the use case and target group, a deployed app should be:
- **fast:** remote communication can make your app slower than on a local host
- **scalable:** a high workload can crash your app
- **stable:** errors also crash your app
## Tools to strengthen
- There are a lot of Shiny extensions that help in strengthening a Shiny app, some of which we will address:
- [`profvis`](https://rstudio.github.io/profvis/): Profiles a Shiny app and creates performance visualizations
- [`reactlog`](https://rstudio.github.io/reactlog/): Logs reactivity of your Shiny app and creates a dynamic reactive graph
- [`shinyloadtest`](https://rstudio.github.io/shinyloadtest/): Simulates a workload of users and determines how well your app is suitable for such a workload
- [`shinytest`](https://rstudio.github.io/shinytest/): Creates snapshots and compares the visual appearance of them in subsequent runs
- [`shiny::testServer()`](https://shiny.posit.co/r/reference/shiny/1.6.0/testserver): Performs programmatic tests using the Shiny server logic of an app
## Further resources
- Chapters [11](https://engineering-shiny.org/build-yourself-safety-net.html#testing-the-interactive-logic) and [13](https://engineering-shiny.org/deploy.html) of Colin Fay's Engineering Production-Grade Shiny Apps
- Chapters [21](https://mastering-shiny.org/scaling-testing.html) and [23](https://mastering-shiny.org/performance.html) of Hadley Wickham's Mastering Shiny
# Debugging Shiny apps
- Debugging Shiny apps is a unique challenge as Shiny code is not linear like regular R code
- Setting breakpoints is unreliable and only supported in RStudio
- Here, we introduce three ways to debug a Shiny app:
- Interactive debugging
- Print debugging aka logging
- Reactivity logging
## Interactive debugging
- Interactive debugging comes from base R and works just like that
- Put a call to `browser()` somewhere in your server function
- Code execution is interrupted on the spot and you can explore the server function in a "frozen" state
![Accessing `input` in Shiny browser call](resources/shiny_browser.png)
## Logging
- In base R, print debugging is frowned upon
- In Shiny, print debugging can be a nice way to understand errors along a reactivity path (or to understand reactivity in general)
## Where am I?
- That is a question you might ask yourself occasionally when encountering errors in Shiny
- The [`whereami`](https://cran.r-project.org/web/packages/whereami/) package can tell you exactly where you are
- Many R packages enable general logging ([`logging`](https://cran.rstudio.com/web/packages/logging/), [`logger`](https://cran.r-project.org/web/packages/logger/), [`log4r`](https://cran.r-project.org/web/packages/log4r)), but `whereami` is especially suitable for Shiny
![Logs from `logger`](resources/shiny_logger.png)
![Logs from `whereami`](resources/shiny_whereami.png)
## Reactivity logging
- Reactivity logging means capturing and visualizing reactive dependencies in Shiny apps
- Useful for detecting overreactiveness and reactive **instabilities**
- Reactivity logging can be done using the [`reactlog`](https://rstudio.github.io/reactlog/) package
### Using the `reactlog` package
- Run [`reactlog::reactlog_enable()`](https://rstudio.github.io/reactlog/reference/setReactLog.html) before running the Shiny app or set `options(shiny.reactlog = TRUE)`
- Do stuff in your Shiny app (particularly something that triggers dependencies!)
- Run [`shiny::reactlogShow()`](https://shiny.posit.co/r/reference/shiny/1.3.1/reactlog.html) or [`reactlog::reactlog_show()`](https://rstudio.github.io/reactlog/reference/reactlog_show.html) after closing the app
- Alternatively, press `Strg + F3` while the app is running
```{r echo=FALSE}
htmltools::tags$iframe(src = "resources/reactlog.html", width = "100%", height = "500px")
```
# Deployment
- The deployment of Shiny apps is restricted to servers that support Shiny apps
- You can either set up a custom Shiny server or use a server provider
## Deployment options
| Name | Use | Requirements |
|---------------------------------------|-----------------|-----------------|
| [shinyapps.io](https://www.shinyapps.io/) | Casual to professional applications | `rsconnect` R package |
| [Posit Connect](https://posit.co/products/enterprise/connect/) | Professional to corporate applications | `rsconnect` R package |
| [Shiny Server](https://posit.co/products/open-source/shinyserver/) / self-hosting | Setting up self-hosted Shiny servers | Shiny Server on a Linux server |
| [ShinyProxy](https://www.shinyproxy.io/) / [Heroku](https://www.heroku.com/) / [Hugging Face](https://huggingface.co/docs/hub/spaces-sdks-docker-shiny) | Deployment of containerized applications | Docker |
| [Shinylive](https://posit-dev.github.io/r-shinylive/) | Simple applications in an embedded setting | An existing static website |
| GitHub / CRAN / BioConductor | Applications for researchers or developers | Proficiency in R package development |
: Shiny app hosting
# Shinylive
- [Shinylive](https://posit-dev.github.io/r-shinylive/) is one of Posit's new things
- An application deployed with Shinylive does not need a server: It is a static website!
- This is possible primarily due to developments in what is called [**WebAssembly**](https://en.wikipedia.org/wiki/WebAssembly) **(Wasm)**
- If you are interested in the magic behind Shinylive and Wasm, check out Joe Cheng's talk at posit::conf(2023)
<iframe width="560" height="315" src="https://www.youtube.com/embed/j1M6YyU2ZX8?si=LKNEUV8I2IEZaP8r" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
</iframe>
## Whats so cool about Shinylive?
- You can simply embed your Shiny app:
- In your personal website
- In your Quarto/RMarkdown document
- In your package documentation
- In another Shiny app (do try this at home)
## R packages
- Running R in the web is powered by the [WebR framework](https://docs.r-wasm.org/webr/latest/)
- Not all R (or Python) packages are available in WebR
- R packages need to be built for wasm
- If a package's dependency cannot be built for wasm, it is not usable
- Only binary packages can be built
- Some features simply do not work:
- `system()` or any other system commands
- Database connections
- Parallelization
- You can check which packages are built for wasm [here](https://repo.r-wasm.org/) (currently about 14,000 packages are available)
```{r, include=FALSE}
knitr::include_url("https://repo.r-wasm.org/")
```
## Other Limitations
- Can be quite slow on startup
- Code and data are fully visible in the source code (thus not useful for sensitive data)
- App runs on the client machine and can thus be very slow and demanding
- The framework is in early development and debugging can be a real hassle
![A common Shinylive error](resources/shinylive-error.png)
## Downloading assets
- Shinylive is actually a library of web assets that run R in the background
- The `shinylive` package needs these assets to turn a Shiny app into HTML code
```{r}
library(shinylive)
assets_ensure()
assets_info()
```
## Creating the app
- The `export` function creates a new directory, installs all WebR packages, and provides all asset files used to
```{r, eval=FALSE}
export("shinyapps/example", destdir = "shinyapps/example/shinylive")
```
- In theory, the resulting HTML document can now simply be opened
- In practice, however, the HTML document must be opened on a static server, e.g. using the `httpuv` package:
```{r, eval=FALSE}
httpuv::runStaticServer("shinyapps/example/shinylive", port = httpuv::randomPort())
```
## Embed Shinylive in Quarto
- Quarto supports Shinylive through the `shinylive` extension
```{r, eval=FALSE}
quarto::quarto_add_extension("quarto-ext/shinylive", no_prompt = TRUE)
```
- In the YAML header, specify the `filters` keyword
```
filters:
- shinylive
```
- Shinylive code chunks must be marked with `shinylive-r` or `shinylive-python`
- Four additional options are defined:
- `standalone` specifies that the code chunk contains an entire app
- `components` specifies whether to show a code editor next to the rendered app
- `layout` specifies whether to show the components vertically or horizontally aligned
- `viewerHeight` specifies the height of the app viewer in pixels
# shinyapps.io
- A common choice for more casual Shiny apps is shinyapps.io
- It requires not much technical knowledge to deploy
- It does not require a pre-existing infrastructure (e.g., a server)
- It offers a free plan
![Shinyapps.io plans](resources/shinyapps_plans.png)
## `rsconnect`
- Both for Posit Connect and shinyapps.io you need the [`rsconnect`](https://rstudio.github.io/rsconnect/) package
- `rsconnect` enables the communication between the Posit services and R
<!-- -->
- `rsconnect` is built around the [`deployApp()`](https://rstudio.github.io/rsconnect/reference/deployApp.html), [`deployAPI()`](https://rstudio.github.io/rsconnect/reference/deployAPI.html) and [`deployDoc()`](https://rstudio.github.io/rsconnect/reference/deployDoc.html) functions
## Creating an account
- Before being able to deploy to Shinyapps, we need an account
- Accounts can be created per Email or using Google, Github or Clever
![Shinyapps.io sign-up](resources/shinyapps_signup.png)
## Connecting R to shinyapps.io
- Just with any interface, linking works using Tokens and Secrets
- On your shinyapps dashboard, navigate to Account -\> Tokens on the sidebar
- Click on "Add Token"
- Click on "Show" next to your newly created token
- Paste the code into your console and execute
![Shinyapps token to connect with `rsconnect`](resources/shinyapps_token.png)
## Deploy!
- To verify that the verification process was successful we run:
```{r eval=FALSE}
rsconnect::accounts()
```
- Finally, to deploy an app, we simply run `deployApp()` to deploy an `app.R` in the current working directory to shinyapps.io:
```{r eval=FALSE}
rsconnect::deployApp()
```
# Appendix: Performance profiling
- Profiling means recording how much time and memory certain actions in your Shiny app need
- Useful for testing performance or **speed** of your app
- Performance profiling in Shiny can be done with the `profvis` package
## Interpreting flame graphs
- Flame graphs are a tool to quickly visualize performance of individual parts of an app
- They consist of two-dimensional "function boxes" which represent an individual function call
- The **width** of a function box indicates the time it took to process the function call
- The **stacked height** of function boxes indicates their ancestry. The lower boxes are the parents and the upper boxes are their children, i.e. the lower boxes call the upper boxes
![The concept of a flame graph](resources/flame_graph.svg)
## Using the `profvis` package
- Simply run your app within a `profvis::profvis()` call:
```{r eval=FALSE}
profvis({runApp()})
```
::: callout-note
It is necessary to use `runApp()`, `shinyApp()` does not suffice!
:::
- Then, perform some tasks in the Shiny app
- Close the app and a flame graph will be created
- Note the color scheme:
- Yellow boxes correspond to specific lines in the code
- Blue boxes correspond to output objects
- Grey boxes correspond to garbage collection
```{r echo=FALSE}
htmltools::tags$iframe(src = "resources/profvis.html", width = "100%", height = "800px")
```
# Exercises
::: callout-note
#### Exercise 1
Export your Shiny app as a static Shinylive webpage using the `shinylive` package.
To prevent problems ahead of time, make sure that you are running the newest version of the Shinylive package and web assets. The latest `shinylive` version can be downloaded from GitHub:
```{r}
# pak::pkg_install("posit-dev/r-shinylive")
packageVersion("shinylive")
```
The latest assets version can be retrieved using `shinylive::assets_version()`
```{r}
# assets_ensure()
assets_version()
```
:::
::: callout-note
#### Exercise 2
Deploy your Shiny app using [shinyapps.io]. If necessary, create an account, and then follow the instructions.
Study the documentation of `?rsconnect::deployApp()` to learn about possible
deployment configurations.
:::