返回技能列表

optimize-shiny-performance

pjt222
更新于 Yesterday
3 次查看
17
2
17
在 GitHub 上查看
general

关于

This skill helps developers profile and optimize slow or unresponsive Shiny applications. It provides techniques like caching with bindCache/memoise, async operations with promises/ExtendedTask, and reactive flow control with debounce/throttle. Use it when diagnosing bottlenecks, handling concurrent load, or preparing apps for production deployment.

快速安装

Claude Code

推荐
主要方式
npx skills add pjt222/agent-almanac -a claude-code
插件命令备选方式
/plugin add https://github.com/pjt222/agent-almanac
Git 克隆备选方式
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/optimize-shiny-performance

在 Claude Code 中复制并粘贴此命令以安装该技能

技能文档

Optimize Shiny Performance

Profile, diagnose, optimize Shiny app performance through caching, async operations, reactive graph optimization.

When Use

  • Shiny app feels slow or unresponsive during user interaction
  • Server resources exhausted under concurrent user load
  • Specific operations (data loading, plotting, computation) create bottlenecks
  • Preparing app for production deployment with many users

Inputs

  • Required: Path to Shiny application
  • Required: Description of performance problem (slow load, laggy interaction, high memory)
  • Optional: Number of expected concurrent users
  • Optional: Available server resources (RAM, CPU cores)
  • Optional: Whether app uses database or external API

Steps

Step 1: Profile the Application

# Profile with profvis
profvis::profvis({
  shiny::runApp("path/to/app", display.mode = "normal")
})

# Or profile specific operations
profvis::profvis({
  result <- expensive_computation(data)
})

Identify top bottlenecks:

  1. Data loading: How long does initial data fetch take?
  2. Reactive recalculation: Which reactives fire most often?
  3. Rendering: Which outputs take longest to render?
  4. External calls: Database queries, API requests, file I/O?

Use reactive log for graph analysis:

# Enable reactive logging
options(shiny.reactlog = TRUE)
shiny::runApp("path/to/app")
# Press Ctrl+F3 in the browser to view the reactive graph

Got: Clear identification of 2-3 biggest bottlenecks.

If fail: profvis doesn't show useful detail? Wrap specific sections with profvis::profvis(). Reactlog overwhelming? Focus on one interaction at a time.

Step 2: Optimize Reactive Graph

Reduce unnecessary reactive invalidations:

# 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())
})

Use isolate() to prevent unnecessary invalidations:

# 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)))
  })
})

Use debounce() and throttle() for high-frequency inputs:

# 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)

Got: Reactive graph fires only necessary recalculations.

If fail: Removing dependency breaks functionality? Use req() to add explicit guards instead of relying on implicit reactive dependencies.

Step 3: Implement Caching

bindCache for Shiny Outputs

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 uses input values as cache keys. Same inputs occur again? Cached result returned immediately.

memoise for Functions

# 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)
)

App-level Data Pre-computation

# 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
}

Got: Repeated operations use cached results; response time drops significantly.

If fail: Cache grows too large? Set max_age or max_size limits. Cached values stale? Reduce max_age or add cache-clear button. bindCache causes errors? Ensure cache key inputs serializable.

Step 4: Add Async for Long Operations

Use ExtendedTask (Shiny >= 1.8.1) for long-running computations:

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()
  })
}

For apps on Shiny < 1.8.1, use promises directly:

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()
  })
}

Got: Long operations don't block UI; other users can interact while computation runs.

If fail: future_promise errors? Check plan(multisession) is set. Variables not available in future? Pass explicitly — futures run in separate R processes.

Step 5: Optimize Rendering

Reduce rendering overhead:

# 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()
})

Got: Rendering operations faster, don't block UI.

If fail: plotly slow with large datasets? Use toWebGL() for WebGL rendering or downsample data before plotting.

Step 6: Validate Performance Improvements

# 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")

Got: Measurable improvement in response times and/or concurrent user capacity.

If fail: Performance didn't improve? Re-profile to find next bottleneck. Optimization iterative — fix biggest bottleneck first, re-measure.

Checks

  • Profiling identifies specific bottlenecks (not guessing)
  • Reactive graph has no unnecessary invalidation chains
  • Expensive operations use caching (bindCache or memoise)
  • Long-running computations use async (ExtendedTask or promises)
  • High-frequency inputs use debounce/throttle
  • Large datasets use server-side processing
  • Performance improvement measurable (before/after timing)

Pitfalls

  • Premature optimization: Profile first. Bottleneck rarely where you think it is.
  • Cache invalidation bugs: Users see stale data? Cache key doesn't include all relevant inputs. Add missing dependencies to bindCache().
  • Future variable scoping: future_promise runs in separate process. Global variables, database connections, reactive values must be captured explicitly.
  • Reactive spaghetti: Reactive graph too complex to understand? App needs architectural refactoring (modules), not just caching.
  • Over-caching: Caching everything wastes memory. Only cache operations expensive AND with repeated input patterns.

See Also

  • build-shiny-module — modular architecture for maintainable reactive code
  • scaffold-shiny-app — choose right app framework from start
  • deploy-shiny-app — deploy optimized apps with appropriate server resources
  • test-shiny-app — performance regression tests

GitHub 仓库

pjt222/agent-almanac
路径: i18n/caveman/skills/optimize-shiny-performance
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

相关推荐技能

content-collections

Content Collections 是一个 TypeScript 优先的构建工具,可将本地 Markdown/MDX 文件转换为类型安全的数据集合。它专为构建博客、文档站和内容密集型 Vite+React 应用而设计,提供基于 Zod 的自动模式验证。该工具涵盖从 Vite 插件配置、MDX 编译到生产环境部署的完整工作流。

查看技能

polymarket

这个Claude Skill为开发者提供完整的Polymarket预测市场开发支持,涵盖API调用、交易执行和市场数据分析。关键特性包括实时WebSocket数据流,可监控实时交易、订单和市场动态。开发者可用它构建预测市场应用、实施交易策略并集成实时市场预测功能。

查看技能

creating-opencode-plugins

该Skill帮助开发者创建OpenCode插件,用于接入命令、文件、LSP等25+种事件。它提供了插件结构、事件API规范和JavaScript/TypeScript实现模式,适合需要拦截操作、扩展功能或自定义事件处理的场景。开发者可通过它快速构建响应式模块来增强OpenCode AI助手的能力。

查看技能

sglang

SGLang是一个专为LLM设计的高性能推理框架,特别适用于需要结构化输出的场景。它通过RadixAttention前缀缓存技术,在处理JSON、正则表达式、工具调用等具有重复前缀的复杂工作流时,能实现极速生成。如果你正在构建智能体或多轮对话系统,并追求远超vLLM的推理性能,SGLang是理想选择。

查看技能