Using Globals to Limit Server Calls

One of the easiest ways to improve app performance is to limit round trip server calls. This is no easy task when you are just getting started.

How do you get the information to populate things like drop downs with data table rows with out calling the server to search for data table rows every time you open a new form?

The answer I have found is to make one call when you open the main navigation form of your app to fetch all the info that you would normally populate forms with (drop downs are a prime example) and save it to a global variable. When ever you want to populate a drop down you can simple use the global variable without making a server call.

The result is almost instantaneous form population for simple drop down selections as opposed to the 1-2 seconds for the round trip server call each time you open and populate a new form.

Comments / critique welcome and appreciated!

https://anvil.works/build#clone:KE7YZXI4XUAWELZ5=HFAOM3ZGHCRBAGOT4BADKG2N

6 Likes

I’ve used this approach myself and I believe it’s a good method for data that doesn’t change and is not user specific.

I honestly don’t do it most of the time because it requires me to map out the data that will and won’t change based on the user or selection. But this is definitely a good way to reduce server calls.

I had the same problem in my app but managed to get around it by bundling a ‘refresh Globals’ function in with the function that is changing the data… I probably should write that in to the example :thinking:

1 Like

Hi @robert, I don’t think the limitations you mention are a problem.

I often use a Globals module to share data across forms, for example when a document is spread across many forms (or tabs): each form edits a subset of the document and when the form is hidden or the save button is clicked it stores the data in a variable in the Global module. When it’s time to send the whole document to the server it’s there ready to be saved.

As long as the global variables are erased when a user logs out and reloaded when a user logs in, I don’t see anything wrong with storing user specific data.

As someone who came to Python from the data science world I’m honestly still not very clear on this. If two users are logged in at the same time are global variables shared between these users?

I need a Python software engineering book for data scientists (read…dummies)…

An Anvil app involves two interpreters, one real Python running on the server and one Pythonish running on the client.

Whatever runs on the client stays on the client. When an app starts, the browser starts the Python interpreter (Skulpt), imports (i.e. interprets from scratch) all the required forms and client modules and starts the session. Any global variable is created here, is not imported or inherited from previous sessions.
Unless you call server functions and explicitly send some data to the server, you are on your own client.

The interpreter running on the server instead can be shared across sessions of different users, provided the “Keep the server running” option is checked. When the client contacts the server, If an interpreter is already running it will respond and it will be able to see any global variable created in any previous call. If an interpreter is not yet running, it will be start and create all the globals from scratch.

Summary:

  • The life of the interpreter on the client is well defined: from the time the app starts until the browser is closed
  • The life of the interpreter on the server without “Keep the server running” is well defined too: it starts every time the client contacts the server
  • The life of the interpreter on the server with “Keep the server running” is unpredictable: two consecutive calls from the same app could land on two different servers, and both the interpreters will have independent globals. Or, two calls from two different apps could land on the same server and the second one will see all the globals created by the first one
3 Likes

Thanks for the description! This helps me a lot.

In my head I never really thought about having a global module on the client side, but this makes perfect sense! The new way of organizing projects in Anvil helps make this more clear as well.

3 Likes

Hi @rickhulbatt,

This is a good question and the responses here are illustrative. That clone link is dead though… any chance of you reviving it?

I can’t find the original post, but at one point someone had put up a sample client side caching module that performed server calls only once for the items supported by the cache. Here’s a minimal version of it:

import anvil.server

def __dir__():
    return ['foo', 'bar', 'abc']
parameters = {}

def __getattr__(name):
  try:
    return parameters[name]
  except KeyError:
    try:
      value = anvil.server.call_s('get_' + name)
      parameters[name] = value
      return value
    except anvil.server.NoServerFunctionError:
      raise NameError(f"name '{name}' is not defined")  

In this you’d have a server function for each piece of data you want to cache, e.g. for the above you’d have server calls get_foo, get_bar, get_abc.

You’d import Cache into your client forms and use it like this to get the contents of the cache:

Cache.foo

The first time you get foo it gets it from the server, after that it gets the cached value. Useful for values that don’t change during a session.

1 Like

Datums that expire could be extended with a “freshness” duration… Then it’d know when to refresh them.

Sorry @danbolinson but I cannot seem to find it.

The basic premise was that there was a client module called ‘Global’.

At start up, the app would call a server function to search of the common tables I would use (status and type tables). The search iterators would be passed back to the client and stored in the Global module.

Any time any of the common tables were updated, the function used to add/edit a row would return a new iterator that would overwrite the iterator stored in the Global module.

Any form that needed any of these common table rows would import the Global module and then get what it needed.

It worked really well other than if someone else updated one of the common tables, it would not show for you.

The next version I built had a table which basically tracked the last date and time of each of the tables. Any time a server function modified the common tables, it would also update the ‘update table’.

A client side timer would then make a call to the server to check whether the information it had was older than what was in the data tables by checking the update table - if so it would return the up-to-date iterator.

The timer polled the server every 30 seconds or so and this worked extremely well. It did not seem to add too much overhead since most of the calls returned None so were very fast (0.5 seconds round trip from Australia). The only thing to note is to make sure that you set the interval of the timer to 0 before making the server call and then back to the original value afterwards. This prevents the timer from firing too many server calls if you app is busy making other server calls.

I don’t have a cloneable app ready but happy to chat more on this if it would be helpful.

2 Likes