在通过 kubernetes 部署的闪亮应用程序上使用 `server=FALSE` 时使用 `DT:replaceData()` 的替代方法
Posted
技术标签:
【中文标题】在通过 kubernetes 部署的闪亮应用程序上使用 `server=FALSE` 时使用 `DT:replaceData()` 的替代方法【英文标题】:Alternatives to using `DT:replaceData()` when `server=FALSE` on shiny application deployed via kubernetes 【发布时间】:2022-01-02 21:46:15 【问题描述】:出于各种原因,我希望能够在使用客户端处理时使用代理数据表和 replaceData,即DT::renderDataTable(..., server = FALSE)
。
上下文
我有一个闪亮的应用程序/仪表板,它与数据库通信并向用户呈现信息。用户可以在应用程序中填写一个表格,该表格将被添加到数据库中,然后闪亮的应用程序通过查询数据库来更新数据以获取新信息。
目前正在通过 kubernetes 使用 LoadBalancer
部署该应用程序,目的是根据需要使用多个副本来扩展应用程序。该应用程序没有通过 Shinyproxy 运行。
注意事项
目前,当应用程序由单个副本(进程)运行时,应用程序将表现得非常好并且能够使用server=TRUE
。但是,当我增加要运行的进程/副本的数量时,除非在 renderDataTable
中指定了 server=FALSE
,否则数据将无法呈现给用户。由于目前未知的原因,但我怀疑这可能是由于会话不粘在 IP 上
虽然代码在server = TRUE
时能够正常运行,但如果我想允许多个用户应用它们都不能共享一个进程,因为一旦建立多个连接,应用程序将变得非常慢。因此,我可能需要使用server=FALSE
,这样每个用户都能够以非常重要的功能细节为代价查看数据(replaceData
停止工作)。应用程序的产品所有者坚持认为这种行为保持不变,因为存在的数据通常很大,并且需要一些列排序和分页才能找到您想要查看的信息。并且在提交表单时,如果我不使用replaceData
并从头开始重建表格,则用户之前的表格状态将丢失。
所以虽然我可以拆除数据表并在 observeEvent
中重新生成它
observeEvent(input$button,
...
output$table = renderDataTable(DT::datatable(df(), selection = 'single', callback =
JS("$.fn.dataTable.ext.errMode = 'none';")), server = FALSE)
...
)
这将提供一种解决方案,即使它会相应地更新表,也会产生不利的行为。
可重复的示例
这将创建一个带有按钮和表格的应用程序。选择表格上的一行,然后单击按钮。预期的行为是表格在所选行上更新为“new_content”。这只会在server=TRUE
时起作用,server=FALSE
时不会发生任何事情。
library(shiny)
library(DT)
data(iris)
server <- function(input, output, session)
iris$new_col = ''
df = reactive(iris)
output$table = renderDataTable(
DT::datatable(df(), selection = 'single',
callback = JS("$.fn.dataTable.ext.errMode = 'none';")), server = FALSE) # When TRUE code works fine,,,
proxy = dataTableProxy('table')
observeEvent(input$button,
# This line would be replacing the write to a db
iris[input$table_rows_selected, 'new_col'] <- 'changed'
# This line would be replacing the query to the db to reflect changes the user (and potentially other users have made between loading the data previously.
df <- reactive(iris)
proxy %>% replaceData(df(), rownames = TRUE, resetPaging = FALSE)
)
ui <- fluidPage(
actionButton('button', 'Press Me'),
DT::DTOutput('table')
)
shinyApp(ui, server)
我对 SO 进行了相当广泛的搜索,这是我能找到的最接近的问题:DT Editing in Shiny application with client-side processing (server = F) throws JSON Error 然而,这实际上并没有得到回答,而是提供了“它只是不起作用”的答案。
kubernetes.yaml(如果您是向导,请仅查看)
我将 yaml 文件包括在内,以防有一些 kubernetes boffins 知道如何通过一些巧妙的技巧专门解决上述问题。所描述的问题可能源于在副本之间交换会话,因此数据被错误传达,但老实说,我在 kubernetes 方面不是最好的......如果是这种情况,那么我将能够在闪亮的应用程序中使用 server=TRUE 那么这个也能解决问题。
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-appname
spec:
replicas: 5
selector:
matchLabels:
app: appname
template:
metadata:
labels:
app: appname
spec:
containers:
- name: appname
securityContext:
privileged: false
image: appname:latest
ports:
- name: http
containerPort: 3838
---
apiVersion: v1
kind: Service
metadata:
name: servive-appname
spec:
ports:
- name: http
port: 3838
protocol: TCP
targetPort: 3838
selector:
app: appname
type: LoadBalancer
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-appname
annotations:
nginx.org/websocket-services: "service-appname"
spec:
tls:
- hosts:
- appname.url.com
rules:
- host: appname.url.com
http:
paths:
- path: /
backend:
serviceName: service-appname
servicePort: 3838
【问题讨论】:
我认为你无法克服这个限制。请参阅?dataTableProxy
中的注释:reloadData() only works for tables in the server-side processing mode
。 replaceData
使用 reloadData
。我宁愿关注为什么当多个用户连接时使用server = TRUE
的版本会失败。
@ismirsehregal 我知道文档说什么 - 我的问题仍然是要求替代 replaceData。
当然,我是这么认为的。我不知道它是否符合上下文,但是您是否可以使用editable datatable 进行用户输入并在用户完成后保存到数据库?这可以通过客户端表实现。
【参考方案1】:
这是一种客户端方法,基于@jpdugo17 的答案和@TJGorrie 的初始示例,使用stateSave
选项来维护重新渲染时的表状态。 selectPage
和 updateSearch
可以与 dataTableProxy
一起使用 - input$table_state$order
的状态需要作为选项传递:
library(shiny)
library(DT)
data(iris)
iris$new_col <- ''
server <- function(input, output, session)
DF = reactiveValues(iris = iris)
output$table <- DT::renderDataTable(expr =
if (is.null(isolate(input$table_state)))
DT::datatable(
DF$iris,
selection = 'single',
callback = JS("$.fn.dataTable.ext.errMode = 'none';"),
options = list(stateSave = TRUE)
)
else
# print(isolate(input$table_state$order))
DT::datatable(
DF$iris,
selection = 'single',
callback = JS("$.fn.dataTable.ext.errMode = 'none';"),
options = list(
stateSave = TRUE,
order = isolate(input$table_state$order),
paging = TRUE,
pageLength = isolate(input$table_state$length)
)
)
, server = FALSE)
proxy <- dataTableProxy('table')
observeEvent(input$button,
DF$iris[input$table_rows_selected, c('new_col')] <- 'changed!'
)
observeEvent(DF$iris,
updateSearch(proxy, keywords = list(global = input$table_state$search$search, columns = NULL)) # see input$table_state$columns if needed
selectPage(proxy, page = input$table_state$start/input$table_state$length+1)
, ignoreInit = TRUE, priority = -1)
ui <- fluidPage(
actionButton('button', 'Press Me'),
DT::DTOutput('table')
)
shinyApp(ui, server)
这是related article。
【讨论】:
selectPage
的使用非常巧妙!但是,似乎 updateSearch() 应该比 SelectPage()
先执行,因为在过滤表时页面会发生变化。请看一下。你可以在第 42 行找到这个
这个解决方案效果很好。谢谢。【参考方案2】:
我们可以尝试结合input$table_rows_selected
的信息使用reactiveValues
。根据要求,server
参数等于 FALSE
。
library(shiny)
library(DT)
data(iris)
server <- function(input, output, session)
iris$new_col = ''
df = reactiveValues(iris = iris)
output$table = renderDataTable(
DT::datatable(df$iris, selection = 'single',
callback = JS("$.fn.dataTable.ext.errMode = 'none';")), server = FALSE) # When TRUE code works fine,,,
observeEvent(input$button,
# This line would be replacing the write to a db
df$iris[input$table_rows_selected, c('new_col')] <- 'changed!'
)
ui <- fluidPage(
actionButton('button', 'Press Me'),
DT::DTOutput('table')
)
shinyApp(ui, server)
【讨论】:
然而,这会重新渲染表格并默认重置列排序和分页位置,@TJGorrie 提到这是不希望的。【参考方案3】:如果您使用的是 kubernetes/ingress-nginx,则可以使用 cookie 实现会话亲和性。
https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
但是从您的 yaml 中,您使用的是 nginx.org 的 kubernetes-ingress,那么您可以阅读
https://github.com/nginxinc/kubernetes-ingress/blob/master/examples/session-persistence/README.md
但它仅在 NGINX Plus 中受支持。
【讨论】:
以上是关于在通过 kubernetes 部署的闪亮应用程序上使用 `server=FALSE` 时使用 `DT:replaceData()` 的替代方法的主要内容,如果未能解决你的问题,请参考以下文章
AWS Elastic Beanstalk 上的闪亮服务器与 Docker