Performance Q&A guide

Believe me @keith, when I say many, I mean many. It would take a while to explain my app, but as explained, it will have many “subscriber” users who themselves will have multiple users, and that is where some of the complexity comes in as every thing I search or view has to reference the subscriber user value.

Yes, I am just starting to implement a custom state system as you describe, but it is complex as there are my design has over 40 reusable elements it has to be passed into, and I’ll have to change a gazillion searches and views I have already written to use the new state, and I want to be sure I need to do this before I embark on the task.

The app seems okay now, but I know that as I get hundreds of users, each database call will cost me money in terms of how much I pay for capacity boost per user subscription I bring in.

This is ultimately a financial optimisation for my business! :slight_smile:

Hey @antony,

Well, I get what you’re saying. There’s a bit of subtlety in terms of what one should be worried about in terms of performance impact versus what one should not. My point was that I do not think that most of what you are thinking about is probably worth optimizing in any special way. I think you are asking about basic stuff. Basic stuff that Bubble is well-designed to handle.

First, I get the sense that your general question is a general one about what Bubble calls “things” (instances of database objects) and how references to things work. And, further, are there good, better, and bad ways to reference things? And I think your second question is kind of along the lines of, “Are there things I should be worried about? And again are there really bad ways to do things?”

Some ways to think about this:

  1. If a page object has a type (and similarly if a group has a type and a data source has been assigned to that typed group) and is displaying an object of that type, that thing is downloaded to the browser. And pretty much any and all fields on that data object that are allowed by privacy rules that pass get downloaded to the browser. You can see this when you view source on such a page.

Bubble does this so that we have convenient access to any and all fields we might need. Also, presumably, they are downloaded to the client so that they are cached and, for example, on a page of type User, if we reference the User’s First Name a zillion times, we are not doing a zillion lookups to get the First Name. It just happens once(…ish… more on this in a minute).

I wouldn’t worry about this. See Josh’s explanations in the thread above where he answers a bunch of questions related to this and similar topics around Jun '17. Keep in mind: We’re building apps here. There’s going to a bunch of conditional text. There are going to be lots of dynamic pages that are simply “views” into different blobs of data. This is something Bubble excels at and a lot of thought has gone into optimizing for this use case.

  1. Very similarly, it sounds like you have pages or groups that might be of type “Company” (or “Client” or “Business” or something similar). A Company might have a name and a location and a currency preference. And as I mentioned in my previous reply, such a page might reference something like Company’s Accounting Currency a lot.

If I understand correctly, in a page or a page that has a group like this, referencing the field of a “preloaded” thing field as [Current Page's / Group's] Company's Accounting Currency has no real additional overhead as this data is already downloaded to the client and it’s just there for our use in a suitably optimized way (basically, the same as if it were a local variable / custom state).

Of course for this to work, we must have retrieved that thing either because the page URL is domain/my-page-of-Company-type/[CompanyUniqueID] or by feeding the Group’s data source (either by reference or by the results of some Search that resolves to a thing of type Company).

Note that the latter case is analagous/equivalent to having a custom state somewhere on the page of type Company that we have initialized in a similar way. (That Groups have a type/data source just saves us the step of creating the custom state… we are simply using the built-in variable state already available to us in the Group. Using a Group or custom state or a Repeating Group for this has one additional advantage as the Thing we snag can then be an arbitrary list of things, rather than just a single thing or a predefined list of things as in the typed page case.)

The point is that in any of these methods, we are essentially grabbing a “handle” to a Thing and then can reference that thing without incurring additional overhead.

2a. We should note that not all Searches are equally performant and some ways of searching are more efficient than others. It’s sort of easiest to think about this by way of example, which I do in #3, below.

2b. It’s worth noting that, regardless of whether we are dealing with a page with no type or page with any arbitrary type, we always have access to one very specific thing of type User without having to do a search: “Current User”. We always have a handle to Current User and Current User's field[s].

  1. inefficient ways to reference a thing: If we need repeated access to a thing and multiple fields on that thing, we should be getting that thing via the methods described in #2. But Bubble is very flexible and we could, in fact, create cases where we are repeatedly reference some thing’s field(s) in an extremely inefficient way. Here’s an example:

We might have a built a page without types or be using groups without types and think we’re being all smarty-pants because we can pretty much always get to a thing we want by doing some sort of clever search. But such a search might not be particularly performant and, further, we might inadvertently be forcing Bubble to do way more work than it needs to.

Imagine we are on a page representing an Invoice and for this we need to reference an “Accounting Currency” preference. We might do something like this – we might get a Company’s Accounting Currency (let’s assume that Accounting Currency is not even an object itself but is just a field of type text that contains a single text token like “$”, “£” or “¥”) like so:

Do a search for Companys:filtered (by condition Advanced: This Company's Name is Current Page Invoice's BillingCompanyName):first item's Accounting Currency

This is all kinds of stupid of course, but I’m sure people do stuff like this. This is a lot of heavy lifting just to return a single-character string. Further, having now gotten this (crazy, but it would work) way of returning dollar sign vs pound sign vs yen sign, we might copy that expression into all sorts of places in our page (like various fields in a repeating group) where we need to indicate the currency.

And, every time we simply want to write out “$” we are forcing Bubble to execute this cockamamie search. And the invoice might have dozens of line items in that repeating group. I don’t think the results of such a search would ever be cached unless we specifically force that via saving that search result to a custom state and then referencing the custom state.

(For those who do not understand what the above expression would do and why it’s cockamamie: It is basically a very inefficient search and further it resolves to a string (a text) when what we really need/want to do is retrieve and keep local reference to an object of type Company. Here’s a painfully explanation of what’s wrong:

  1. The unconstrained Search for Companys will return ALL company objects (and the contents of all of their fields) in the database and these objects will be downloaded to the browser just so that we can…

  2. Do a :filtered operation on them (which happens on the client side – in the browser) and examine but one field – the Company’s Name (presumably a text field) and find a match between that text field and a text field on Invoice (BillingCompanyName). This is further dopey because…

  3. It then uses the “Advanced” search operation – which iterates over that list of texts containing all Company names in the database – to find the match (again in the user’s browser, which may be a mobile device of limited processing power). (This should have been written as a constraint on the Search, not as a “post-processing” filter step.)

  4. Having found a match we now have a 1-item list containing a single Company object, so we take its first item (the only item in the list, presumably) and snag its Accounting Currency.

  5. In doing all of that, all we’ve managed to do is return a single field from Company that is a single-character string ("$"). We’ve not retained any other essential info about the Company
    – like we’ll probably need that Company’s address somewhere else on the page and I suppose we could do yet another search to snag that. But what a waste of time and compute resources.

  6. And further we just have a static string value. We don’t have any sort of handle on it except as the result of the crazy search. “Here’s the dollar sign we found for you!.. ‘$’ Hooray!” is what this search amounts to.

An improvement (but still very faulty) approach would be to say, “Oh hai, @keith, you’re right… We don’t need everything about all of those Company objects… we just need their Names!” (While this is correct, I am still reaching for my ruler and preparing to rap you firmly across the knuckles.) But before I can do that, you rewrite the expression as:

Do a search for Company Names:filtered (by condition Advanced: This Name is Current Page Invoice's BillingCompanyName):first item's Accounting Currency

Well sure that’s better as now all we’re downloading is a list of texts that represent all Company Names, but this really only addresses PART of point #1 above. That might still be a VERY long list, eh? And it still has all of the problems of points 2 thru 6.

A better (but still not optimal – usually) approach would be to simply return one Company to the browser by ensuring that the search is done on the server side. For example, perhaps we have a group (let’s call it Group: Company) of type Company and we make its data source:

Do a search for Companys (constraint: Name = Current Page's Invoice's BillingCompanyName):first item

This will “work”, but it’s still not really optimal. My question would be, “Why are we referencing a Company object in this implicit, rather than explicit way?” That is, why are we associating an Invoice with a Company by a text field, rather than by a field of type Company? (In the former case, we need to do a search to find the Company. in the latter case, we can just reference Invoice’s Company – once we have a handle to the Invoice, we always have a handle to the associated Company.)

Further, even if there’s a good reason for implicitly referring to the Company via a text field, we shouldn’t have chosen “Company’s Name” (which would not necessarily be unique), we should have chosen “Company’s unique ID” as the text to store in BillingCompanyName.

  1. This last issue is an interesting one: Are there good reasons for storing an implicit rather than explicit reference from one object to another? (Store the reference as Thing’s unique ID [a text] rather than Thing [a thing]?) For most – even “the vast majority of” – use cases, I’d say no. However, if the referenced Thing is very large and complex, but we only really need to reference some small subset of fields, what Bubble downloads to the browser by default for that referenced Thing may be overkill. I can’t remember if this is a topic discussed here in this thread, but it’s been discussed in various other places. It’s a complex topic on its own.
5 Likes

Two Related questions.

  1. Say a page is type “User”. Actions on the page modify the “User”. Is this causing Bubble to refresh the client-side “User” data repeatedly so that the client-side “User” and DB “User” record are always in sync?

  2. After reading your thread, I’m curious if there is an efficient way to have multiple persistent record types on page that are synced with the DB.

Here’s our use-case:

  • Page = “Event” type.

  • On the page there is Group = “Session” type.

  • A slew of actions modify the “Session” in the database. Currently, we run a display group action each time an action modifies the Session database record, so that the client-side group Session data and the Session database record are in sync.

Is there another approach to consider based on how Bubble syncs the Page type data?

@kramwe there is an active relationship between objects you display in a page (more correctly: objects you reference in the page – they need not be displayed) and their state in the database. If you are on a page viewing info about user johndoe@example.com and John elsewhere updates his information or some process updates his information, you will see it change in your browser.

I don’t think these page elements are continually polling for changes. Rather, Bubble knows about this session and is actively sending info when it changes. (But I could be wrong - I don’t know the exact mechanism used here.)

(ALSO, that a page has a type says NOTHING about whether information is changing. It sounds to me like you’re assuming that viewing a page of type User changes something. Nothing changes unless your app takes action to change data. Conversely… if you are displaying a Thing and the Thing changes, YOU WILL SEE IT CHANGE… in most cases.)

As for your question about “persistent record types on page synced with the dB”… Your page doesn’t need a type for you to pull any data you want. You could have any number of groups on a page whose datasources and types are different. Any group, RG, or text element, etc. that uses a Search as a source for something displayed will update as underlying data changes.

If you want to see – in realtime – what is going on with Sessions in your dB… Just display the Sessions! They will magically change right before your eyes.

NOW, there are ways of disconnecting things (in the example I point to below, look at the lower right text area – I source a list of things via a custom state there so the list size is fixed, but the fields on the individual data objects displayed in that list will still reflect changes).

You can play around with this in this example app that took me a few minutes to build. Open two different browsers or browser sessions and make some changes to the Foos displayed here… watch them change in the OTHER session at nearly the same time:

So much easier to show than tell (and I can’t imagine why you’ve never noticed that this is, in fact, the way things work) – go look:

Check out the example below. This is exactly the same as when you have an admin page like this – check out runmode first and then look at the editor – this shows/explains what you’re curious about:

Run mode:

Editor (anyone can view):

Are you sure you’re doing anything that Bubble wouldn’t do for you automagically? (It doesn’t sound to me like you’re “syncing” anything. You’re refreshing a display. )

Note that, in my dopey example app, anybody can come along and delete a Foo if the Foo is more than 2 minutes old. And anyone can edit a Foo’s name at any time. So the user on the right (in my spoopy GIF) can delete a Foo that the user on the left is trying to edit. (In the first video… but note that I did a change…)

Do we care? Not in this case. You of course could take measures to ensure stuff like this doesn’t happen in your app. (In the usual ways - like making it so only the Thing’s creator can modify it, via privacy rules and workflows that can only be executed by the Creator, etc.)

Can an API Workflow be updating some thing while the Creator might pop by to edit it? Sure, but of course we can handle that (set a field on the Thing “I’m being updated” - don’t let anybody else change it when that is true).

Even so: Can database collisions still happen? Prolly. But not in most cases.

My net net point: There’s no “client side” record versus a dB record. Shtuff only changes in the dB when your app tells it to!

And… Even so, ways to prevent collisions are easy to implement. Since I hadn’t played with it in a while I just went and added that weird trick with reusables (where you can make them run workflows from inside a repeating group) to the sample project.

Now, when a user clicks in an edit field in the Foo Editor, a workflow runs that “locks” that Foo from editing. (It’s not really “locked”, right? – this state just tells the Foo Editors that might be running in other sessions to disable the edit field and display a little “lock” icon. This is how we do it.)

Since those users have nothing to select, they can’t edit the item. It’s super hinky and of course you can’t run API Workflows in a free project (which you’d need to really manage such a system properly), but even then you can do weird workarounds like this project does (e.g., when the page loads, we run “Make Changes” workflow to clean up any “locked” Foos that might not have gotten unlocked – e.g., the user’s session could end while they were inside the input and so the workflow might never run to unlock the Foo… so we just check for Foos that have a lock expiry that is now in the past and unlock them).

Here’s how that looks:

2 Likes

Thank you for all these very interesting details about the Bubble performance question !

Mine is easy because it is such a necessary use for any app : is it better to use “cross-reference” instead of “Search for” ?

Cross-reference for a chat system

  • User
    – conversation (list of conversations)

  • Conversation (thing)
    – messages (list of messages)
    – participants (list of User)

  • Message (thing)
    – conversation linked (type : conversation)
    – body (text)
    – creator
    – creation date

OR

Do Search for a Chat system

  • User

  • Conversation
    – participants (list of User)

  • Message
    – conversation (Conversation? or text ?)
    – body (text)

And you do search for messages of a conversation for this current user ?

What do you think about performance ? I know the Bubble limit so the “cross-reference” system is easy to use, particularly for small website but I imagine the search is longer and longer when database is growing.

I am also asking me what data are loaded with cross-reference system, because you can navigate through all linked data… so is Bubble optimized for that use ? Very deeply or just for a first/second level use, like “Current User’s Conversation” ?

Thank you so much for any lights about this !

Charles

1 Like

These are really good, practical questions! @josh offered helpful guidance on some of your questions in a different forum post. In light of his guidance, we converted several “cross-reference” architecture to “Search for”. Here’s what Josh said:

If I understand correctly, “Search for” queries happen server side with Bubble providing some behind-the-scenes indexing optimizations before they are downloaded to the browser whereas “cross-reference” queries must be all completely downloaded to the browser client-side. That’s why Josh recommends larger databases (>100 items) use “Search for”.

3 Likes

Thank you so much @dserber for the clear answer of @josh I didn’t saw ! It is very interesting to read because I took some courses with this technique explained…

But 100 items is just the beginning of a database… so definitively have to convert all my structure for a Do Search…

Thank you !

1 Like

I believe this question has not been answered yet. I have a saas system where you have many companies. Each companies has many registrations. The registration thing has a company field. The user has also a company field.

If I set a privacy rule on registration: when registrations company = current users company --> show all fields

Is this quicker then do a workflow filter: do a search for registration with company = current users company?

@j.poolman, this is a great question.

I currently do both as I want my system to be super secure… If one of my clients ends up seeing another client’s data I’ll end up with a bad reputation pretty fast… and as I am a mere human, I can’t guarantee to always have the search term or the privacy setting 100% correct.

I’ve no idea how much this is costing me in terms of performance!

Best wishes,
Antony.

You have a good point there about security. When I started to build the app I was still in a learning curve. So I have almost everywhere in conditionals and workflow the company search/filter.

I hope the answer Is that filtering in the privacy setting is faster. Then I take the effort to eliminate these company searches/filter on conditionals.

I think having it filtered twice (privacy settings and on page level) does not make it more secure. The privacy settings are 100% secure. That’s for sure :slight_smile:

I posted a request on one of the forums. I hope its the right one. But nonetheless posting it here in case this is the right place for it -
http://forum.bubble.io/t/storing-traversing-display-tree-structures-in-bubble/90247

@josh - Thanks for this thread its super helpful. I have an Airtable Plugin/Performance related question. I have a RG that displays aboutw ~50 records of data from Airtable, and I apply some sorting and filtering to it in bubble using custom states. The performance is great for exactly 119 seconds from page load, and at 120 seconds it stalls out requiring a refresh. After the page is refreshed the repeating group filters and sorts perfectly again for another 2 minutes, but stalls out again thereafter. Any thoughts on what may be happening here?

As a temporary solution, I’ve set up a workflow to refresh the page every 2 minutes/whenever the user goes idle, but this does not lead to the best user experience. Any help would be much appreciated!

@josh

Unless I’m missing how to do this, I find that you won’t let me make some searches more efficient.

For example, I want to display a list of posts created by the current users’ list of followed people. I have to do a search for all posts and then filter for ones where Current user’s followed list contains parent’s post creator. Unless you tell me that Bubble is smart enough to inverse this, it seems that it searches all posts ever, and then filters ones that match the filter, rather than start with the current users, pulling only their followed list, and shows all of their posts. I really expected the search to look more like Search for: Current User’s Followed Posts.

I have a simple set of lanes (phases) in an OptionSet
I load “cards” in to show on these lanes.
I have only 20 items in here and at first it took 45 seconds to load. (no img)
Optimised, got rid of filters all solved in Search now. It’s loading in 14 seconds. A lot faster but still slow.

It seems to hang on requisition the OptionSets which has no search what soever on it. The RG loads super fast without the lanes. Any data known on user optionsets vs tables?

A search based on an option set should be instantaneous. If you provide some screenshots/description of your data and search setups, someone may be able to help you.

1 Like

3 minutes.
I show the page I’m loading. You see it hanging (this time on 2 google analytics calls). Then the rest loads.
Then I go into the builder,… and show the 2 RG’s and their conditions.

Hmm… if it’s google analytics hanging, that’s beyond me since I don’t use it. I’d suggest trying to test a version of the app without Google analytics in there and see how it runs.

Re: your setup generally…

  • Good that you are avoiding a “:filter”
  • I see you have a nested search (search occurring within a search) – that adds time
  • Looks like you have a horizontal RG. I notice at the bottom of each cell there are stats for Stakeholders, Startups and Talk – if those are searches or calculated values (rather than values already set in the datatype) then that’s adding time of course
  • In addition to google analytics hanging, it looks like it takes a few seconds to draw your page, I am assuming due to: a) having a lot of elements and functionality on the page; and/or b) the page having to do a lot of searches from the database in order to start displaying; and/or c) a crappy plugin that’s slowing something down

