Speed issue after saving with Serversideoutput

I’m in trouble.
I want to display the serverside output data smoothly with hover data, but it is difficult.
Is it possible to improve the speed?

Dash - 個人 - Microsoft​ Edge 2023-01-02 04-31-24

import numpy as np
import plotly.graph_objs as go
from dash_extensions.enrich import Output,Input,dash,DashProxy,ServersideOutput,ServersideOutputTransform,State
from dash import html, dcc
app = DashProxy(prevent_initial_callbacks = True,transforms = [ServersideOutputTransform()])
app.layout = html.Div([
                       dcc.Graph(id="my_graph",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       dcc.Graph(id="curve",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       html.Div(dcc.Store(id="da")),
                       html.Div(dcc.Store(id="d2")),
                       html.Div(dcc.Store(id="da3")),
                       dcc.Input (id = 'inputsubmit',type = 'text'),
                       html.Button ('aaa',id='a',n_clicks = 0)
                       ])

@app.callback(
    Output("curve", "figure"),
    [Input("my_graph", "hoverData")],
    [Input("da","data")],
    [Input("d2","data")])
def update_graph(hoverData,da,da2):
      if hoverData is None:
          return dash.no_update
      else:
          selectx = hoverData['points'][0]['x']
          selecty = hoverData['points'][0]['y']
          rt = da[selecty,selectx,:]
          rt2=da2[selecty,selectx,:]
          fig2 = go.Figure()
          fig2.add_trace(go.Scattergl(
                x = rt,
                y = rt2))
          return  fig2
@app.callback (
    Output ('my_graph','figure'),
    [Input ('da3','data')]
)
def image(da3):
    fig3=go.Figure ()
    fig3.add_trace (go.Heatmapgl (z = da3))
    fig3.update_layout (yaxis = dict (scaleanchor = 'x'),plot_bgcolor = 'rgba(0,0,0,0)')
    return fig3
#
@app.callback(
    ServersideOutput('da', 'data'),
    ServersideOutput('d2', 'data'),
    ServersideOutput('da3', 'data'),
    Input("a", "n_clicks"),
    [State("inputsubmit", "value")]
)
def button(n_clicks,inputsubmit):
    if inputsubmit is None:
        return dash.no_update
    else:
        arr=np.random.rand (256,256,512)
        arr2=np.random.rand (256,256,512)
        heatmap=np.random.rand (256,256)
        return arr,arr2,heatmap

app.run_server(debug=True,port=9080)

Hi @Osako, is the reason for using the serverside output the size of your data to store or could you store it in a clientside store?

I use serversideoutput because the amount of data is large.
The client side cannot also be used due to the large capacity.

Hello @Osako,

Since you are using dash-extensions, you could push an update to the figure’s data via OperatorTransform.

This should decrease the transferred data and render the figure on the clientside. This may boost the speed at which the secondary figure responds.

I tried it and it didn’t go much faster. (only x is operatortransform) Also, I don’t know how to change two elements with operatortransform. I think the server side input is taking a long time.

The cache shouldn’t be taking a long time.

You can see the breakdown in how the callbacks are working by using some of this:

I may not be explaining well.
For example, this operation is much faster when using global variables. My opinion is that I think it’s taking a long time to retrieve the cache from the server.

What type of cache are you using?

Default FileSystemStore.

@Emil,

Any recommendations to speed up this process?

With global variables, the data are loaded into memory on app initialization, so this approach will always be faster than a caching solution. Hence, a degradation in performance is expected when switching from global variables to a ServersideOutput. You can speed up the FileSystemStore by using a faster disk, e.g. a good NVMe SSD. Another approach would be to use an in-memory cache such as Redis.

thank you.
How should Redis be set up?
Is this correct?
For some reason it doesn’t speed up

import numpy as np
import plotly.graph_objs as go
from dash_extensions.enrich import Output,Input,dash,DashProxy,ServersideOutput,ServersideOutputTransform,State,RedisStore,FileSystemStore
from dash import html, dcc

my_backend = RedisStore()
app = DashProxy(prevent_initial_callbacks = True,transforms = [ServersideOutputTransform()])
app.layout = html.Div([
                       dcc.Graph(id="my_graph",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       dcc.Graph(id="curve",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       html.Div(dcc.Store(id="da")),
                       html.Div(dcc.Store(id="d2")),
                       html.Div(dcc.Store(id="da3")),
                       dcc.Input (id = 'inputsubmit',type = 'text'),
                       html.Button ('aaa',id='a',n_clicks = 0)
                       ])

@app.callback(
    Output("curve", "figure"),
    [Input("my_graph", "hoverData")],
    [Input("da","data")],
    [Input("d2","data")])
def update_graph(hoverData,da,da2):
      if hoverData is None:
          return dash.no_update
      else:
          selectx = hoverData['points'][0]['x']
          selecty = hoverData['points'][0]['y']
          rt = da[selecty,selectx,:]
          rt2=da2[selecty,selectx,:]
          fig2=go.Figure ()
          fig2.add_trace(go.Scattergl (
            x = rt,
            y = rt2,
            mode = 'markers',
            name = 'approach')
        )
          return fig2
@app.callback (
    Output ('my_graph','figure'),
    [Input ('da3','data')]
)
def image(da3):
    fig3=go.Figure ()
    fig3.add_trace (go.Heatmapgl (z = da3))
    fig3.update_layout (yaxis = dict (scaleanchor = 'x'),plot_bgcolor = 'rgba(0,0,0,0)')
    return fig3
#
@app.callback(
    ServersideOutput('da', 'data',backend = my_backend),
    ServersideOutput('d2', 'data',backend = my_backend),
    ServersideOutput('da3', 'data',backend = my_backend),
    Input("a", "n_clicks"),
    [State("inputsubmit", "value")]
)
def button(n_clicks,inputsubmit):
    if inputsubmit is None:
        return dash.no_update
    else:
        arr=np.random.rand (256,256,512)
        arr2=np.random.rand (256,256,512)
        heatmap=np.random.rand (256,256)
        return arr,arr2,heatmap

app.run_server(debug=True,port=9080)

Almost, you need to pass the store to the transform via the backend kwarg, i.e. something like

from dash_extensions.enrich import ServersideOutputTransform, RedisStore

rs = RedisStore(host="localhost", port=6379, password=None, db=0)
ServersideOutputTransform(backend=rs)

No speed improvement at all. Is this correct?
Is it because my PC has low memory?

import numpy as np
import plotly.graph_objs as go
from dash_extensions.enrich import Output,Input,dash,DashProxy,ServersideOutput,ServersideOutputTransform,State,RedisStore,FileSystemStore
from dash import html, dcc

rs = RedisStore(host="localhost", port=6379, password=None, db=0)
app = DashProxy(prevent_initial_callbacks = True,transforms = [ServersideOutputTransform(backend=rs)])
app.layout = html.Div([
                       dcc.Graph(id="my_graph",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       dcc.Graph(id="curve",
                                 style = {'width':500,
                                          'height':500,
                                          'display':"inline-block",
                                          'verticalAlign':'top'},
                                 ),
                       html.Div(dcc.Store(id="da")),
                       html.Div(dcc.Store(id="d2")),
                       html.Div(dcc.Store(id="da3")),
                       dcc.Input (id = 'inputsubmit',type = 'text'),
                       html.Button ('aaa',id='a',n_clicks = 0)
                       ])

@app.callback(
    Output("curve", "figure"),
    [Input("my_graph", "hoverData")],
    [Input("da","data")],
    [Input("d2","data")])
def update_graph(hoverData,da,da2):
      if hoverData is None:
          return dash.no_update
      else:
          selectx = hoverData['points'][0]['x']
          selecty = hoverData['points'][0]['y']
          rt = da[selecty,selectx,:]
          rt2=da2[selecty,selectx,:]
          fig2=go.Figure ()
          fig2.add_trace(go.Scattergl (
            x = rt,
            y = rt2,
            mode = 'markers',
            name = 'approach')
        )
          return fig2
@app.callback (
    Output ('my_graph','figure'),
    [Input ('da3','data')]
)
def image(da3):
    fig3=go.Figure ()
    fig3.add_trace (go.Heatmapgl (z = da3))
    fig3.update_layout (yaxis = dict (scaleanchor = 'x'),plot_bgcolor = 'rgba(0,0,0,0)')
    return fig3
#
@app.callback(
    ServersideOutput('da', 'data',backend = rs),
    ServersideOutput('d2', 'data',backend = rs),
    ServersideOutput('da3', 'data',backend = rs),
    Input("a", "n_clicks"),
    [State("inputsubmit", "value")]
)
def button(n_clicks,inputsubmit):
    if inputsubmit is None:
        return dash.no_update
    else:
        arr=np.random.rand (256,256,512)
        arr2=np.random.rand (256,256,512)
        heatmap=np.random.rand (256,256)
        return arr,arr2,heatmap

app.run_server(debug=True,port=9080)
1 Like

I don’t think that low memory should affect the performance (as long as the data fits in memory of course), but it might depend on how you setup your Redis server.

Where can I look to understand it?
I have Redis installed.

I just tested you example on my laptop. With normal Output, the browser receives a payload > 1 GB and crashes. With ServersideOutput, the graphs load in < 2s. With global variables it takes about 1s. What is your performance requirement?

I would expect the Redis option to be somewhere in-between. I would recommend the official Redis docs for more details on configuration. However, with the amount of data you have, I am concerned that you might run out of memory at some point.

1 Like

I understand.
Thank you for your kindness.

https://docs.redis.com/latest/ri/memory-optimizations/

Might be interesting to see if there’s anything to improve

@Osako A different way to improve performance would be to use the BlockingCallbackTransform to prevent multiple callbacks to accumulate (the hover callback is triggered very often as-is). Just add the transform, and mark the hover callback as blocking,

app = DashProxy(prevent_initial_callbacks=True, transforms=[ServersideOutputTransform(), BlockingCallbackTransform()])

...

@app.callback(
    Output("curve", "figure"),
    [Input("my_graph", "hoverData")],
    [Input("da", "data")],
    [Input("d2", "data")], blocking=True)
def update_graph(hoverData, da, da2):

With this change, it runs a lot smoother on my laptop. Here is the full code for completeness,

import numpy as np
import plotly.graph_objs as go
from dash_extensions.enrich import Output, Input, dash, DashProxy, ServersideOutput, ServersideOutputTransform, State, \
    RedisStore, FileSystemStore, BlockingCallbackTransform
from dash import html, dcc

rs = FileSystemStore()
app = DashProxy(prevent_initial_callbacks=True, transforms=[ServersideOutputTransform(backend=rs), BlockingCallbackTransform()])
app.layout = html.Div([
    dcc.Graph(id="my_graph",
              style={'width': 500,
                     'height': 500,
                     'display': "inline-block",
                     'verticalAlign': 'top'},
              ),
    dcc.Graph(id="curve",
              style={'width': 500,
                     'height': 500,
                     'display': "inline-block",
                     'verticalAlign': 'top'},
              ),
    html.Div(dcc.Store(id="da")),
    html.Div(dcc.Store(id="d2")),
    html.Div(dcc.Store(id="da3")),
    dcc.Input(id='inputsubmit', type='text'),
    html.Button('aaa', id='a', n_clicks=0)
])


@app.callback(
    Output("curve", "figure"),
    [Input("my_graph", "hoverData")],
    [Input("da", "data")],
    [Input("d2", "data")], blocking=True)
def update_graph(hoverData, da, da2):
    if hoverData is None:
        return dash.no_update
    else:
        selectx = hoverData['points'][0]['x']
        selecty = hoverData['points'][0]['y']
        rt = da[selecty, selectx, :]
        rt2 = da2[selecty, selectx, :]
        fig2 = go.Figure()
        fig2.add_trace(go.Scattergl(
            x=rt,
            y=rt2,
            mode='markers',
            name='approach')
        )
        return fig2


@app.callback(
    Output('my_graph', 'figure'),
    [Input('da3', 'data')]
)
def image(da3):
    fig3 = go.Figure()
    fig3.add_trace(go.Heatmapgl(z=da3))
    fig3.update_layout(yaxis=dict(scaleanchor='x'), plot_bgcolor='rgba(0,0,0,0)')
    return fig3


#
@app.callback(
    ServersideOutput('da', 'data', backend=rs),
    ServersideOutput('d2', 'data', backend=rs),
    ServersideOutput('da3', 'data', backend=rs),
    Input("a", "n_clicks"),
    [State("inputsubmit", "value")]
)
def button(n_clicks, inputsubmit):
    if inputsubmit is None:
        return dash.no_update
    else:
        arr = np.random.rand(256, 256, 512)
        arr2 = np.random.rand(256, 256, 512)
        heatmap = np.random.rand(256, 256)
        return arr, arr2, heatmap


app.run_server(debug=True, port=9080)

EDIT: I think I would go for this solution combined with the FileSystemCache to avoid the additional complexity of spinning up a Redis server :slight_smile:

1 Like