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?
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.
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.
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.