<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Archives des Experienced level | dash-resources.com</title>
	<atom:link href="https://dash-resources.com/category/experienced-level/feed/" rel="self" type="application/rss+xml" />
	<link>https://dash-resources.com/category/experienced-level/</link>
	<description>Learn to build interactive web applications with Python and Dash plotly</description>
	<lastBuildDate>Thu, 20 Feb 2025 22:57:16 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://dash-resources.com/wp-content/uploads/2024/12/cropped-dash-logo-favicon-512-32x32.png</url>
	<title>Archives des Experienced level | dash-resources.com</title>
	<link>https://dash-resources.com/category/experienced-level/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Dash app callback performance: a real-world debugging example</title>
		<link>https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 20 Feb 2025 10:07:28 +0000</pubDate>
				<category><![CDATA[Experienced level]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=756</guid>

					<description><![CDATA[<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to display some information, that I found annoying.</p>



<p>In this blog post, I’ll walk you through how I quickly enhanced my app’s performance by analyzing Dash callback execution, which hopefully give you some ideas for your debugging too. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#context">Context</a>
      </li>
      <li>
        <a href="#so-how-to-debug-this">So how to debug this?</a>
      </li>
      <li>
        <a href="#resolution">Resolution</a>
      </li>
      <li>
        <a href="#result">Result</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<h2 id='context'  id="boomdevs_1" class="wp-block-heading" >Context</h2>



<p>My dash application is essentially a real estate map which displays information when clicking on map points.</p>



<p>The click that displayed information felt instant when running locally, but when running in production, it had about a one-second delay. I also noticed that the more elements I displayed on my map, the worse the performance became.</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see a 1-2sec delay between the click and the display of the information on the sidebar.</figcaption></figure>



<p>My production app and local app differ in some ways, and I could imagine at least two reasons:</p>



<ul class="wp-block-list">
<li><strong>Maybe callbacks are slower</strong> due to design differences between production and local development. For example, the cache used is not the same: I use Flask cache disk locally, which is faster, while my app uses a Redis backend to cache results. Redis introduces additional overhead due to the external request it must handle, compared to just RAM or disk caching on the same device.</li>



<li><strong>Maybe the request itself is slower</strong> due to upload/download speed limits. When deployed, my app is on a remote web server, whereas locally it runs directly on my computer. Obviously, this introduces extra overhead. It’s much faster for my browser to communicate with a local app on the same device as with a remote app over the internet.</li>
</ul>



<h2 id='so-how-to-debug-this'  id="boomdevs_2" class="wp-block-heading" >So how to debug this?</h2>



<p>To make it short, I tried to replicate the issue locally (which is always a good way to debug things). I started by inspecting the triggered callbacks after a click on the map. You might know that all Dash callbacks translate to HTTP requests, so I used the developer tools and network tab to analyze what was happening.</p>



<p class="callout">If you don’t know that callbacks translate to HTTP request, I recommend you to download my guide “<strong>Master Dash Callbacks: Architecture &amp; Best Practices (Free PDF)</strong>” <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> find it on the sidebar &#8212;-&gt; or below this article on mobile.</p>



<p>To simulate a “distant” request from my computer to a remote server, I set the network throttling to “Good 3G” to mimic a 3G mobile connection. <strong>This was key in debugging.</strong> Without this, everything appeared snappy in my browser.</p>



<p>I discovered that eight callbacks were triggered (directly or indirectly) after clicking on the map. Here is the debugging process in video:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see no delay in debug mode. But when simulating network throttling, the delay became visible again.</figcaption></figure>



<p>As you can see in the video, <strong>it</strong> <strong>literally took around 14 seconds</strong> (!!) to complete the eight callbacks after clicking on the map.</p>



<p>Two requests appeared to be blocking the others, leading to huge delays: between 1 and 8 seconds for some callbacks. This is significant.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png" alt="Illustration of the network debugging tools and the corresponding http requests to the callbacks." class="wp-image-759" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1320x323.png 1320w" sizes="(max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the first request, I quickly realized that the bottleneck was the time spent “Sending” the data: 4.05s. The “Waiting” time (when the browser waits for the server response) was only 64ms. So, the callback processing was not the problem—the data upload was.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png" alt="Illustration of the timings for the first request." class="wp-image-760" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1320x323.png 1320w" sizes="(max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the second request, I saw that it was also blocked for 4.05s, meaning it was simply not triggered immediately.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="2048" height="557" src="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png" alt="Illustration of the timings for the second request." class="wp-image-761" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-300x82.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1024x279.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-768x209.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1536x418.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1320x359.png 1320w" sizes="(max-width: 2048px) 100vw, 2048px" /></figure>



<p>The third request showed the same problem as the first one: too much time spent sending data, with only 108ms processing on the server side and no delay in receiving the result.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="555" src="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png" alt="Illustration of the timings for the third request." class="wp-image-762" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-300x81.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1024x278.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-768x208.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1536x416.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1320x358.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>At this point, it was clear that two callbacks were problematic. To identify which callbacks these HTTP requests were triggering, I inspected the request details. The names of outputs, inputs, and states are visible as part of the information Dash sends with each callback:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1048" src="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png" alt="Illustration of debugging the inputs/states/outputs of a callback request" class="wp-image-763" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-300x154.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1024x524.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-768x393.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1536x786.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1320x675.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>I realized that the full map data (<code>map_figure_data</code>) was being sent. For my app, that meant all the points and their hover information (descriptions, images, etc.).</p>



<p>Even though it wasn’t a huge amount of data, it posed two problems:</p>



<ul class="wp-block-list">
<li>Upload speeds are usually slower than download speeds. For slow connections like mobile networks, this delay is unbearable.</li>



<li>The issue worsens as the number of data points on my map increases (!)</li>
</ul>



<p>The second callback had the same problem. This time, the map data was an input. The goal was to see how I could eliminate this <code>map_figure_data</code> dependency.</p>



<h2 id='resolution'  id="boomdevs_3" class="wp-block-heading" >Resolution</h2>



<p>I then looked at my two callbacks. Here’s what they originally looked like.</p>



<p><strong>callback 1 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        State("map_figure_data", "data"),  # the problematic state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids, figure_data
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>While reviewing the callback code, I realized that <code>figure_data</code> wasn’t even used. So, I removed it and it solved the problem for this callback… (yes, I guess that these things happens!).</p>



<p><strong>callback 1 &#8211; after (removed useless state!)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        # just removed the useless state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>Now, looking at the second callback.</p>



<p><strong>callback 2 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "data"),  # this is problematic
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    fig_data, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>Again, <code>fig_data</code> wasn’t used. However, I still needed <code>map_figure_data</code> to trigger updates, so I changed the input property to <code>modified_timestamp</code>. This enables the callback to be triggered when <code>map_figure_data</code> changes (its timestamp is updated) while not transferring the whole data.</p>



<p><strong>callback 2 &#8211; after (changed to modified_timestamp)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "modified_timestamp"),  # this is efficient
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    map_update_timestamp, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>And that did the job perfectly!</p>



<p><strong>But what if I actually had to keep the full figure data</strong> ? I would have had two choices:</p>



<ul class="wp-block-list">
<li>Create another input, that is lighter than the figure data</li>



<li>Process the figure data in a clientside callback to avoid a roundtrip to the server.</li>
</ul>



<h2 id='result'  id="boomdevs_4" class="wp-block-heading" >Result</h2>



<p>These changes literally took 20 seconds to implement. The result was instantly better, even with the &#8220;Good 3G&#8221; mobile throttling still enabled:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4"></video><figcaption class="wp-element-caption">Illustration: no more delay on the debug app, even with 3G network throttling. </figcaption></figure>



<p>After making these adjustments, I ensured there were no other instances of <code>map_figure_data</code> being used as either an Input or Output.</p>



<h2 id='conclusion'  id="boomdevs_5" class="wp-block-heading" >Conclusion</h2>



<p>When debugging this issue, I thought it would be an interesting use case to share, because: </p>



<ul class="wp-block-list">
<li>Using <strong>developer tools</strong> is a powerful way to debug slow callbacks and sluggish Dash apps. Keep in mind that all inputs and states trigger data uploads, which in some cases can be extremely slow.</li>



<li>Using <strong>throttling mode</strong> allows you to simulate production conditions or at least non-optimal conditions, unlike debugging locally.</li>
</ul>



<p>I hope you found this interesting. Such performance explanations and debugging techniques are part of my <a href="https://dash-resources.com/dash-plotly-course/"><strong>Dash course</strong></a>, where you can find more <strong>detailed explanations and videos</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>If you have questions, feel free to ask them <a href="https://community.plotly.com/t/dash-app-callback-performance-a-real-world-debugging-example/90653">here</a> on Plotly&#8217;s forum.</p>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4" length="378456" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4" length="1929505" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4" length="785372" type="video/mp4" />

			</item>
	</channel>
</rss>