I’m not an expert by any sense, but my simplistic approach would be remove something and see how it runs, then put that thing back and remove something else and test again until you isolate the issue(s).

The book on performance by @petter is worth reading.

Perhaps someone else on the forum may be able to spot the issue.

1 Like

Thanks @ed727 much appreciated!
I think you are right to just rebuild the page and see where the time comes from.

I just received another suggestion. Loading the data in a separate object on the page. Then using that one to populate the RG-like structure with show/hide properties. Since this board will never have more than 20 cards on them,…

Indeed each card does have lookups / counts,… each of them is about a second. So much to win there too!

Good luck – my guess is it’s either google analytics or the nested search slowing it down, or perhaps both. I forgot to ask whether you are using a scrolling RG, but that can help limit the amount of searches/data called at page load and make things faster. Please post the solution so we’re aware of what to look out for!

(On preloading the data, I tested it, but it had more downsides than upsides for my particular app. If too much data is loaded, that slows the initial page load and also can bog down or crash the browser.)

1 Like

Hello Pranjal,

Were you able to find a good solution for this?

My structure has Projects, which have Contracts, which have Payments.

On the main page of my webapp I display all Payments in four different Repeating Groups, filtered by their Status. To do this, I search for them on page load and set them as a State on the page. That works well.

When the page is rendered, however, I use various Text elements to display different details about each payment. For example, I show each Payment’s Contract’s Project’s Name.

My main user is approaching 1000 Payments, and displaying these text element takes a long time (30 secs) for them. I assume because, once it loads all the payments, then it’s searching to get the details of each one. I have added pagination so that only 16 payments are displayed at a time. This did not help.

Any suggestions? Would there be a way to get each Payments details on another search when page is loaded, and then tell each text element to get the Payment’s Contract’s Project’s Name from there, instead of having to search again from scratch?

Thanks for any comments you could share!

Search for Payments on page load:

Payments displayed in Repeating Groups:

This is where it gets slow, when it has to get each Payment’s Contract’s Project’s Name: