Slow variable assignment to anvil.server.session

I’m working with the Session State, and variable assignments to the dictionary are a lot slower than I’d expect.

I’m using a callable like this:

@anvil.server.callable
def set_active_filters(active_filters):
  anvil.server.session['active_filters'] = active_filters

To store a list of filters that looks like this (typically btwn 2-10 elements - very short lists):

['filter1', 'filter3', 'filter4']

The call to assign this to the session state is taking >200ms. For so little data, that’s a ton of time and is impacting user experience. (For reference, 200ms is about 25% of total time per page load - the rest is due to an external API call to my own service running in us-east-1.)

Anything I can do to improve the performance? Is there another Anvil tool that’s more appropriate for storing this kind of data during a session?

Hi @kdal

The time you are seeing is dominated by the round-trip to the server (see ‘Measuring the timings’ below).

Is it possible for you to store the active filters client-side instead? You could create a module called Globals and put it there.

Here’s an example that does that.

https://anvil.works/build#clone:FDGKLWQEMXVSHXY5=BBQOKIWYCWLA6OBOKY4ES3PH

Measuring the timings

Here’s an app that does what you describe, with timing outputs added:

https://anvil.works/build#clone:HE5472Z2YRMX2ZSH=CJTORBBHGTSIZ32KPP35X6BX

It has a server function that stores a variable in the session:

@anvil.server.callable
def set_active_filters(active_filters):
  start = datetime.now()
  anvil.server.session['active_filters'] = active_filters
  print((datetime.now()-start).total_seconds())

And a Button that calls that function with a short list:

  def button_1_click(self, **event_args):
    """This method is called when the button is clicked"""
    start = datetime.now()
    anvil.server.call('set_active_filters', ['filter1', 'filter3', 'filter4'])
    print((datetime.now() - start).total_seconds())

In the Restricted Server Module environment, the print within set_active_filters reports 8ms. The print within the Button click handler reports 170ms for the first call, and ~100ms for subsequent calls (the first call takes longer because it’s establishing the websocket connection). In the Full Python environments, the set_active_filters takes 11 microseconds.

This is incredibly helpful, thank you for the thorough reply! Given the round trip from California to us-west-2 (I found your AWS region in this older post), 200ms is actually pretty fantastic performance.

Storing the filters client-side should theoretically work just as well for my purposes. I hadn’t fully realized the difference between Modules and Server Modules, but your example illustrates it well. I’m going to try to implement the functionality with a Globals module in my project.

One follow up question - can I store functions that call my own API in the client-side Globals module too? Here’s an example function that presently lives in a Server Module that I’d like to move:

import anvil.server

api_root_url = 'https://my.api.com'
tmp_api_key = 'my_api_key_string'

@anvil.server.callable
def filter_customer_data(active_filters=[], refresh_data=0, start=0):
    return anvil.http.request(
      api_root_url + '/filter-customer-data',
      method='POST',
      headers={
        'Content-Type': 'application/json',
        'x-api-key': tmp_api_key
      },
      json=True,
      data={
        'active_filters': active_filters,
        'refresh_products': refresh_products,
        'start': start
      }
    )

I put them in the Server Module following the example in the docs, but I could save a ton of time if these functions live client-side, right? Are there any limitations to client-side Module capabilities that I need to be aware of?

Your api key will be publicaly readable if you move that client side. Don’t know if that’s an issue for you.

1 Like

One follow up question - can I store functions that call my own API in the client-side Globals module too?

anvil.http.request does work client-side, but making HTTP requests between web pages depends on CORS (Cross-Origin Resource Sharing) settings.

It sounds like your API is entirely under your control, so you should be able to configure it to accept requests from anvil.app (or your custom domain if you’re using a custom domain).

Here’s a relevant section of the reference docs.

App secrets

If you want your API key to be encrypted at rest, you could put it in an App Secret.

As @david.wylie says, it will have to be in plain text in the browser’s memory at some point if you’re going to make the HTTP request from the client.

Just in case you need a CORS example in Anvil (as @shaun was saying) -

1 Like

In fact, the procedure for building CORS-able HTTP endpoints in Anvil has improved substantially in the last couple of weeks! I’ve added an update to that forum thread…

Thanks very much, @meredydd, @david.wylie and @shaun. This is exactly the info I needed to get the job done right. Great timing too since you’ve just made improvements! I really appreciate your help.

@david.wylie - The API key is stored as a string in the module for development, but when I push this live, I’m planning to add the Users service and will store the API key in a data_table with a foreign key to user.

@shaun - yes, our API is entirely under my control. It’s used as an interface to our backend database which is not publicly accessible. I hadn’t considered using an App Secret, and I’ll look into it for sure - thanks for the tip!

Beyond that, it’s okay if the API key is in plain text in the browser’s memory, it’s used to throttle/limit requests and determine which datasets to return, but security will be managed by the Users service.

Thanks again! Let me know if anything I’ve said sets off alarm bells for you. :slight_smile: