Performance Q&A guide

I’m going to be checking this thread periodically over the next week to answer questions on performance best practices, inspired by (among other people who have asked for this):

This will probably get turned into a performance section in the manual, but I figure the forum is a good place to kick things off. I have to run for now but will chime in later today with some general tips + answers to blueback09’s questions above.

52 Likes

So some overall guidelines. Note that these kinds of guidelines definitely have a sell-by date, since we’re always trying to optimize slow stuff, but here are some of the big performance levers:

  • The more data a search fetches, the longer it takes. That probably sounds pretty basic, but we routinely see pages that fetch thousands of data items on page load. Or, we sometimes see searches that only fetch a few things – but each thing has KBs or MBs of text data stored in its field.

  • Bubble tries to avoid fetching more than it needs to. So, if you have a search in a repeating group, we only fetch enough data to fill the visible cells in the repeating group, plus some extra as buffer in case the user scrolls. However, there are things that force Bubble to load more data. If you do an additional operation to the results of a search such as filtering the results, we have to load enough data to find stuff that passes that filter… so if you have a 10 cell repeating group, we might have to load 100 items to find 10 that match. If you then get that filtered searches’ :count, we have to load the entire list, because without looking at each item we can’t know the number that passes the filter. Same with sorting – if you have a sort directly in the search, we can do it in the database, but if you do a search, then another operation on the search, then sort the result of that, we have to load all the data to see what sorts lowest.

  • So the general rule is: do as much sorting / filtering in the original search as possible. The more you’re able to shrink the search to begin with, before you start doing various :filtered, :unique, :count, merged with, etc., the less data we’ll need to load. (This is one of the areas that we’re regularly changing, though… we’re trying to move as many operations as possible to the database. For instance, when you do a search and then a :count, with no intervening operations, we don’t need to load all the data to count it – we do that in the database, so it’s much faster to do a search then :count than to do a search, then :unique, then :count )

  • Another thing to keep in mind is that when you use dynamic data such as the result of a search as one of the constraints in a search, we can’t start the second search until we finish the first search, because we don’t know what the correct constraint should be. If the first search only returns a small amount of data, that’s not a big deal, but if it returns a lot of data, and you chain multiple searches together like that, you can create extremely slow pages, because we have to evaluate it all one-at-a-time, instead of in parallel (which is what we normally try to do).

So those are some general guidelines. Think about what data we’d need to fetch to get the answer, and minimize the data. Think about what order things have to happen in, and try to make it so that things can happen in parallel.

Now, to answer some of @blueback09’s specific questions:

“For example, if I have a dozen elements that need to run the same search, is it better to have one hidden element run the search and then have the visible elements reference that hidden element’s value?”

Generally, no. Bubble is smart enough that if two things do the exact same search, it’ll be able to combine them automatically.

"Is there a difference between one action that changes a dozen fields and a dozen actions that change one field? "

Yes. The first one is much faster. We execute each action all at once, then wait for it to finish before executing the next action, so if you have 12 actions, we have to wait 12 times, whereas if you have one action that touches a bunch of fields, we do that all at once.

“Does it matter if a long list of changes are hard-coded into one or more workflows vs iterated over by an API workflow?”

I’m not 100% sure I know what you mean by “hard-coded” here, but I think what you’re asking is, is it better to have a big “change list of things” in one workflow, or kick off a one API workflow per item?

The general rule is, change list of things is fast + good for relatively small (say, 100 items or less) lists. API workflows are a little slower to get through, but they are more scalable and reliable for large lists… if you have a change list of things operating on too big a list, it might time out / fail to finish and break the workflow.

Anyway, everyone please feel free to chime in with follow up or additional questions! I’ll try to check this thread a couple more times over the next week :slight_smile:

65 Likes

If I have a bunch of image thumbnails on a page and the source is a full sized image does that mean the page has to wait for all of the full sized images to download? I would assume that’s the case at least for something like a group background that might have to resize so it would need the whole image from the word go.

Is there a way to keep my users logged in for more than a day? It’s frustrating to have to log back in every day.

Is there any performance trade off between different ways of doing the same thing? For example, I can display an image in an image element, in the background of a group, or in an html element.

“Native” apps work by swapping out groups on the same page. Is there a way to ensure the groups never blink? Like when you see a group you’re not supposed to see for a second?

Thank you for the thorough explanation @josh! I will be heeding your advice from her on out until the sell-by date hits :slight_smile:

I have overcome this by marking ALL groups as hidden on page load, and making very clear in the conditional tab when each group is allowed to load. Since doing this I no longer experience this blinking effect.

4 Likes

This post is a very nice initiative :slight_smile:

Regarding searches : let’s say that I have items that have a validity flag (yes/no field).
When I do searches, let’s assume I always do it with the constrain that the result must have this validity flag set to “yes”.
In order to increase my app speed, should I regularly clean my database to remove all items that have the validity flag set to “no” in order to speed up these searches ?
In a more general case, can we expect a bubble app to get slower and slower because the database is growing, even if we are not displaying more items (we are just keeping old data, but only display fresh one) ?
And when does this becomes critical (i.e., how fast is a search on server side before transmitting the data depending on the number of items in the database) ?

On my websites, I can still be logged after several days, so I don’t know what you did wrong, but I think it could be browser-related (some privacy settings?)

1 Like

Hi Josh
Thanks for the tips regarding data. What about the timing related to navigation from page to page?
On regular websites, the transition from page to page it’s under 2 seconds.

Regards

1 Like

It log off 24h when you signed with Google.

How does the number of

  • elements
  • pages
  • used plugins
  • unused plugins

affect performance?

Are some elements more intensive than others?
What about not optimised plugins? Is there some Quality Assurance review?

2 Likes

Hi,

What is the ideal way to display text using nested thing referencing.

e.g.
With the assumption that - Thing(i) is field in Thing(i+1)

Consider a repeating group with content type as Thing 1:
And a text box showing the following:
This Thing1’s Thing2’s Thing3’s Thing4’s Thing5’s… Thing(n)'s email

Now the database has to traverse n things to find email - what is the ideal way to call something nested so deep?

I have considered saving the email field in Thing1, Thing2 … and Thing(n) - but this eats up a lot of workflows. And if the field is editable it becomes challenging to keep track of all the Things where the field needs to be updated.

Appreciate any help!

2 Likes

How about store the reference to the final thing in each other thing?
Thing1’s Thing(n)'s email
Thing2’s Thing(n)'s email

Are you building a Cat in the Hat?

5 Likes

There’s a sequence or hierarchy in Bubble. Can you describe it, or at least a simplified version? What order does everything happen in? Which things can happen in parallel if they’re setup correctly?

I imagine it’s something like this
page > group > element > expression

If we understand the actual sequence Bubble uses, in terms of which thing happens first and which thing happens last, then we should be able to put data and actions into optimal places. For example, Gaby pointed out that I should move the key piece of data defining a whole page to the page itself, rather than a dropdown on the page. That way all of the elements can start loading their own data after the page loads, rather than having to wait for the page to load, so the dropdown can load and do its search first.

1 Like

Next batch of questions (skipping things that someone else already gave a good answer to in the thread):

“If I have a bunch of image thumbnails on a page and the source is a full sized image does that mean the page has to wait for all of the full sized images to download?”

Generally we’re good about resizing things on the server. But you can check for yourself: if you look at the image url in the browser, you’ll see something like: “?w=380.94643874044306&h=243.1&fit=crop” at the end of the url if we’re resizing it on the backend. And you can copy / paste the url into a new tab and see how big it is.

“Is there any performance trade off between different ways of doing the same thing? For example, I can display an image in an image element, in the background of a group, or in an html element.”

Mostly not. See above answer about resizing – the biggest cost of displaying an image is downloading the full thing, so you can check the image url to see what size we are actually downloading it at.

“In order to increase my app speed, should I regularly clean my database to remove all items that have the validity flag set to “no” in order to speed up these searches ?
In a more general case, can we expect a bubble app to get slower and slower because the database is growing, even if we are not displaying more items (we are just keeping old data, but only display fresh one) ?
And when does this becomes critical (i.e., how fast is a search on server side before transmitting the data depending on the number of items in the database) ?”

Generally, having more stuff in the database that isn’t loaded in a search shouldn’t slow things down too much. We automatically build database indexes that let us quickly find things that match a search criteria. The very first time you do a particular kind of search, we don’t have an index yet, so that will be noticeably slower (perhaps very slow) on big databases, but we auto-detect that happening and build an index, so that in maybe 15 minutes to an hour, that search is fast. So, I wouldn’t worry too much about cleaning up old data. That said, if you know you never need a piece of data again, it doesn’t hurt to delete it.

“Thanks for the tips regarding data. What about the timing related to navigation from page to page?”

There are two things that control navigation speed:

-Page load time of the page you are navigating to. The biggest factor here is the amount of data the page relies on, which is why I opened this conversation with optimizing data use. It also matters how many elements are on the page: a page with only a few elements will load faster than a page with hundreds of elements.

-Whether there are any ongoing operations on the page you are navigating from. When you use a “change page” action, we don’t actually change the page until we are sure that it’s safe, so if there are other workflows running, we’ll wait til they are far enough along that we know it is okay to change. This just matters for the change page action, though… if you navigate via a link, the only thing that matters is the page you are going to.

"How does the number of

elements
pages
used plugins
unused plugins
affect performance?

Are some elements more intensive than others?"

An app with lots of pages with few elements on each one is going to be faster in general than an app with a few pages with lots of elements. When you navigate to a page, we have to load all the elements on the page, and all the custom definitions needed for custom elements on that page. But, we don’t need to load any other pages in the app, and we don’t need to load custom definitions that aren’t used on that page, so we don’t send that data.

For the most part, the number of elements has a bigger affect on speed than what type they are: ie, there’s no real difference between a group and a text in terms of page speed. The one major exception here is repeating groups: repeating groups basically multiply the load speed by the number of cells they display. If you have a repeating group inside a repeating group, and both have a lot of visible cells, that can get very slow.

Plugins sometimes affect page load speed even if you aren’t using them. We often have to include the javascript to run the plugin as part of the page’s javascript whether or not you actually need that plugin, so my recommendation is if you know you aren’t using a plugin, to uninstall it.

"Consider a repeating group with content type as Thing 1:
And a text box showing the following:
This Thing1’s Thing2’s Thing3’s Thing4’s Thing5’s… Thing(n)'s email

Now the database has to traverse n things to find email - what is the ideal way to call something nested so deep?"

Yeah, so as you correctly guess, that’s going to be slow… We have to load thing1, then load thing2, then load thing3. The best way is to just avoid that kind of chain. If you’re having a hard time figuring out how to design your app without doing it, you might want to start a seperate forum thread and solicit opinions.

"There’s a sequence or hierarchy in Bubble. Can you describe it, or at least a simplified version? What order does everything happen in? Which things can happen in parallel if they’re setup correctly?

I imagine it’s something like this
page > group > element > expression"

So, the first thing we do is we draw all the visible elements on the page, and then in parallel, start calculating the properties for each visible element (including fetching any data from the database). It takes much, much longer to fetch data than it does to render the element, so it doesn’t make too much of a difference if you put a something in a group, vs putting it directly on the page; they’ll both be rendered within the same couple miliseconds, and then they’ll send off any database requests they need to display their properties.

We don’t start rendering / fetching data for invisible elements until they actually get displayed. (With the exception that if you reference an invisible group’s data source, that forces us to kick it off). Generally this is a good trade-off, because the more data we fetch and elements we load on page load, the longer it takes for the page to load, so it’s better to take a little longer to display an element when you do make it visible than pay that cost upfront when the user can’t even see it. That said, in certain situations you might want to force data to load by having an element be transparent or covered up rather than actually invisible. (We do render covered-up elements because it’s too hard to tell whether or not the user can see them).

The other big thing that controls hierarchy in initial page rendering is when one element’s properties reference another element’s properties. This basically works like you would expect it to… if you’re displaying a search in a repeating group, and the search has a constraint that uses a group’s current item, obviously we can’t do the search until we’ve loaded the groups’ item.

21 Likes

Thank you for all these answers, they are really useful !

It might be I silly question but : how does the use of predefined style affects the page load time ?
For instance if I have 100 stylized text elements on a page that all look identical.
Does the page load faster if I define a style that is used by all these elements, rather than specifying the style parameters for each one of them ?

5 Likes

Not a silly question at all – it does load faster, yes (and this was one of our initial motivations in building styles, beyond making life easier for users)

8 Likes

Bubble apps are limited by the number of workflow runs they use. Or is it the number of times a change is made to the database? If a workflow doesn’t modify the database, like if it hides an element on the page, does it not count against the limit?

Is there a performance difference between navigating to another page in the app using a workflow action vs a link element vs a hyperlink in an html element? Like, maybe if it’s a workflow the page can load only what it needs, but if it’s a normal hyperlink the page has to load from scratch? Anything like that?

Can we profile a page to find out which part of the process is causing the biggest delay? For example, this is a page with a repeating group. Some of the elements populate with data right away, but the same element in another cell doesn’t populate. The rest of the data loads eventually.

2 Likes

Most useful topic ever :slight_smile:

5 Likes

Super helpful thread, Josh, thanks for initiating this!

2 Likes

Nice work @josh

In my case for example I have this data types:

  • Users
  • Companies
  • Invoices

So when the user access to his companies invoices he might have about 500 invoices that has to be loaded in a repeating group. In the repeating group there are 6 cells.

The constraints on the “do a search for …” will be, show invoices of:

  • Company = current page company
  • Status = yes

Someone suggested to use in the company a field of a “list of invoices” but the invoices could be more than 10.000.

So what I did is not to load at page load all the invoices, but just 25. The problem is that when the user clicks of see “all invoices” it could take about 3-5 minutes to load them all, right now users have about 300 invoices per company.

I know this is going to be so much better once you get the database performance finished but is there something we could do to improve the load performance too?

Thanks a lot @josh for the time you´re spending with this threat.

2 Likes

Couple more answers:

Bubble apps are limited by the number of workflow runs they use. Or is it the number of times a change is made to the database? If a workflow doesn’t modify the database, like if it hides an element on the page, does it not count against the limit?

Yes, we don’t count workflows against the limit that we can run entirely in the web browser, without talking to the Bubble servers. It’s more than just modifying data that requires the servers, though: logging the user in or out, hitting an external API, etc., all need that. Basically, the only things that don’t need the server are actions that work directly with elements on the page.

Is there a performance difference between navigating to another page in the app using a workflow action vs a link element vs a hyperlink in an html element? Like, maybe if it’s a workflow the page can load only what it needs, but if it’s a normal hyperlink the page has to load from scratch? Anything like that?

A link element will be as fast as a hyperlink in an html element. Changing it via a workflow action won’t be faster, but it might be a little slower – we wait to change the page if there are other workflows that haven’t finished saving data.

Can we profile a page to find out which part of the process is causing the biggest delay? For example, this is a page with a repeating group. Some of the elements populate with data right away, but the same element in another cell doesn’t populate. The rest of the data loads eventually.

Profiling web pages is a complicated subject – can’t really give a full tutorial here. Google Chrome comes with a lot of powerful tools for seeing how things load… there’s an intro here: Chrome DevTools  |  Chrome for Developers

In the gif you posted, it looks like its the images that’s slowing things down. If you use the network tab of the Chrome dev tools, you can see a) how big each image is, b) when it starts loading, c) how long it takes to load

The constraints on the “do a search for …” will be, show invoices of:

Company = current page company
Status = yes
Someone suggested to use in the company a field of a “list of invoices” but the invoices could be more than 10.000.

So what I did is not to load at page load all the invoices, but just 25. The problem is that when the user clicks of see “all invoices” it could take about 3-5 minutes to load them all, right now users have about 300 invoices per company.

So what you’re doing is a good way of structuring it – if you have that many invoices per company, it’s better to have a “Company” field on each invoice which you filter on… that’s generally pretty efficient. And then only displaying the first few invoices in a repeating group. For “see all invoices”, rather than try to display 300 things on a page (which is going to be a little slow no matter what tool you use), I’d suggest trying to use a repeating group in infinite scroll mode, so the user can just keep scrolling down til they find the one they want. Or, maybe provide them with better search tools to narrow the list down.

6 Likes

To take the same example, let’s assumeI have companies that can create invoices and I only want to show the invoices of a specific company.
So what you are saying is that it is actually more efficient to have a “Company” field in each invoice and then do a “search for…” on the invoices with a constrain on the company, rather than having a “Invoices” field in “Company” that is a list of invocies, and directly display this list without passing by a search ?
Intuitively, I would have think the opposite (using a list you don’t have to perform a search on all invoices, you already know them)

2 Likes