About improving Dash app performance

Hi everyone,

As some of you may know, I’ve been developing my first dash application [https://www.buscafes.com.ar]. It consists on an interactive map of coffee places from my city (Buenos Aires) with the possibility to filter the map by certain features obtained by google places API.

The app is almost finished, there are some ux/ui things I need to improve, but the core of the app is finished. I’m currently deploying it with Azure using the first paid plan, the basic one.
The thing is, I’ve been analyzing the performance of my app with devtools like lighthouse, webpagetest and gtmetrix but there are some stuff I don’t know/understand how to solve. Let me show you some of the information provided by webpagetest on mobile performance:

As you can see, the part that takes most of the time te finish is the layout, which makes sense because I have to render many points of the map, but there are other steps like the bWLwgP.css located on my github assets foulder and the montserrat font (step 16) that delays a little bit the app.
Some other relevant information about app performance:



And let me print some part of my code:

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import dash_leaflet as dl
import pandas as pd
from dash_extensions.javascript import assign
#import dash_bootstrap_components as dbc
import numpy as np
from flask_compress import Compress
import json
import requests

# Crear la aplicación Dash

external_stylesheets = [
    #dbc.themes.BOOTSTRAP,
    'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'
]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets,title="Buscafes")
...
# URL of the JSON file
url = url
# Fetch the content from the URL
response = requests.get(url)
# Load the content into a Python dictionary
geojson_data = response.json() 
...
#Then, the part of app.layout that render everything
dl.Map(
        id='map',
        style={'width': '100%', 'height': '100vh', 'max-height': '100vh'},
        center=[-34.598, -58.436], 
        zoomControl=False,
        bounds=[[lat_min, lon_min], [lat_max, lon_max]],
        zoom=12, 
        children=[
            dl.TileLayer(id="base-layer", url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"),
            dl.LocateControl(locateOptions={'enableHighAccuracy': True,'setView': False}, position='topright', showPopup=False),
            dl.ZoomControl(position='topright'),
            dl.GeoJSON(
                id="geojson-layer",
                options=dict(
                    pointToLayer=assign("""
                    function(feature, latlng){
                        let iconUrl = feature.properties.iconUrl;  // Usa el URL del ícono definido en Python
                        
                        return L.marker(latlng, {
                            icon: L.icon({
                                iconUrl: iconUrl,
                                iconSize: [18, 27],
                                iconAnchor: [12, 23],
                                popupAnchor: [1, -34],
                                shadowSize: [0, 0]
                            })
                        }).bindPopup(feature.properties.popupContent)
                          .bindTooltip(feature.properties.tooltipContent, {
                              className: 'marker-tooltip'
                          });
                    }
                    """)
                ),
                zoomToBoundsOnClick=True,
            )
...
#then it comes the callbacks, in which the most relevant one (the one that applies the filters is a clientside one)

I’m having a hard time trying to solve those issues. Is there any part of my code that can be improved in order to reduce the time of the app execution? Maybe even paying a better server, idk, or doing some changes in the libraries that I’m importing, or trying to place the .css and google fonts in another place, or changes on the app.layout code or on the external codes, .etc.

I’d really appreciate your help.

Hi @Kriko

To me, your app loads fast.
I mean, on my Firefox the app is fully loaded and ready to use after 4-5sec.

If you had many callbacks, I would recommend you to set gunicorn threads number and/or worker number to 2, because that really has an impact on how snappy a Dash app can have, especially if you have a lot of callbacks that can be computed asynchronously.

But that’s not your case, as you said the initial layout is taking some time.

The initial layout HTTP request : I timed it to be around 1500-2500ms.
As comparison, my dash app loads the layout under 100ms.
So If I were you, I would comment/uncomment anything in the app.layout to know what causes the initial layout to be slow. Maybe some parts of the layout can also be initialized with callbacks ?

Also I could see that the initial layout HTTP response is about ~5MB. Which can be a lot to load on slow internet connections (not noticeable on mine, for example). There are some redundant information in the intial layout (e.g. the map GeoFeatures contains the style like font size, font family, icons url… that seem to be often the same?

I am sure you don’t need to pay a better server, these simple apps should run on inexpensive servers :slight_smile:

I think it also loads fairly well, Think a custom loading screen prior to the render like:

would add a lot.

Outside of that like the rating setup but not sure if you’ve seen:

just thought it could be a nice addition specifically for the areas highlighted red:

Hi Pip! Glad that it was fast for you. In computer it works well, but it takes a little more time in the cellphone.

That loading screen is good! My sister (graphic designer) said she was goind to one for me haha, still waiting.

About the stars I used to have what you mention but took it out for less rendering, but I wasn’t aware of the dash mantine components at all, it could work.

And I want to ask you about my code sth since you have your webpage, do you believe it’s important the amount of “import” libraries or the dash.Dash settings? or they don’t change very much your app performance?

Thanks again!

Good point sprit, maybe initialize part of the layout with the callback can be a good option, I’m gonna try that. I’ve tried just rendering 2 markers on the map, and it was really fast, so the main issue lies on rendering the total amount of markers.

What I didn’t get was the redundant information you talked about. How would that be? I’m defining the url of the font in external_stylesheets and then applying it in the .css file. Maybe I’m missing sth?

And about the callbacks, I just have 3, but only one of them is actually important.

Thanks for your help

Imported modules are loaded into memory (RAM) when you launch the app/server. When the server starts listening to new connections (e.g. new people loading the page), this is already loaded. It has therefore no impact (unless you import new modules in callbacks).

Open “network” in Web Development tools of your browser. Go look at the response from dash_layout → Here an extraact of what I have :

{"props":{"children":[{"props":{"id":"clientside-store-data","data":{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.4258769,-34.5858]},"properties":{"Nombre":"Cafe De La Fundacion","Rating":3.8,"Cantidad Reviews":4,"Sitio Web":"Sin datos","Dirección":"Jorge Luis Borges 2020","iconUrl":"https:\u002f\u002fjsonbuscafe.blob.core.windows.net\u002fcontbuscafe\u002fmarkbeige.svg","popupContent":"\n            \u003cp style='font-family: Montserrat; font-size: 16px; text-decoration: underline;'\u003e\u003cstrong\u003eCafe De La Fundacion\u003c\u002fstrong\u003e\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eRating:\u003c\u002fstrong\u003e 3.8\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eReviews:\u003c\u002fstrong\u003e 4\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eSitio Web:\u003c\u002fstrong\u003e Sin datos\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eDirección:\u003c\u002fstrong\u003e Jorge Luis Borges 2020\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eHorarios:\u003c\u002fstrong\u003e\u003cbr\u003eDomingo: 12:00 - 16:00\u003cbr\u003eLunes: 09:00 - 20:30\u003cbr\u003eMartes: 10:00 - 22:00\u003cbr\u003eMiercoles: 10:00 - 22:00\u003cbr\u003eJueves: 10:00 - 22:00\u003cbr\u003eViernes: 10:00 - 22:00\u003cbr\u003eSabado: 10:00 - 19:00\u003c\u002fp\u003e\n            ","tooltipContent":"\n            \u003cp class='nombre'\u003eCafe De La Fundacion\u003c\u002fp\u003e\n            \u003cp\u003e\u003cspan class='bold-text'\u003eRating: \u003c\u002fspan\u003e3.8\u003c\u002fp\u003e\n            \u003cp\u003e\u003cspan class='bold-text'\u003eDirección: \u003c\u002fspan\u003eJorge Luis Borges 2020\u003c\u002fp\u003e\n            ","Delivery":0,"Comer en lugar":1,"Almuerzo":1,"Cena":0,"Brunch":1,"Vino":0,"Espacio afuera":0,"Sirve postre":1,"Musica en vivo":0,"Desayuno":1,"Reservable":0,"Tiene takeaway":0,"Barrio":"Palermo","Domingo_open":"12:00","Domingo_close":"16:00","Lunes_open":"09:00","Lunes_close":"20:30","Martes_open":"10:00","Martes_close":"22:00","Miercoles_open":"10:00","Miercoles_close":"22:00","Jueves_open":"10:00","Jueves_close":"22:00","Viernes_open":"10:00","Viernes_close":"22:00","Sabado_open":"10:00","Sabado_close":"19:00"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.4241482,-34.5864365]},"properties":{"Nombre":"Kajue Café","Rating":4.7,"Cantidad Reviews":2067,"Sitio Web":"\u003ca href=\"https:\u002f\u002fwww.instagram.com\u002fkajuecafe\u002f\" target=\"_blank\"\u003ehttps:\u002f\u002fwww.instagram.com\u002fkajuecafe\u002f\u003c\u002fa\u003e","Dirección":"Guatemala 4665","iconUrl":"https:\u002f\u002fjsonbuscafe.blob.core.windows.net\u002fcontbuscafe\u002fmarkverde.svg","popupContent":"\n            \u003cp style='font-family: Montserrat; font-size: 16px; text-decoration: underline;'\u003e\u003cstrong\u003eKajue Café\u003c\u002fstrong\u003e\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eRating:\u003c\u002fstrong\u003e 4.7\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eReviews:\u003c\u002fstrong\u003e 2067\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eSitio Web:\u003c\u002fstrong\u003e \u003ca href=\"https:\u002f\u002fwww.instagram.com\u002fkajuecafe\u002f\" target=\"_blank\"\u003ehttps:\u002f\u002fwww.instagram.com\u002fkajuecafe\u002f\u003c\u002fa\u003e\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eDirección:\u003c\u002fstrong\u003e Guatemala 4665\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 14px;'\u003e\u003cstrong\u003eHorarios:\u003c\u002fstrong\u003e\u003cbr\u003eDomingo: 09:00 - 20:00\u003cbr\u003eLunes: 09:00 - 20:00\u003cbr\u003eMartes: 09:00 - 20:00\u003cbr\u003eMiercoles: 09:00 - 20:00\u003cbr\u003eJueves: 09:00 - 20:00\u003cbr\u003eViernes: 09:00 - 20:00\u003cbr\u003eSabado: 09:00 - 20:00\u003c\u002fp\u003e\n            ","tooltipContent":"\n            \u003cp class='nombre'\u003eKajue Café\u003c\u002fp\u003e\n            \u003cp\u003e\u003cspan class='bold-text'\u003eRating: \u003c\u002fspan\u003e4.7\u003c\u002fp\u003e\n            \u003cp\u003e\u003cspan class='bold-text'\u003eDirección: \u003c\u002fspan\u003eGuatemala 4665\u003c\u002fp\u003e\n            ","Delivery":1,"Comer en lugar":1,"Almuerzo":1,"Cena":1,"Brunch":1,"Vino":1,"Espacio afuera":1,"Sirve postre":1,"Musica en vivo":0,"Desayuno":1,"Reservable":0,"Tiene takeaway":1,"Barrio":"Palermo","Domingo_open":"09:00","Domingo_close":"20:00","Lunes_open":"09:00","Lunes_close":"20:00","Martes_open":"09:00","Martes_close":"20:00","Miercoles_open":"09:00","Miercoles_close":"20:00","Jueves_open":"09:00","Jueves_close":"20:00","Viernes_open":"09:00","Viernes_close":"20:00","Sabado_open":"09:00","Sabado_close":"20:00"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-58.43242480000001,-34.6221714]},"properties":{"Nombre":"SUKHA VEGANERIE","Rating":4.8,"Cantidad Reviews":58,"Sitio Web":"\u003ca href=\"https:\u002f\u002finstagram.com\u002fsukha.veganerie?igshid=YmMyMTA2M2Y=\" target=\"_blank\"\u003ehttps:\u002f\u002finstagram.com\u002fsukha.veganerie?igshid=YmMyMTA2M2Y=\u003c\u002fa\u003e","Dirección":"Viel 467","iconUrl":"https:\u002f\u002fjsonbuscafe.blob.core.windows.net\u002fcontbuscafe\u002fmarkverde.svg","popupContent":"\n            \u003cp style='font-family: Montserrat; font-size: 16px; text-decoration: underline;'\u003e\u003cstrong\u003eSUKHA VEGANERIE\u003c\u002fstrong\u003e\u003c\u002fp\u003e\n            \u003cp style='font-family: Montserrat; font-size: 

As you can see, you have styling here, URL for icons, … These are redundant information (styling could be set once in CSS file). Maybe all data is not necessary either → maybe you only display parts / maybe you could load the additional parts with a callback.

Typically, you could not scale your app on entire Argentina. That would take too much time to retrieve all the data in one shot. You would have to create a callback that retrieve stores data when the user clicks on a point :slight_smile: It depends if you expect to have more points in the future.

1 Like

Yes, depending on people’s reviews I’ll see if I scale it to more provinces and cities. In thos cases I believe your option is a must.
I’ll check that redundant information about styling.

Thanks!

1 Like

With https://pip-install-python.com I have many imports and I don’t see any issue with it. I do build the individual pip components to have the cdn’s within the package rather than calling on a url to bring them in. Thats the only thing I’d check for, is making sure the packages you are using are self contained and not dependent on an outside cdn or script to work as that ping to the other server for which its hosted on could cause longer load times.

1 Like