[Performance] Slow serialisation of callback responses
Les mainteneurs répondent en général sous 2 jours
Évaluation
- Difficulté
- 3/5
- Temps estimé
- 1-2 jours
- Accessibilité débutants
- 45/100
Piste de recherche
Start with to_json in dash's _utils module, which hands callback responses to plotly's to_json_plotly, and the orjson fallback path in plotly's JSON io module. Run the issue's timing snippet first to confirm the gap between the figure-only and figure-plus-Div cases. Done means a default= hook keeps the Div case close to the figure-only timing while the JSON output stays identical.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Labels: performance
Describe your context
dash 4.1.0
plotly 6.7.0
orjson 3.11.9
pandas 2.3.3
numpy 2.3.5
- Python 3.13. Server-side only; not browser related.
Describe the problem
Dash serialises every callback response with dash._utils.to_json, which hands the work to plotly's to_json_plotly. When orjson is installed, plotly first passes the whole response to orjson.dumps in a single call. If any value in the response is not orjson-native, orjson raises TypeError and plotly discards the attempt. Plotly then converts the entire payload in pure Python with clean_to_json_compatible and calls orjson.dumps a second time on the result.
Dash passes callback return values to to_json unconverted. A response therefore takes the slow path when it contains any of these:
- a component, e.g.
children=html.Div(...) - a
go.Figureobject, e.g.return fig - a value such as
pd.Timestamp
Because the walk covers the whole response, it also visits every element of the figure's data, even though orjson could have serialised that data natively. Numeric numpy arrays are passed through the walk without being visited element by element, so most of the cost comes from figures built from plain Python lists of floats or datetimes.
The snippet below compares two callback responses. Both contain the same large figure of 10 traces x 20k points.
- figure only: the figure is the only output. Every value is orjson-native, so orjson serialises it in one fast pass.
- figure + html.Div: the same figure plus one extra output, a tiny
html.Div("ok"). This mirrors a common callback that updates a graph and a status message.
The Div adds only a few bytes, so both responses should take about the same time to serialise. In practice the second is over 10x slower. The Div is not orjson-native, so it sends the whole response down the fallback path. All 200k figure values are walked in Python just to convert that one Div.
import time
import numpy as np
import plotly.graph_objects as go
import plotly.io
from dash import html
from dash._utils import to_json
plotly.io.json.config.default_engine = "orjson"
points = np.random.randn(20_000).tolist() # plain Python list, as when data comes from a list or dataframe column
figure = go.Figure([go.Scatter(y=points) for _ in range(10)]).to_plotly_json()
for label, extra in [("figure only", {}), ("figure + html.Div", {"status": {"children": html.Div("ok")}})]:
response = {"multi": True, "response": {"graph": {"figure": figure}, **extra}}
start = time.perf_counter()
to_json(response)
print(f"{label}: {(time.perf_counter() - start) * 1000:.0f} ms")
Typical output: figure only: 7-10 ms and figure + html.Div: 100-118 ms.
The fallback cost depends on how the figure data was built. The table below repeats the same two responses for four common data types. Each row uses the same figure size of 10 traces x 20k points. Timings are the best of 3 runs of to_json. The right-hand column is the same response with the one tiny Div added.
figure data figure only + html.Div
y = numpy array 5 ms 6 ms
y = list of float 6 ms 93 ms
x = pd.DatetimeIndex 20 ms 99 ms
x = list of datetime 11 ms 456 ms
One small component makes the whole response roughly 5-40x slower to serialise. The numpy row is the exception, since the walk does not visit numpy arrays element by element. Returning a go.Figure object has the same effect as the Div. So does a pd.Timestamp anywhere in the response.
Expected behavior
Adding a small non-native value to a response should only add the cost of converting that one value. A tiny component returned alongside a large figure should therefore serialise in about the same time as the figure on its own.
Possible solution
Pass a default= callback to orjson.dumps. orjson calls it only for the values it cannot serialise, so the callback can convert just those values (for example with to_plotly_json()) while orjson handles everything else natively. This removes both the retry and the Python walk over the whole payload. In a quick prototype, responses containing a component or a pd.Timestamp serialised as fast as fully native ones, with identical JSON output.
The change could go in dash._utils.to_json or in plotly's to_json_plotly, which would also speed up fig.to_json().
A related note: without orjson installed, plotly falls back to the much slower stdlib json engine, and orjson is not a required dependency of dash. This may be worth mentioning in the performance docs.
- Langage dominant
- Python
- Étoiles
- 24.4k
- Forks
- 2.3k
- Merge moyen
- 3 j 2 h
- PR mergées (30 j)
- 28
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de plotly/dash
-
P2 plotly-internal size: 1 task
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 2 jours
-
Extract graph schema validation into properly named testPeut-être pris @Nice6042 l’a pris il y a 62 jours. Ouvertegood first issue P3 size: 1 task
Difficulté 2/5 1-3 heures Accessibilité débutants 68/100
plotly/dash#3735 · 3 commentaires ·
Les mainteneurs répondent en général sous 2 jours
-
[BUG] dcc.Dropdown: hidden focus-target input is focusable inside aria-hidden and can overflow its wrapperPeut-être pris @KoolADE85 l’a pris il y a 9 jours. Ouvertebug P2 size: 1
Difficulté 4/5 3-5 jours Accessibilité débutants 56/100
plotly/dash#4042 · 3 commentaires · 1 personne assignée ·
Les mainteneurs répondent en général sous 2 jours
-
Linter/Type Checking ReplacementOuverteP3 plotly-internal size: 10+ task
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 30/100
plotly/dash#4040 · 3 commentaires ·
Les mainteneurs répondent en général sous 2 jours
-
Drop Python 3.9 SupportOuverteP2 plotly-internal size: 5 task
Difficulté 4/5 3-5 jours Accessibilité débutants 35/100
plotly/dash#4038 · 2 commentaires ·
Les mainteneurs répondent en général sous 2 jours
Toutes les issues de plotly/dash
Issues similaires
-
md/asciidoc/csv: an explicit `encoding="utf-8"` keeps a leading BOM, the auto-detect path drops itOuverte
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
docling-project/docling#4846 ·
Les mainteneurs répondent en général sous 1 jour
-
area/sessions comp/dashboard comp/desktop comp/gateway comp/tui duplicate P2 sweeper:risk-session-state type/bug
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
NousResearch/hermes-agent#138914 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
bot-triaged module: activation checkpointing module: inductor oncall: pt2 triaged
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
Les mainteneurs répondent en général sous 1 jour
-
[oblt-aw][autodoc] Update PR Actions Detective E2E prerequisites and fix dashboard-format table typoOuvertedocumentation
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
elastic/oblt-aw#2382 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 74/100
Les mainteneurs répondent en général sous 1 jour