Forum Documentation Showcase Pricing Learn more

Performance Q&A guide


#104

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:


#105

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.

#106

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?


#107

@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: