With the recent changes of 0.38 and 0.39 adding callback context and multiple outputs, I was wondering:
performance wise, would it be better to move inputs/outputs to fewer callbacks? Especially if the overall app is huge and small callbacks in repeating app sections get multiplied. Or is there no difference from the point of the dash backend?
It depends on how many workers you are using (i.e. gunicorn app:server --workers 4
).
Each worker can process a single callback at once. So, if you have many callbacks that do independent computations, then it can make sense to keep them separate as they will be processed “in parallel” by the different workers.
However, if they all share computations, then it can make sense to keep them in the same callback to prevent excessive computation.
What you would probably want to avoid is combining expensive, independent computations into a single callback, for example:
@app.callback([Output('id-1', ...), Output('id-2', ...)], [Input(...)])
def update_outputs(input1, input2, ...):
output1 = time_consuming_computation_for_output_1()
output2 = time_consuming_computation_for_output_2()
output3 = time_consuming_computation_for_output_3()
return [output1, output2, output3]
In this case, if each output is forced to wait for the other computations to finish before the update gets sent to the front end. If these were separate callbacks, then they could be handled in parallel by separate workers (provided that enough workers were available).
To better understand this, it can be helpful to make a simple example with time.sleep
to simulate long-running tasks and open up your browser’s network console timeline view and watch the request and response times.
Hope that’s helpful!
Very true! The other big consideration for now is when callbacks fire on page-load.
I have test page with 180 callbacks, the page-load performance lengthy even when the server and client are on the same machine. The slowness is related to just the 180 HTTP calls overhead.
However the callbacks all have the same logic, so collapsing them together with the callback context and multi-output is fairly trivial and something I’m experimenting with now. My hope is this kind provide some performance relief until page-load callbacks are filtered.
Yes it is, thank you! In many callbacks, computations would be limited to first checking which input in the UI section (there would be some physical sense in grouping) triggered the callback and then only performing that action, so technically computational stuff would be limited.
I just noticed that the overall app seems to run faster with less callbacks, even though at most a single callback would be clicked. Not sure why, though.
The worker part would be really interesting, but in this case the computations concern an actualy physical device, so there can’t be different states of the app simultaneously. I have no clue how to fix that issue. In general, I’d like the app to be more performative, or being able to outsource specific requests to always be handled on one worker (e.g. Intervals) and the rest on another worker. I’m guessing this is non-trivial though.
I’ve seen some solutions on gunicorn with redis or some cache or memsharing, but I have absolutely no clue how to use these. It’s supposedly easy.