실행 컨텍스트

개요

Shiny 인터랙티브 문서에는 렌더링 시 실행되는 코드와 사용자 동작 및 입력 값 변경에 반응해 서버에서 실행되는 코드가 함께 들어갈 수 있습니다. 이러한 실행 컨텍스트를 제대로 이해하는 것은 개발 중 올바른 모델을 갖는 데에도 중요하고 문서 성능을 최적화하는 데에도 중요합니다.

렌더링 & 서버 컨텍스트

좀 더 명확히 설명하기 위해, 인터랙티브 문서 소개에서 시작한 “Hello, Shiny” 문서를 다시 살펴보겠습니다.

---
title: "Old Faithful"
format: html
server: shiny
---

```{r}
sliderInput("bins", "Number of bins:", 
            min = 1, max = 50, value = 30)
plotOutput("distPlot")
```

```{r}
#| context: server
output$distPlot <- renderPlot({
  x <- faithful[, 2]  # Old Faithful Geyser data
  bins <- seq(min(x), max(x), length.out = input$bins + 1)
  hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
```

이 문서의 실행 흐름은 다음과 같이 정리할 수 있습니다.

  1. sliderInput()plotOutput() 호출이 들어 있는 첫 번째 코드 청크는 문서를 렌더링할 때 실행됩니다(예: quarto render old-faithful.qmd).

  2. context: server 옵션이 있는 두 번째 코드 청크는 렌더링 시에는 실행되지 않으며, 문서가 서빙될 때만 실행됩니다.

두 청크는 완전히 별도의 R 세션에서 실행된다는 점을 이해하는 것이 매우 중요합니다. 즉, 첫 번째 청크에서 만든 변수를 두 번째 청크에서 사용할 수 없고, 그 반대도 마찬가지입니다. 이는 일반적인 Shiny 애플리케이션을 구성하는 ui.Rserver.R의 관계와 유사합니다.

물론 컨텍스트 간에 코드를 재사용할 수 있으면 유용하므로, 아래 코드 공유 섹션에서 이를 위한 방법을 다룹니다.

인터랙티브 문서의 코드를 이해하고 다루기 쉽게 만들기 위해, 서버 컨텍스트(여러 개일 수도 있음)는 문서 하단에 배치하는 것을 강력히 권장합니다. 이렇게 하면 문서 소스 흐름에서 서로 다른 실행 환경이 더 명확해집니다.

server.R

보다 강한 분리를 원한다면 다른 선택지도 있습니다. .qmd 파일에는 렌더링 시 실행될 코드만 넣고, 서버 코드는 별도의 server.R 파일로 분리할 수 있습니다.

이 방식으로 예시를 다시 작성하면 다음과 같습니다.

old-faithful.qmd
---
title: "Old Faithful"
format: html
server: shiny
---

```{r}
sliderInput("bins", "Number of bins:", 
            min = 1, max = 50, value = 30)
plotOutput("distPlot")
```
server.R
function(input, output, session) {
  output$distPlot <- renderPlot({
    x <- faithful[, 2]  # Old Faithful Geyser data
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = 'darkgray', border = 'white')
  })
}

이는 다소 덜 편리할 수 있지만, 전통적인 Shiny 애플리케이션의 ui.R / server.R 분리와 더 잘 맞습니다.

코드 공유

렌더링 컨텍스트 간 코드를 공유하는 방식은 .qmd 파일 하나로 작성하는지, server.R를 사용하는지에 따라 조금 다릅니다. 아래에서 두 시나리오를 모두 다룹니다.

단일 파일

context: setup

렌더링과 서빙 컨텍스트 모두에서 코드를 실행하려면 context: setup이 있는 코드 청크를 만듭니다. 예시는 다음과 같습니다.

```{r}
#| context: setup
#| include: false

# load libraries
library(dplyr)

# load data
dataset <- import_data("data.csv")
dataset <- sample_n(dataset, 1000)
```

이 코드는 렌더링 시에도 실행되고, 각 사용자 세션마다 서버가 생성될 때도 실행됩니다. 또한 include: false를 지정해 이 청크의 코드, 경고, 출력이 렌더링된 문서에 포함되지 않도록 합니다.

context: data

데이터 로딩과 변환은 Shiny 애플리케이션의 시작 시간을 좌우하는 경우가 많습니다. 인터랙티브 문서는 두 단계(초기 렌더링, 그리고 사용자에게 문서를 서빙)로 실행되므로, 렌더링 중에 비용이 큰 데이터 처리 작업을 수행하고 애플리케이션 시작 시에는 데이터만 로드하도록 할 수 있습니다.

context: data 옵션을 R 코드 청크에 추가하면 사전 렌더링된 데이터를 정의할 수 있습니다. 이 청크는 렌더링 중 실행되며, 생성된 R 객체는 .RData 파일에 저장되고 Shiny 서버 시작 시 로드됩니다. 예를 들어 위의 setup 청크에서 데이터 로딩을 별도의 청크로 분리하면 다음과 같습니다.

```{r}
#| context: data
#| include: false

dataset <- import_data("data.csv")
dataset <- sample_n(dataset, 1000)
```

context: data 청크에서 생성된 R 객체는 UI 렌더링과 서버 컨텍스트 모두에서 사용할 수 있습니다.

Knitr cache

데이터 렌더링 성능은 cache: true 옵션으로 더 향상시킬 수 있습니다. 이 옵션을 추가하면 필요할 때만 코드 청크가 다시 실행됩니다. 예시는 다음과 같습니다.

```{r}
#| context: data
#| include: false
#| cache: true
#| cache.extra: !expr file.info("data.csv")$mtime

dataset <- import_data("data.csv")
dataset <- sample_n(dataset, 1000)
```

이 예시에서는 청크의 R 코드가 변경되거나 “data.csv” 파일의 수정 시간이 변경되면 캐시가 무효화됩니다(cache.extra 옵션으로 구현됩니다).

기존 캐시는 인터랙티브 문서와 함께 있는 _cache 디렉터리를 삭제하여 무효화할 수도 있습니다.

context: server-start

추가로, 여러 사용자 세션 간에 코드와 데이터를 공유할 수 있는 실행 컨텍스트가 하나 더 있습니다. context: server-start가 있는 청크는 Shiny 문서가 처음 실행될 때 한 번만 실행되며, 문서의 각 새 사용자마다 다시 실행되지 않습니다. context: server-start는 다음과 같은 시나리오에 적합합니다.

  1. 원격 서버(예: 데이터베이스, Spark 컨텍스트 등)에 대한 공유 연결을 설정할 때.

  2. 세션 간 공유할 반응형 값을 생성할 때(예: reactivePoll 또는 reactiveFileReader).

예시는 다음과 같습니다.

```{r}
#| context: server-start

library(DBI)
db <- dbConnect(...)
```

여러 파일

인터랙티브 문서가 사용자 인터페이스를 정의하는 .qmd 파일과 서버를 위한 server.R 파일을 사용하는 경우, 공유 코드는 global.R이라는 파일에 둘 수 있습니다. global.R에 정의된 함수와 변수는 렌더링 중에도, 서버 실행 중에도 모두 사용할 수 있습니다.

이 시나리오에서 인터랙티브 문서는 다음과 같은 세 개의 소스 파일로 구성됩니다.

파일 설명
doc.qmd Markdown 콘텐츠와 Shiny 입력/출력(예: sliderInput(), plotOutput() 등)
server.R 반응형 표현식, 출력 할당 등을 포함한 메인 서버 함수
global.R doc.qmdserver.R에서 공유되는 코드