Optimisation

We now have a functioning web app. Congratulations, at this point you have all the skills you need to create a website in Anvil. The rest of these tutorials are about improving the web app. In particular we are going to speed it up.

You will notice that our webapp is not very responsive. Navigating between pages often shows a process swirl as the component loads. This is called latency and it is not ideal. People are used to websites loading quickly, so we need to do something about that.

Causes of Latency

There can be a wide range of causes for latency, involving networks, and service providers, but these are beyond our control. We are going to focus on the latency that is caused by the architecture of our code.

Remote Database Access

Remember talking about frontend and backend architecture?

frontend backend

Remember we also discussed that the slowest link in this process is the communication between the Frontend and the Backend? That’s why we created a single-page application, to minimise the amount for data being transferred between the our two halves.

Keeping all that in mind let’s look at our code.

Below is the code for the MainForm. Notice the highlighted code.

 1from ._anvil_designer import MainFormTemplate
 2from anvil import *
 3import anvil.server
 4import anvil.tables as tables
 5import anvil.tables.query as q
 6from anvil.tables import app_tables
 7import anvil.users
 8from ..HomeComponent import HomeComponent
 9from ..CalendarComponent import CalendarComponent
10from ..AddComponent import AddComponent
11from ..AccountComponent import AccountComponent
12from ..SetDetailsComponent import SetDetailsComponent
13from ..WelcomeComponent import WelcomeComponent
14
15
16class MainForm(MainFormTemplate):
17  def __init__(self, **properties):
18    # Set Form properties and Data Bindings.
19    self.init_components(**properties)
20    self.breadcrumb_stem = self.label_title.text
21
22    # Any code you write here will run before the form opens.
23    self.switch_component("home")
24
25  def switch_component(self, state):
26    # set state
27    if state == "home":
28      if anvil.users.get_user():
29        cmpt = HomeComponent()
30      else:
31        cmpt = WelcomeComponent()
32      breadcrumb = self.breadcrumb_stem
33    elif state == "account":
34      cmpt = AccountComponent()
35      breadcrumb = self.breadcrumb_stem + " - Account"
36    elif state == "add":
37      cmpt = AddComponent()
38      breadcrumb = self.breadcrumb_stem + " - Add"
39    elif state == "calendar":
40      cmpt = CalendarComponent()
41      breadcrumb = self.breadcrumb_stem + " - Calendar"
42    elif state == "details":
43      cmpt = SetDetailsComponent()
44      breadcrumb = self.breadcrumb_stem + " - Account - Set Details"
45    
46    # execution
47    self.content_panel.clear()
48    self.content_panel.add_component(cmpt)
49    self.label_title.text = breadcrumb
50    self.set_active_link(state)
51  
52  def set_active_link(self, state):
53    if state == "home":
54      self.link_home.role = "selected"
55    else:
56      self.link_home.role = None
57    if state == "add":
58      self.link_add.role = "selected"
59    else:
60      self.link_add.role = None
61    if state == "calendar":
62      self.link_calendar.role = "selected"
63    else:
64      self.link_calendar.role = None
65
66    self.link_register.visible = not anvil.users.get_user()
67    self.link_login.visible = not anvil.users.get_user()
68    self.link_account.visible = anvil.users.get_user()
69    self.link_logout.visible = anvil.users.get_user()
70  
71  # --- link handlers
72  def link_home_click(self, **event_args):
73    self.switch_component("home")
74
75  def link_calendar_click(self, **event_args):
76    self.switch_component("calendar")
77
78  def link_add_click(self, **event_args):
79    self.switch_component("add")
80
81  def link_account_click(self, **event_args):
82    """This method is called when the link is clicked"""
83    self.switch_component("account")
84
85  def link_register_click(self, **event_args):
86    anvil.users.signup_with_form(allow_cancel=True)
87    self.switch_component("details")
88
89  def link_login_click(self, **event_args):
90    anvil.users.login_with_form(allow_cancel=True)
91    self.switch_component("home")
92
93  def link_logout_click(self, **event_args):
94    anvil.users.logout()
95    self.switch_component("home")

Each call to anvil.users.get_user() is a communication between the frontend and backend of our web app. It’s not just here. If you do a search for anvil.users.get_user() you will notice that we also call it in four other frontend modules.

search results

So there are a heap of times when the frontend is communicating with the backend. What’s more, most of these are unnecessary. It is not as if the user logs in on every page. This means that the frontend is repeatedly requesting the same information from the backend.

So our first step in optimising our code is to reduce the number of times the frontend remotely accesses the database on the backend.

Local Database Access

There is another way we can speed our web app up. We’ve already established that network communication is the slowest link in our chain. The next slowest link is retrieving data from the database, because it lives on a hard drive.

Returning to our search, we will notice that the server modules also calls to anvil.users.get_user() four times. Although, not as expensive as a remote access to the database, it will still slow the website down.

search server

So how do we improve this?

Caching data

The answer is to cache data. Caching is a way of temporarily storing data so that it can be accessed more quickly the next time it’s needed. The first time we make a call to the database, the returned data should be stored in a variable so it can be quickly reused when needed. The only time that the database is accessed is when that data has changed.

The principle of caching is this: store frequently used data in the fastest place. For our website that means in frontend variables.

The rest of the tutorials will show how we will apply this principle to our website.