From c262931379c588ff6c6b2afdfba5441895bddb26 Mon Sep 17 00:00:00 2001 From: Eric Nantz Date: Sun, 17 Sep 2023 00:37:17 -0400 Subject: [PATCH] move async slides to performance section --- .../codealong-1/execute-results/html.json | 14 +++ .../assets/img/lego_line_pay.jpg | Bin .../assets/img/shiny.png | Bin .../codealong-1.qmd | 0 materials/d1-03-performance/index.qmd | 84 +++++++++++++++- materials/d1-9002-async/index.qmd | 95 ------------------ units/d1-03-performance.qmd | 16 ++- units/d1-9002-async.qmd | 32 ------ 8 files changed, 108 insertions(+), 133 deletions(-) create mode 100644 _freeze/materials/d1-03-performance/codealong-1/execute-results/html.json rename materials/{d1-9002-async => d1-03-performance}/assets/img/lego_line_pay.jpg (100%) rename materials/{d1-9002-async => d1-03-performance}/assets/img/shiny.png (100%) rename materials/{d1-9002-async => d1-03-performance}/codealong-1.qmd (100%) delete mode 100644 materials/d1-9002-async/index.qmd delete mode 100644 units/d1-9002-async.qmd diff --git a/_freeze/materials/d1-03-performance/codealong-1/execute-results/html.json b/_freeze/materials/d1-03-performance/codealong-1/execute-results/html.json new file mode 100644 index 0000000..a99eb2e --- /dev/null +++ b/_freeze/materials/d1-03-performance/codealong-1/execute-results/html.json @@ -0,0 +1,14 @@ +{ + "hash": "c12dcd124a82cea6178e9a18ef6d6d65", + "result": { + "markdown": "---\ntitle: Asynchronous Processing of LEGO Model Prediction\nformat:\n html:\n code-line-numbers: false\n execute:\n echo: true\n eval: false\n---\n\n\n## Requirements\n\nThe current version of our Shiny application contains a module for generating predictions of the number of LEGO parts in a set using the number of unique colors and number of unique part categories. The API is executed and processed using the [`{httr2}`](https://httr2.r-lib.org/) package. Here is the function wrapping the API execution:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n#' @importFrom httr2 request req_body_json req_perform resp_body_json\nrun_prediction <- function(df, endpoint_url, back_transform = TRUE, round_result = TRUE) {\n # create request object\n req <- request(endpoint_url)\n\n # perform request\n resp <- req |>\n req_body_json(df) |>\n req_perform()\n\n # extract predictions from response\n pred_values <- resp_body_json(resp)$.pred |> unlist()\n\n # back-transform log10 value of predicted number of parts if requested\n if (back_transform) {\n pred_values <- 10 ^ pred_values\n }\n\n # round result up to nearest integer if requested\n if (round_result) pred_values <- ceiling(pred_values)\n\n # append predictions to supplied data frame\n dplyr::mutate(df, predicted_num_parts = pred_values)\n}\n```\n:::\n\n\nUnfortunately, the prediction API call takes a bit of time to execute due to some **extremely sophisticated processing** 😅. As a result, any interactions within the application will not be processed until the prediction call completes. Our goal is to convert the prediction processing from *synchronous* to *asynchronous* using `{crew}`\n\n## Plan\n\n1. Establish reactive values for tracking the status of the prediction calls\n1. Create a new controller to launch new R processes when new prediction tasks are launched\n1. Modify the existing `observeEvent` to push the prediction task to the controller, ensuring the key objects and required packages are passed on to the controller.\n1. Create a poll that's invalidated every 100 milliseconds to query the status of the submitted tasks in the controller and update the prediction result reactive value when complete.\n\n## Solution \n\nFirst we create the following `reactiveVal` objects to keep track of the prediction state:\n\n\n::: {.cell}\n\n```{.r .cell-code}\npred_status <- reactiveVal(\"No prediction submitted yet.\")\npred_poll <- reactiveVal(FALSE)\n```\n:::\n\n\nNext we set up a new controller:\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# establish async processing with crew\ncontroller <- crew_controller_local(workers = 4, seconds_idle = 10)\ncontroller$start()\n\n# make sure to terminate the controller on stop #NEW\nonStop(function() controller$terminate())\n```\n:::\n\n\nInside the `observeEvent` for the user clicking the prediction button, we update the logic to push the prediction task to the controller:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ncontroller$push(\n command = run_prediction(df),\n data = list(\n run_prediction = run_prediction,\n df = pred_data_rv$data\n ),\n packages = c(\"httr2\", \"dplyr\")\n)\n\npred_poll(TRUE)\n```\n:::\n\n\nLastly, we create a new `observe` block that periodically checks whether the running `{crew}` tasks have completed, ensuring that this is only executed when a prediction has been launched:\n\n\n::: {.cell}\n\n```{.r .cell-code}\nobserve({\n req(pred_poll())\n\n invalidateLater(millis = 100)\n result <- controller$pop()$result\n\n if (!is.null(result)) {\n pred_data_rv$data <- result[[1]]\n print(controller$summary()) \n }\n\n if (isFALSE(controller$nonempty())) {\n pred_status(\"Prediction Complete\")\n pred_poll(controller$nonempty())\n removeNotification(id = \"pred_message\")\n }\n})\n```\n:::\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/materials/d1-9002-async/assets/img/lego_line_pay.jpg b/materials/d1-03-performance/assets/img/lego_line_pay.jpg similarity index 100% rename from materials/d1-9002-async/assets/img/lego_line_pay.jpg rename to materials/d1-03-performance/assets/img/lego_line_pay.jpg diff --git a/materials/d1-9002-async/assets/img/shiny.png b/materials/d1-03-performance/assets/img/shiny.png similarity index 100% rename from materials/d1-9002-async/assets/img/shiny.png rename to materials/d1-03-performance/assets/img/shiny.png diff --git a/materials/d1-9002-async/codealong-1.qmd b/materials/d1-03-performance/codealong-1.qmd similarity index 100% rename from materials/d1-9002-async/codealong-1.qmd rename to materials/d1-03-performance/codealong-1.qmd diff --git a/materials/d1-03-performance/index.qmd b/materials/d1-03-performance/index.qmd index 5779dd5..586ab5b 100644 --- a/materials/d1-03-performance/index.qmd +++ b/materials/d1-03-performance/index.qmd @@ -6,11 +6,13 @@ format: revealjs: theme: [default, ../slides.scss] # moon= teal bg | dark scrollable: true - incremental: true + incremental: false slide-number: c/t # c/t | c | h/v | h.v slide-tone: false #true code-line-numbers: false history: false +revealjs-plugins: + - codewindow --- ## Performance Agenda @@ -113,3 +115,83 @@ You need to wrap the `run_app()` function in `print()`, before passing it to `pr - {arrow} let's you use {dplyr} verbs against a single parquet file (or, perhaps more importantly, a *set* of parquet files) to query the data in those files - When it comes to building Shiny apps, we should look for easy places where we can gain efficiency & speed to improve our user experience (you don't want users waiting 20 seconds for your data prep logic to run against a single massive csv); it's very likely that the combination of .parquet + {arrow} + {dplyr} can meet your app performance needs (it does for at least 95% of my use cases -- there are very few cases where I have to go beyond that and start looking into other engines for faster data manipulation) ::: + +# Async Processing + +## Single (threaded) Line {background-image="assets/img/lego_line_pay.jpg" background-size="cover"} + +* A single R process managing the different tasks in a Shiny application +* Executed one-by-one + +# Should I care? + +## It Depends ... + +If you are the __only__ user for a quick and efficient app: Likely not + +::: {.notes} +TODO: Find a way to center the sentence vertically in the slide +::: + +## Crowd Pleaser + +Multiple users accessing the app __concurrently__: + +* Single-threaded R process serving multiple users in typical deployments + +## Asynchronous Processing (circa 2018) + +:::: {.columns} + +::: {.column width="50%"} + +### 📦 [`{promises}`](https://rstudio.github.io/promises/index.html) + +Handle objects representing the (eventual) result of an async operation + +::: + +::: {.column width="50%"} + +### 📦 [`{future}`](https://future.futureverse.org/) + +Launch tasks without blocking current R session + +::: + +:::: + +::: footer +[Using promises with Shiny](https://rstudio.github.io/promises/articles/promises_06_shiny.html) +::: + +## Introducing [`{crew}`](https://wlandau.github.io/crew/) + +> A distributed worker launcher for asynchronous tasks + +* Extends use of the [mirai](https://github.com/shikokuchuo/mirai) task scheduler to multiple computing backends +* Central controller object manages tasks (scales on fly) +* Supports multiple [controller groups](https://wlandau.github.io/crew/articles/controller_groups.html) for specialized worker types +* Fits nicely with [`{targets}`](https://docs.ropensci.org/targets/) and ... + +. . . + +![](assets/img/shiny.png){.absolute top=0 left=200} + +## Watch-Along {background-color="#17395c"} + +Using `{crew}` inside a Shiny application: + +* Vignette: +* Application: + +## Setting up for Success + +1. Create functions for long-running tasks +1. Create multiple [`reactiveVal`](https://shiny.posit.co/r/reference/shiny/latest/reactiveval) objects for bookkeeping +1. Set up a `{crew}` controller +1. Establish an event-driven push of task to the controller with monitoring of worker status + +# Code-Along {background-color="#17395c"} + +[Code-Along 1](codealong-1.html){target="_blank"}: Asynchronous calls of a web API \ No newline at end of file diff --git a/materials/d1-9002-async/index.qmd b/materials/d1-9002-async/index.qmd deleted file mode 100644 index 4a86079..0000000 --- a/materials/d1-9002-async/index.qmd +++ /dev/null @@ -1,95 +0,0 @@ ---- -title: "Asynchronous Processing" -subtitle: "posit::conf(2023)
Shiny in Production: Tools & Techniques" -author: "TBD" -footer: "[{{< var workshop_short_url >}}]({{< var workshop_full_url >}})" -format: - revealjs: - theme: [default, ../slides.scss] # moon= teal bg | dark - scrollable: true - incremental: false - slide-number: c/t # c/t | c | h/v | h.v - slide-tone: false #true - code-line-numbers: true - history: false -revealjs-plugins: - - codewindow ---- - -## Single (threaded) Line {background-image="assets/img/lego_line_pay.jpg" background-size="cover"} - -* A single R process managing the different tasks in a Shiny application -* Executed one-by-one - -# Should I care? - -## It Depends ... - -If you are the __only__ user for a quick and efficient app: Likely not - -::: {.notes} -TODO: Find a way to center the sentence vertically in the slide -::: - -## Crowd Pleaser - -Multiple users accessing the app __concurrently__: - -* Single-threaded R process serving multiple users in typical deployments - -## Asynchronous Processing (circa 2018) - -:::: {.columns} - -::: {.column width="50%"} - -### 📦 [`{promises}`](https://rstudio.github.io/promises/index.html) - -Handle objects representing the (eventual) result of an async operation - -::: - -::: {.column width="50%"} - -### 📦 [`{future}`](https://future.futureverse.org/) - -Launch tasks without blocking current R session - -::: - -:::: - -::: footer -[Using promises with Shiny](https://rstudio.github.io/promises/articles/promises_06_shiny.html) -::: - -## Introducing [`{crew}`](https://wlandau.github.io/crew/) - -> A distributed worker launcher for asynchronous tasks - -* Extends use of the [mirai](https://github.com/shikokuchuo/mirai) task scheduler to multiple computing backends -* Central controller object manages tasks (scales on fly) -* Supports multiple [controller groups](https://wlandau.github.io/crew/articles/controller_groups.html) for specialized worker types -* Fits nicely with [`{targets}`](https://docs.ropensci.org/targets/) and ... - -. . . - -![](assets/img/shiny.png){.absolute top=0 left=200} - -### Watch-Along {background-color="#17395c"} - -Using `{crew}` inside a Shiny application: - -* Vignette: -* Application: - -## Setting up for Success - -1. Create functions for long-running tasks -1. Create multiple [`reactiveVal`](https://shiny.posit.co/r/reference/shiny/latest/reactiveval) objects for bookkeeping -1. Set up a `{crew}` controller -1. Establish an event-driven push of task to the controller with monitoring of worker status - -# Code-Along {background-color="#17395c"} - -[Code-Along 1](codealong-1.html){target="_blank"}: Asynchronous calls of a web API \ No newline at end of file diff --git a/units/d1-03-performance.qmd b/units/d1-03-performance.qmd index 071bdaf..879899b 100644 --- a/units/d1-03-performance.qmd +++ b/units/d1-03-performance.qmd @@ -15,16 +15,22 @@ listing: sort-ui: false filter-ui: false image-placeholder: assets/img/placeholder.png + - id: code-along + contents: + - ../materials/d1-03-performance/codealong-*.qmd + type: table + fields: [subtitle, title] + field-display-names: + subtitle: "Code-Along" + sort: [filename] + sort-ui: false + filter-ui: false + image-placeholder: assets/img/placeholder.png tbl-colwidths: [5,20,75] --- ## Slides -::: callout-warning -These slides are under construction and will be finalized prior to the workshop date. -::: - - [View slides in full screen](../materials/d1-03-performance/index.html) ```{=html} diff --git a/units/d1-9002-async.qmd b/units/d1-9002-async.qmd deleted file mode 100644 index 19e03e3..0000000 --- a/units/d1-9002-async.qmd +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Async Processing" -subtitle: "TBD" -author: "Eric Nantz & Michael Thomas" -date: "2023-09-18" -listing: - - id: code-along - contents: - - ../materials/d1-9002-async/codealong-*.qmd - type: table - fields: [subtitle, title] - field-display-names: - subtitle: "Code-Along" - sort: [filename] - sort-ui: false - filter-ui: false - image-placeholder: assets/img/placeholder.png -tbl-colwidths: [5,20,75] ---- - -## Slides - -::: callout-warning -These slides are under construction and will be finalized prior to the workshop date. -::: - -[View slides in full screen](../materials/d1-9002-async/index.html) - -```{=html} - -``` -