executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
optimize-shiny-performancePerfilar y optimizar el rendimiento de aplicaciones Shiny usando profvis, bindCache, memoise, async/promises, debounce/throttle y ExtendedTask para computaciones de larga duración. Úsalo cuando la app se siente lenta o no responde durante la interacción del usuario, cuando los recursos del servidor se agotan bajo carga concurrente, cuando operaciones especí…
Independent GitHub snapshot · Automated classification · Original documentation governs
Perfilar, diagnosticar y optimizar el rendimiento de aplicaciones Shiny mediante caché, operaciones async y optimización del grafo reactivo.
# Profile with profvis
profvis::profvis({
shiny::runApp("path/to/app", display.mode = "normal")
})
# Or profile specific operations
profvis::profvis({
result <- expensive_computation(data)
})
Identifica los principales cuellos de botella:
Usa el registro reactivo para análisis del grafo reactivo:
# Enable reactive logging
options(shiny.reactlog = TRUE)
shiny::runApp("path/to/app")
# Press Ctrl+F3 in the browser to view the reactive graph
Esperado: Identificación clara de los 2-3 mayores cuellos de botella.
En caso de fallo: Si profvis no muestra detalles útiles, envuelve secciones específicas con profvis::profvis(). Si reactlog es abrumador, céntrate en una interacción a la vez.
Reduce las invalidaciones reactivas innecesarias:
# BAD: Recomputes on ANY input change
output$plot <- renderPlot({
data <- load_data() # Runs every time
filtered <- data[data$category == input$category, ]
plot(filtered)
})
# GOOD: Isolate data loading from filtering
raw_data <- reactive({
load_data()
}) |> bindCache() # Cache the expensive part
filtered_data <- reactive({
raw_data()[raw_data()$category == input$category, ]
})
output$plot <- renderPlot({
plot(filtered_data())
})
Usa isolate() para prevenir invalidaciones innecesarias:
# Only recompute when the button is clicked, not on every input change
output$result <- renderText({
input$compute # Take dependency on button
isolate({
paste("N =", input$n, "Mean =", mean(rnorm(input$n)))
})
})
Usa debounce() y throttle() para entradas de alta frecuencia:
# Debounce text input — wait 500ms after user stops typing
search_text <- reactive(input$search) |> debounce(500)
# Throttle slider — update at most every 250ms
slider_value <- reactive(input$slider) |> throttle(250)
Esperado: El grafo reactivo solo activa los recálculos necesarios.
En caso de fallo: Si eliminar una dependencia rompe la funcionalidad, usa req() para añadir guardas explícitas en lugar de depender de dependencias reactivas implícitas.
output$plot <- renderPlot({
create_expensive_plot(filtered_data())
}) |> bindCache(input$category, input$date_range)
output$table <- renderDT({
expensive_query(input$filters)
}) |> bindCache(input$filters)
bindCache usa los valores de entrada como claves de caché. Cuando las mismas entradas ocurren de nuevo, el resultado en caché se devuelve inmediatamente.
# Cache expensive function results
load_reference_data <- memoise::memoise(
function(dataset_name) {
readr::read_csv(paste0("data/", dataset_name, ".csv"))
},
cache = cachem::cache_disk("cache/", max_age = 3600)
)
# In global.R or outside server function — computed once at app startup
reference_data <- readr::read_csv("data/reference.csv")
model <- readRDS("models/trained_model.rds")
server <- function(input, output, session) {
# reference_data and model are available to all sessions
# without reloading
}
Esperado: Las operaciones repetidas usan resultados en caché; el tiempo de respuesta cae significativamente.
En caso de fallo: Si la caché crece demasiado, establece límites max_age o max_size. Si los valores en caché son obsoletos, reduce max_age o añade un botón para limpiar la caché. Si bindCache causa errores, asegúrate de que las entradas de la clave de caché sean serializables.
Usa ExtendedTask (Shiny >= 1.8.1) para computaciones de larga duración:
server <- function(input, output, session) {
# Define the extended task
analysis_task <- ExtendedTask$new(function(data, params) {
promises::future_promise({
# This runs in a background process
run_heavy_analysis(data, params)
})
}) |> bind_task_button("run_analysis")
# Trigger the task
observeEvent(input$run_analysis, {
analysis_task$invoke(dataset(), input$params)
})
# Use the result
output$result <- renderTable({
analysis_task$result()
})
}
Para apps en Shiny < 1.8.1, usa promises directamente:
library(promises)
library(future)
plan(multisession, workers = 4)
server <- function(input, output, session) {
result <- eventReactive(input$compute, {
future_promise({
Sys.sleep(5) # Simulate long computation
expensive_analysis(isolate(input$params))
})
})
output$table <- renderTable({
result()
})
}
Esperado: Las operaciones largas no bloquean la UI; otros usuarios pueden interactuar mientras la computación se ejecuta.
En caso de fallo: Si future_promise da error, verifica que plan(multisession) esté configurado. Si las variables no están disponibles en el futuro, pásalas explícitamente — los futuros se ejecutan en procesos R separados.
Reduce la sobrecarga de renderizado:
# Use plotly for interactive plots instead of re-rendering
output$plot <- plotly::renderPlotly({
plotly::plot_ly(filtered_data(), x = ~x, y = ~y, type = "scatter")
})
# Use server-side DT for large tables
output$table <- DT::renderDataTable({
DT::datatable(large_data(), server = TRUE, options = list(
pageLength = 25,
processing = TRUE
))
})
# Conditional UI to avoid rendering hidden elements
output$details <- renderUI({
req(input$show_details)
expensive_details_ui()
})
Esperado: Las operaciones de renderizado son más rápidas y no bloquean la UI.
En caso de fallo: Si plotly es lento con conjuntos de datos grandes, usa toWebGL() para renderizado WebGL o reduce la muestra de datos antes de graficar.
# Before/after benchmarking
system.time({
shiny::testServer(myModuleServer, args = list(...), {
session$setInputs(category = "A")
session$flushReact()
})
})
# Load testing with shinyloadtest
shinyloadtest::record_session("http://localhost:3838")
shinyloadtest::shinycannon(
"recording.log",
"http://localhost:3838",
workers = 10,
loaded_duration_minutes = 5
)
shinyloadtest::shinyloadtest_report("recording.log")
Esperado: Mejora medible en los tiempos de respuesta y/o capacidad de usuarios concurrentes.
En caso de fallo: Si el rendimiento no mejoró, vuelve a perfilar para encontrar el siguiente cuello de botella. La optimización del rendimiento es iterativa — corrige primero el mayor cuello de botella y luego vuelve a medir.
bindCache().future_promise se ejecuta en un proceso separado. Las variables globales, conexiones de base de datos y valores reactivos deben capturarse explícitamente.build-shiny-module — arquitectura modular para código reactivo manteniblescaffold-shiny-app — elegir el framework correcto de la app desde el iniciodeploy-shiny-app — desplegar apps optimizadas con recursos de servidor apropiadostest-shiny-app — tests de regresión de rendimiento