Server Call Return Time affected by number of forms

Has anyone noticed a correlation between Server Call times and the number of forms in an Anvil project?

I have a project with approx. 300 forms and my minimum server call is taking 1.5s for every server call (even just returning an int).

To investigate further I’ve used the ticketing app from one of the Anvil Blog posts (with some forms duplicated to create a larger project of approx 50 forms) to re-create this issue in a shareable format.

I’ve created a new blank form as the start up module which runs 100 server calls to return an single int at 1.5s intervals to get the min/max/average server call times.


The ticketing app with forms:
image
Average server call time with the forms: 589ms
Clone with Forms:
https://anvil.works/build#clone:V3DVTIUH32NSYKT3=QN4LEMEPETYCJSCN5KBJSQ7V


Then removed all forms (except for the start up module form) and undertook the same timing exercise

The ticketing app without forms:
image
Average server call time without the forms: 315ms
Clone withOUT Forms:
https://anvil.works/build#clone:F7AAZLKYCZF2IF25=5LZEDY5MQCNDCPY3TH75XDUV


This leaves a difference in the average server call return time of 274ms with the only difference between the two projects the approx. 50 forms.
(When multiplied by the number of forms, this is very similar to what I am experiencing in my larger app of approx. 300 forms of 1.5s per server call)

Are there any suggestions on how to decrease this server call time while creating larger projects?

Thanks.

Have you checked the keep server running option?

If not, every round trip will require the startup of a new interpreter and the import of all the app modules.

I always have the keep server running checked and like to keep my apps smaller. Apps can share login and data tables, so it’s easy to keep them small. I don’t do microservices or microapps, but my most complex apps have 20-30 forms at most.

Thanks @stefano.menci

This issue relates to Client Forms, not the Server Modules.

Using the “Keep server running” option makes only a tiny amount of difference and this difference is reflected across both apps equally (i.e. both apps are approx. 20-40ms faster per server call).
I believe this option only relates to the server modules being initialised and kept running, and not the client side forms.

To keep the demonstration of the client side issue, the single Server Module in both clone links contains only:

import anvil.server

@anvil.server.callable
def return_one():
  return 1

The rest of the module is completely commented out to reduce server load time.

In relation to the smaller apps, I have a domain specifically for my Anvil project which contains the approx. 300 client forms (and growing). I do not know how I could reduce the app into smaller apps and still allow for the domain being the single url for the app.
Also, as this appears to be a client side issue, if I were to create smaller apps and import a component library app to use, I assume the full component library app client side would need to load each time (or each server call?) and affect the server call times in the same way.

I cloned both your apps and noticed that the first call is the slowest, because that’s the one that spins the interpreter, loads the app and initializes all.

Also, when you check software performance you should check the minimum, not the average. The minimum will show you how fast the software can go, the average will show you how much noise there was that increased the average time. It is true that you don’t really care about the minimum, the user will experience the average, but it’s difficult to get rid of the noise.

If you change your code to get rid of the highest values you will see that the two apps have the same speed. Try with this:

  def timer_1_tick(self, **event_args):
    """This method is called Every [interval] seconds. Does not trigger if [interval] is 0."""
    if self.count == 100:
      self.timer_1.interval = 0
    self.stats()
    timestamp = datetime.datetime.now()
    lblText = anvil.server.call('return_one')
    diff = datetime.datetime.now()-timestamp
    
    print(diff)
    
    millisec = diff.total_seconds() * 1000
    self.timings.append(millisec)
    self.count += 1

  def stats(self):
    if len(self.timings) < 4:
      return
    
    # get rid of 20% of the highest measurements, too high due to high server load, communication problems, etc.
    timings = self.timings[:]
    for _ in range(int(len(timings) / 5)):
      timings.remove(max(timings))
  
    self.label_1.text = f"Avg server call (ms): {sum(timings) / len(timings)}"
    self.label_2.text = f"Max: {max(timings)} / Min: {min(timings)}"

We use subdomains: appname.domain.com requires us to request the Anvil team to register the domain for every app and wait for it to be live, while appname.app.ourdomain.com works immediately because we have registered the app subdomain once and it works for all the *.app sub domains. Things may have changed now, but that’s how we have been working for years.

Perhaps you could split the app in smaller apps called customers.domain.com, tickets.domain.com.

In some apps keeping the server running can make a huge difference. I have an app that imports a bunch of modules that need to make same http request during their import and initialization (if I remember they access some server to initialize the holidays in different countries… I don’t remember, I don’t even know if I am still using those libraries), and keeping the server running shaved more than one second per server call.

As a side note, since you are working on a ticketing app, I strongly suggest to use the anvil_extras.routing module, for the time being still documented in its old HashRouting repository.

This will allow you to copy and paste links to specific forms, something like https://tickets.domain.com/#details?id=ticket123&showdetails=true.

Switching from managing forms the Anvil way to the HashRouting way will require you to change how all the forms are loaded, but the learning curve will be short and it will be worth the effort.

1 Like

Not really tested it out but I think this can also be because of too many functions in a Server Module. Since your app is very large, I suppose you also have many functions in your Server Module. Maybe try splitting into multiple Server Modules.

@stefano.menci thanks for the test code update.

I still get a minimum call difference of 160ms between the test project clones included above, the one with client forms (min:276ms) and the one without client forms (min:119ms).

When I do the same test in my project with approx. 300 forms the min is 1534ms and the average is 1751ms (being what my users are currently experiencing including the 200-ish ms noise).
My large app output:
image

In terms of subdomains, I’ll try this as a potential solution and have message the Anvil team to add a new app to my domain as a subdomain.
As mentioned before, my concern with this option is creating re-usable components in a library app and that library app being the slow-down issue. Do you use an app as a component library in this way, and if so does this impact the server call times?

P.S. just an FYI - I’m not doing anything with the ticketing app, I just needed a shareable example with multiple client forms (replicating the 300 form project was going to be too time intensive!!)

Having done some further research and testing, it looks like just having the dependency app attached to the project (through App Dependencies in settings) and without importing or utilising anything from the library app, this does affect the parent (dependant) app’s server call times to the same extent as having not split up the project into main and dependency apps.

I just ran the 2 tests, and the time is virtually identical: 127.2ms with forms, 127.5 without.

Without forms:
image

With forms:
image

I’m guessing 10% of that time happens on the server, 90% is crossing the ocean.

If you were working without “keep the server running” I would look at all the imports of your dependency chain. Perhaps somewhere there is an import that does some slow initialization and can be made lazy, so it will affect only the calls that actually need it.

But since you are using “keep the server running”, I would say there is something slow somewhere that is executed at every call.

1 Like

Thank you for checking this.

Strangely, I cannot get the same result from the two clone apps like you have.
Each time I run them both; the without forms is in in the same region as your results, and the with forms will not produce a minimum better than 265ms, which is well over your max time. (also tried running both in isolation, and concurrently which doesn’t make a difference to my findings)

There are no dependencies to the clone apps.

I’ve already asked the Anvil team to check my account (Business plan) to check I wasn’t hitting any capacity issues and this isn’t a factor.

I agree with this but cannot see how/where it could be if your copy of the apps runs so much faster and consistently.

Maybe one of the Anvil team could provide some thoughts or suggestions? @daviesian @meredydd @stucork

Hi @stu,

Thanks for raising this. We’re taking a look into it and will post an update when we have some more information.

Thanks,
Ryan

Much appreciated Ryan,

If you need any further information or need access to my apps to review, drop me a message.

Look forward to the update.

1 Like

Thanks Anvil Team.

With what ever changes you have made, you’ve increased the server call speed 10 fold.

Now… The ticketing app WITH forms:
image

These changes have taken the average server call from 589ms to 68ms.

Good work guys!

Thanks