Azure Databricks の Shiny

Shiny は、CRAN で利用できる R パッケージであり、対話型の R アプリケーションとダッシュボードを構築するために使用されます。 Azure Databricks クラスターでホストされている RStudio Server 内で Shiny を使用できます。 また、Azure Databricks ノートブックから直接 Shiny アプリケーションを開発、ホスト、共有することもできます。

Shiny の使用を開始する場合は、Shiny のチュートリアルを参照してください。 これらのチュートリアルは、Azure Databricks ノートブックで実行できます。

この記事では、Azure Databricks で Shiny アプリケーションを実行し、Shiny アプリケーション内で Apache Spark を使用する方法について説明します。

R ノートブック内の Shiny

R ノートブック内で Shiny の使用を開始する

Shiny パッケージは、Databricks Runtime に含まれています。 ホストされている RStudio と同様に、Azure Databricks R ノートブック内で、Shiny アプリケーションを対話的に開発およびテストできます。

作業を開始するには、次の手順に従います。

  1. R ノートブックを作成します。

  2. Shiny パッケージをインポートし、次のようにサンプル アプリ 01_hello を実行します。

      library(shiny)
      runExample("01_hello")
    
  3. アプリの準備ができると、新しいタブを開くためのクリック可能なリンクとして、Shiny アプリの URL が出力に含められます。このアプリを他のユーザーと共有するには、「Shiny アプリの URL を共有する」を参照してください。

    Shiny アプリの例

注意

  • ログ メッセージは、例に示されている既定のログ メッセージ (Listening on http://0.0.0.0:5150) と同様に、コマンドの結果に表示されます。
  • Shiny アプリケーションを停止するには、[キャンセル] をクリックします。
  • Shiny アプリケーションは、ノートブックの R プロセスを使用します。 クラスターからノートブックをデタッチした場合、またはアプリケーションを実行しているセルを取り消した場合、Shiny アプリケーションは終了します。 Shiny アプリケーションの実行中は、他のセルを実行することはできません。

Databricks Git フォルダーから Shiny アプリを実行する

Databricks Git フォルダーにチェックインされている Shiny アプリを実行できます。

  1. リモート Git リポジトリをクローンします。

  2. アプリケーションを実行します。

    library(shiny)
    runApp("006-tabsets")
    

ファイルから Shiny アプリを実行する

自分の Shiny アプリケーションのコードが、バージョン コントロールによって管理されているプロジェクトの一部である場合は、それをノートブック内で実行できます。

注意

絶対パスを使用するか、setwd() を使用して作業ディレクトリを設定する必要があります。

  1. 次のようなコードを使用して、リポジトリからコードをチェックアウトします。

      %sh git clone https://github.com/rstudio/shiny-examples.git
      cloning into 'shiny-examples'...
    
  2. アプリケーションを実行するには、別のセルに次のようなコードを入力します。

    library(shiny)
    runApp("/databricks/driver/shiny-examples/007-widgets/")
    

Shiny アプリの URL を共有する

アプリの起動時に生成される Shiny アプリの URL は、他のユーザーと共有できます。 クラスターに対するアタッチ可能アクセス許可を持つすべての Azure Databricks ユーザーは、アプリとクラスターの両方が実行されている限り、アプリを表示および操作できます。

アプリが実行されているクラスターが終了すると、アプリにアクセスできなくなります。 クラスター設定で、自動終了を無効にできます。

別のクラスターで Shiny アプリをホストするノートブックをアタッチして実行すると、Shiny URL が変更されます。 また、同じクラスター上でアプリを再起動すると、Shiny によって別のランダムなポートが選択される場合があります。 URL を安定させるには、shiny.port オプションを設定するか、同じクラスターでアプリを再起動するときに port 引数を指定します。

ホストされている RStudio Server 上の Shiny

必要条件

重要

RStudio Server Pro では、プロキシ認証を無効にする必要があります。 auth-proxy=1/etc/rstudio/rserver.conf 内に存在しないことを確認してください。

ホストされている RStudio Server で Shiny の使用を開始する

  1. Azure Databricks の RStudio を開きます。

  2. RStudio で、Shiny パッケージをインポートし、次のようにサンプル アプリ 01_hello を実行します。

    > library(shiny)
    > runExample("01_hello")
    
    Listening on http://127.0.0.1:3203
    

    新しいウィンドウが表示され、Shiny アプリケーションが示されます。

    最初の Shiny アプリ

R スクリプトから Shiny アプリを実行する

R スクリプトから Shiny アプリを実行するには、RStudio エディターで R スクリプトを開き、右上にある [アプリの実行] ボタンをクリックします。

Shiny 実行アプリ

Shiny アプリ内で Apache Spark を使用する

SparkR または sparklyr を利用して、Shiny アプリケーション内で Apache Spark を使用できます。

ノートブックで Shiny と SparkR を使用する

library(shiny)
library(SparkR)
sparkR.session()

ui <- fluidPage(
  mainPanel(
    textOutput("value")
  )
)

server <- function(input, output) {
  output$value <- renderText({ nrow(createDataFrame(iris)) })
}

shinyApp(ui = ui, server = server)

ノートブックで Shiny と sparklyr を使用する

library(shiny)
library(sparklyr)

sc <- spark_connect(method = "databricks")

ui <- fluidPage(
  mainPanel(
    textOutput("value")
  )
)

server <- function(input, output) {
  output$value <- renderText({
    df <- sdf_len(sc, 5, repartition = 1) %>%
      spark_apply(function(e) sum(e)) %>%
      collect()
    df$result
  })
}

shinyApp(ui = ui, server = server)
library(dplyr)
library(ggplot2)
library(shiny)
library(sparklyr)

sc <- spark_connect(method = "databricks")
diamonds_tbl <- spark_read_csv(sc, path = "/databricks-datasets/Rdatasets/data-001/csv/ggplot2/diamonds.csv")

# Define the UI
ui <- fluidPage(
  sliderInput("carat", "Select Carat Range:",
              min = 0, max = 5, value = c(0, 5), step = 0.01),
  plotOutput('plot')
)

# Define the server code
server <- function(input, output) {
  output$plot <- renderPlot({
    # Select diamonds in carat range
    df <- diamonds_tbl %>%
      dplyr::select("carat", "price") %>%
      dplyr::filter(carat >= !!input$carat[[1]], carat <= !!input$carat[[2]])

    # Scatter plot with smoothed means
    ggplot(df, aes(carat, price)) +
      geom_point(alpha = 1/2) +
      geom_smooth() +
      scale_size_area(max_size = 2) +
      ggtitle("Price vs. Carat")
  })
}

# Return a Shiny app object
shinyApp(ui = ui, server = server)

Spark Shiny アプリ

よく寄せられる質問 (FAQ)

しばらくしてから Shiny アプリが灰色表示されるのはなぜですか?

Shiny アプリとのやり取りがない場合、そのアプリへの接続は約 4 分後に切断されます。

再接続するには、Shiny アプリ ページを更新します。 ダッシュボードの状態はリセットされます。

しばらくしてから Shiny ビューアー ウィンドウが表示されなくなるのはなぜですか?

Shiny ビューアー ウィンドウが数分間アイドル状態になってから表示されなくなる場合は、''灰色表示'' のシナリオと同じタイムアウトが原因です。

長い Spark ジョブが戻らないのはなぜですか?

これもアイドル タイムアウトが原因です。 前述のタイムアウトを超えて実行されているすべての Spark ジョブでは、そのジョブが戻る前に接続が切断されるため、結果をレンダリングできません。

タイムアウトを回避するにはどうすればよいですか?

  • Github の「機能要求: クライアントからキープ アライブ メッセージを送信して一部のロード バランサーでの TCP タイムアウトを回避する」に回避策が提案されています。 この回避策では、アプリがアイドル状態のときに WebSocket 接続を維持するためにハートビートを送信します。 ただし、アプリが長時間実行される計算によってブロックされている場合、この回避策は機能しません。

  • Shiny では長時間実行されるタスクはサポートされません。 Shiny のブログ記事では、promise と future を使用して長いタスクを非同期に実行し、アプリをブロック解除したままにしておくことが推奨されています。 ハートビートを使用して Shiny アプリを存続させ、長時間実行される Spark ジョブを future コンストラクトで実行する例を以下に示します。

    # Write an app that uses spark to access data on Databricks
    # First, install the following packages:
    install.packages(‘future’)
    install.packages(‘promises’)
    
    library(shiny)
    library(promises)
    library(future)
    plan(multisession)
    
    HEARTBEAT_INTERVAL_MILLIS = 1000  # 1 second
    
    # Define the long Spark job here
    run_spark <- function(x) {
      # Environment setting
      library("SparkR", lib.loc = "/databricks/spark/R/lib")
      sparkR.session()
    
      irisDF <- createDataFrame(iris)
      collect(irisDF)
      Sys.sleep(3)
      x + 1
    }
    
    run_spark_sparklyr <- function(x) {
      # Environment setting
      library(sparklyr)
      library(dplyr)
      library("SparkR", lib.loc = "/databricks/spark/R/lib")
      sparkR.session()
      sc <- spark_connect(method = "databricks")
    
      iris_tbl <- copy_to(sc, iris, overwrite = TRUE)
      collect(iris_tbl)
      x + 1
    }
    
    ui <- fluidPage(
      sidebarLayout(
        # Display heartbeat
        sidebarPanel(textOutput("keep_alive")),
    
        # Display the Input and Output of the Spark job
        mainPanel(
          numericInput('num', label = 'Input', value = 1),
          actionButton('submit', 'Submit'),
          textOutput('value')
        )
      )
    )
    server <- function(input, output) {
      #### Heartbeat ####
      # Define reactive variable
      cnt <- reactiveVal(0)
      # Define time dependent trigger
      autoInvalidate <- reactiveTimer(HEARTBEAT_INTERVAL_MILLIS)
      # Time dependent change of variable
      observeEvent(autoInvalidate(), {  cnt(cnt() + 1)  })
      # Render print
      output$keep_alive <- renderPrint(cnt())
    
      #### Spark job ####
      result <- reactiveVal() # the result of the spark job
      busy <- reactiveVal(0)  # whether the spark job is running
      # Launch a spark job in a future when actionButton is clicked
      observeEvent(input$submit, {
        if (busy() != 0) {
          showNotification("Already running Spark job...")
          return(NULL)
        }
        showNotification("Launching a new Spark job...")
        # input$num must be read outside the future
        input_x <- input$num
        fut <- future({ run_spark(input_x) }) %...>% result()
        # Or: fut <- future({ run_spark_sparklyr(input_x) }) %...>% result()
        busy(1)
        # Catch exceptions and notify the user
        fut <- catch(fut, function(e) {
          result(NULL)
          cat(e$message)
          showNotification(e$message)
        })
        fut <- finally(fut, function() { busy(0) })
        # Return something other than the promise so shiny remains responsive
        NULL
      })
      # When the spark job returns, render the value
      output$value <- renderPrint(result())
    }
    shinyApp(ui = ui, server = server)
    
  • 最初のページ読み込みから 12 時間というハード制限があり、その後、接続は、アクティブな場合であっても終了します。 このような場合に再接続するには、Shiny アプリを最新の状態に更新する必要があります。 ただし、基盤となる WebSocket 接続は、ネットワークの不安定性やコンピューターのスリープ モードなど、さまざまな要因によっていつでも切断される可能性があります。 Databricks では、長時間の接続を必要とせず、セッションの状態に過度に依存しないように、Shiny アプリを書き直すことをお勧めします。

アプリは起動直後にクラッシュしますが、コードは正しいようです。 どうなっているのでしょうか?

Azure Databricks の Shiny アプリに表示できるデータの合計量は 50 MB に制限されます。 アプリケーションのデータの合計サイズがこの制限を超えると、起動直後にクラッシュします。 これを回避するために、Databricks では、表示されるデータをダウンサンプリングしたり、画像の解像度を下げたりするなどして、データ サイズを小さくすることを推奨しています。

Databricks では最大 20 個を推奨しています。

Databricks Runtime にインストールされているものとは異なるバージョンの Shiny パッケージを使用できますか?

はい。 「R パッケージのバージョンを修正する」を参照してください。

Shiny サーバーに発行して Azure Databricks のデータにアクセスできる Shiny アプリケーションを開発するにはどうすればよいですか?

Azure Databricks での開発とテスト時には、SparkR または sparklyr を使用してデータには当然アクセスできますが、Shiny アプリケーションがスタンドアロンのホスティング サービスに発行された後は、Azure Databricks 上のデータとテーブルに直接アクセスできません。

アプリケーションが Azure Databricks の外部で機能するようにするには、データへのアクセス方法を書き直す必要があります。 次のようないくつかのオプションがあります。

  • JDBC/ODBC を使用して、Azure Databricks クラスターにクエリを送信する。
  • Databricks Connect を使用する。
  • オブジェクト ストレージのデータに直接アクセスする。

Databricks では、Azure Databricks ソリューション チームと協力して既存のデータと分析アーキテクチャに最適な方法を見つけることを推奨しています。

Azure Databricks ノートブック内で Shiny アプリケーションを開発することはできますか?

はい、Azure Databricks ノートブック内で Shiny アプリケーションを開発することはできます。

ホストされている RStudio Server で開発した Shiny アプリケーションを保存するにはどうすればよいですか?

DBFS にアプリケーション コードを保存するか、バージョン管理にコードをチェックインできます。