Tips for speeding up dcc.Tabs on dash

Hello! I’ve really enjoyed using Dash, and have deployed an app to heroku.

https://ftacv-simulation.herokuapp.com/

It’s much slower than on my localhost, and I’m trying to find reasons why. I’ve switched to the webgl renderer, and have reduced the amount of data being plotted in some instances. My issue is now that switching between different graphs using dcc.Tabs is very slow relative to the app in my local host. I’m storing the output of various simulations in a dcc.Store element, but accessing them is still slow. Is there a way to speed this up, potentially by caching the data in the users browser?

If you are storing a lot of data in a Store, the data will be transferred between the client and the server for each callback, which might slow down the app. If this is the problem, you might experience an improvement by caching the data server side. See e.g. this example,

I have been facing the same problem! My solution was not to use dcc.store at all.

When you set on your localbrowser the throttle of the Developer Tools to (for example) DSL speed, you will face the same slowness on your localhost. That what made it easy for me to debug locally.

image

Interesting - can I ask what your alternative solution was?

Hi @FTACV, in case you missed, there is a recent post by @Emil that could be interesting to you: Show and Tell - Server Side Caching

Hi @Emil and @fohrloop - I have now tried to implement this. The data I pass into store using cached_callback() (which is in the form of a dictionary that can be passed to dcc.Graph()) is empty when read by the plotting function. I’m not sure why this is - is it because it has to be in the form of a pandas dataframe?

No, the data can be in any format. Could you share some code? Otherwise it’s hard to tell what goes wrong.

I’m struggling to reproduce the error in a simple example, and I don’t want to post the whole code as its enormous, but the source of the problem seems to be the error :

TypeError: object supporting the buffer API required

The structure of my code is

      Apply_changes(button)
                ↓
           Store(data)  -> Save_data(button) -> Saved_data_store(data)
                ↓                                     ↓
  Combine with saved data and plot(figure) <----------                                                      

There are two cc.cached_callback() functions, one for the current data store, and one for the saved data store. If you remove the ability to save plots (i.e. the second cached_callback() function), then you don’t get the error and everything works fine.

Apologies that this is kind of garbled! I will have another go at recreating the error with a minimal example.

Ah, I might have an idea about what goes wrong then. A cached callback calculates the key for the cache as an md5 hash of the inputs. In your case, the input itself is a cached callback, which might cause issues if the input data does not support the md5 hash calculation,

What type of data are you using?

Just tried to fix it by putting the whole thing in one callback, but of course you still need store as an input to the function…

The data is in the form of a dictionary for the graphing options - so a dictionary of strings, where the x and y data are stored as numpy arrays (casting them to list() has no effect unfortunately). I tried moving everything to a JSON format, but it seems that also isn’t supported by the buffer API.

I just tried to reproduce your error, but without any luck. I seem to be able to cache dicts, list and so on. Could you post a small MWE demonstrating the error? Then i’ll try to figure out a fix :slight_smile:

Hi Emil, I’ve edited the example from your show and tell, I hope you don’t mind. The only change is in the cached_callback()

import dash
import dash_core_components as dcc
import dash_html_components as html
import time
import plotly.express as px

from dash.dependencies import Output, Input, State
from flask_caching.backends import FileSystemCache
from dash_extensions.callback import CallbackCache, Trigger

# Create app.
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
    html.Button("Query data", id="btn"), dcc.Dropdown(id="dd"), dcc.Graph(id="graph"),
    dcc.Loading(dcc.Store(id="store"), fullscreen=True, type="dot")
])
# Create (server side) cache. Works with any flask caching backend.
cc = CallbackCache(cache=FileSystemCache(cache_dir="cache"))


@cc.cached_callback(Output("store", "data"), [Input("btn", "n_clicks")], [State("store", "data")])  # Trigger is like Input, but excluded from args
def query_data(n_clicks, prev_store):
    #also want to do something with previous data in store, so pass it in as a state
    time.sleep(1)  # sleep to emulate a database call / a long calculation
    return px.data.gapminder()


@cc.callback(Output("dd", "options"), [Input("store", "data")])
def update_dd(df):
    return [{"label": column, "value": column} for column in df["year"]]


@cc.callback(Output("graph", "figure"), [Input("store", "data"), Input("dd", "value")])
def update_graph(df, value):
    df = df.query("year == {}".format(value))
    return px.sunburst(df, path=['continent', 'country'], values='pop', color='lifeExp', hover_data=['iso_alpha'])


# This call registers the callbacks on the application.
cc.register(app)

if __name__ == '__main__':
    app.run_server(debug=True)

Thanks for the example! Now it’s clear to me what happens. By adding the store as State, the first time the query_data method is called, there is no data in the cache to load, which was causing an error.

Is there a way to populate the cache the first time the app loads?