Web App Optimization
From Xojo Documentation
Contents
Web App Optimization
Unlike a desktop app that is on the same computer as the user, a web app could be on a server thousands of miles away from the user. Also, different users are connecting with varying degrees of speed in between. Some may truly be using a state-of-the-art broadband service, while others are making do with less than an optimal mobile connection. The web app running on the server has to send commands to the browser to communicate with the user interface so it is important that you take into consideration how to build your app to optimize for performance on the internet. Here are a few techniques you can use to optimize your web app’s performance.
Graphics
Use PNG
Although pictures in JPEG format can be smaller than those in PNG format, JPEG tends to be fuzzy for text and smooth color, does not support transparency and has possible licensing issues.
Load Pictures Separately and Store In Properties
Pictures that are part of the project take up space in the app binary and are always loaded into memory, which causes your app to take longer to load. They also can take up up to twice as much memory as the picture when you go to use it because they are converted from Picture to WebPicture.
Instead you can load your pictures from the drive as they are needed at runtime and store the picture in a property. Pictures stored in properties of a web app are cached by the web browser so they are sent only once from the app to the browser. As a result, storing pictures as App properties reduces the amount of data that is transmitted between the app on the server and the browser. Storing your pictures in Modules as properties also allows the browser to cache them.
A good technique is to use a Computed Property to "lazy load" the picture when it is first used. To do this, add a module to your project and call it "Images".
Add a private property called:
Add a protected Computed Property:
Put this code in its Get block (there is no code in the Set block making this a read-only property):
Dim picFile As FolderItem = SpecialFolder.Documents.Child("images").Child("MyPicture.png")
If picFile <> Nil And picFile.Exists Then
Try
mMyPicture = Picture.Open(picFile)
mMyPicture.Filename = picFile.Name
mMyPicture.MIMEType = "image/png"
mMyPicture.Session = Nil
Catch ioex As IOException
// Opening the picture failed
mMyPicture = Nil
End Try
Else
// Picture does not exist
MessageBox("Missing picture: " + picFile.NativePath)
End If
End If
Return mMyPicture
Now you can use this property to refer to a picture and it will be loaded only once:
You can also get the URL like this:
Use the Style Editor
Web Styles are small (in terms of the amount of data sent from the app on the server to the browser) so they are a very efficient way to make visual changes. Add a Web Style to your project by using Insert->Style. The Style Editor lets you change a variety of settings, including Text & Fonts, Borders, Shadows, Padding, Corner Radii, Opacity and Background. After you create a style you can apply it to a control by selecting the Style’s name from the popup menu for the control’s Style property.
Use Rectangles
Rather than creating and loading pictures for simple backgrounds, use Rectangles when possible. They can be dramatically altered using Styles. For example, by setting the corner radii to 50, you can turn a rectangle into a circle.
Rectangles are very small in terms of the amount of data that needs to be sent from your app to the browser making them quick to draw.
Image Locations
Displaying small images (stored in the project) is generally fine. However browsers will cache the images that don't change, so it's important to use a WebPicture whenever possible (as shown above) because they return caching headers.
If you are having issues with image loading speed, you will want to consider moving your images out of your app entirely so that they can be delivered by another web server or content delivery network (CDN). This reduces the load on your Xojo web app and with a CDN it can make the delivery of the image quicker because it is closer to where the user is located.
Reduce Latency
Xojo web apps run in the browser and communicate with an app running on the server. This communication happens over the Internet, which has varying speeds for how long data communication takes that can depend the broadband, congestions, distance and other factors. This is called latency and the best way to minimize it is to limit unnecessary communication between the browser and the server web app.
Remove Unused Event Handlers
Event handlers cause communication between the browser and the web app on the server even if they have no code in them. For this reason, remove any event handlers that do not have code in them.
Send Large Objects in the Background
If you have large objects you know you will likely need, you can use a Timer or use Push to assign them to properties of the page while the user is busy doing something else. For example, Google Maps sends in the background map segments that are around the area you are viewing in case you scroll the map. You can use this same technique in your app. This allows your app to load and display quickly, while the data is used continues to load in the background.
Limit Query Results
If youʼre accessing a database and loading the results into a ListBox, be careful with large data sets with lots of rows. The more data you load, the longer it will take for that information to be transmitted to the browser and the more memory that the browser uses. Remember, there’s only so much information a user can realistically view anyway. Avoid filling ListBoxes with huge numbers of rows and instead consider loading rows on demand or using a paging system.
Limit Use of Key and Mouse Events
Every event causes data to be transferred between the browser and your app. That means you want to avoid using things that can cause frequent events such as KeyUp, KeyDown and MouseMove. If your app is running on the local network, these events will probably be fine but if the user is accessing it over the Internet and you have a lot of simultaneous users, it will cause too much of a lag to work responsively. Test and experiment!
General Tips
Avoid Implicit WebPage Names
Store a reference to a WebPage in a variable or property rather than using the name of the WebPage as an implicit reference. Implicit references have to be looked up by the framework which takes longer than simply accessing a reference that is stored somewhere. For example, rather than doing this:
Create a property (CustomerPage As WebPage1) and then instantiate it yourself and use it to show the page:
CustomerPage.Show
The advantage is that you are creating the instances and not the framework. For example, if you have 50 WebPages with ImplicitInstance turned ON in your project, then when a new session is created, all 50 page instances are created at that same time. If you turn off ImplicitInstance for the pages and instead create them yourself when needed, then only the ones that are actually used will take up memory.
Regardless of the method you use, WebPages are not actually sent to the browser until they are shown.
Use InsertText/AppendText when Updating Text Areas
Each time you update the Text property of a Text Area, all the text is sent from your app to the browser. If you just need to append text or insert some text, use the WebTextArea.AppendText and WebTextArea.InsertText methods instead. These send only the text being inserted or appended to the browser.
WebPage Handling
When a WebPage object is sent to the browser, it becomes part of the page that makes up your app. Delivering this page to the browser is typically the most time-consuming operation. After the initial delivery, when you call Show, the browser is told to bring that page to the foreground, make it visible and make all other WebPages invisible. Because the WebPage contents is already part of the browser's web page at this point, this happens quickly. After the page is shown a command is sent back to the app on the server to call the Shown event(s) as appropriate. To properly clean up and remove a WebPage you should call the Close method, but remember that also means that your app will have to deliver the page back to the web browser the next time is is needed.
Pages that load slowly are usually the result of very complex designs which in turn mean more memory usage on the browser. Some browsers, mobile in particular, have hard memory limits and your app will cease to function if you go over that limit. You can check this on iOS by launching Safari on a Mac with an iPhone or iPad connected and use the developer tools to inspect the memory usage on the device (10MB is often cited as the maximum for iOS).
Deployment
Connections and Sessions
By default, web apps can handle 200 simultaneous connections. Keep in mind that a connection is not the same as a session.
When a browser first connects to your app, there are typically 3-4 simultaneous connections to get all of the initial assets down to the the browser. This is a standard number across web browsers and not something that can be adjusted. Once the initial payload has been delivered, browsers tend to settle down to 1 or 2 connections. There is always at least one connection per session open.
Assuming CPU and RAM are plentiful during initial connection, the effective number of users per web app instance is about 50, but once the initial setup work is done, it gets closer to 200 (probably around 180).
That said, once your app needs to host more than that, there are several techniques you can use to increase that number. For instance, if you're running a standalone app, you can specify a command line option to increase the number of connections. There's a limit to this however, because Xojo apps run on a single core, eventually you'll need to start running instances of your app in parallel to take advantage of multiple CPU cores. This can be done with a Reverse Proxy or a Load Balancer.
Consider the use of CGI vs. Standalone vs. Load Balancing
You can build your app as a CGI or standalone HTTP Server. A CGI app communicates with an actual web server (usually Apache). Because Apache is a full-featured web server, it can handle significantly more traffic and is far more configurable than a Standalone web app. However, Apache does have to communicate with the web app via CGI, which can result in slower performance.
A Standalone app has its own web server built-in, but it is not as full-featured. Tests suggest that a standalone HTTP Server app should handle a couple hundred users without a problem, depending on the type of web app.
But in general, the number of users that a single instance your app can comfortably handle at once is almost wholly a function of your app and deployment environment. If you need to support more than a handful of simultaneous users, you should design load testing into your app from the beginning so you know how much memory, CPU time, and bandwidth each user needs (it's a function of your app, not Xojo) and do the deployment math accordingly.
You may find that a load balancer, such as HAproxy, can be used to handle large amounts of users by directing them to multiple instances of the web app.
Memory Leaks
Memory leaks occur when objects are created but never destroyed. As more and more objects are created and not destroyed, the amount of memory used increases. Eventually, the app will crash because the machine runs out of available memory. In a desktop app this may not be a big deal because the user will eventually quit the app and that will clear memory. However, in a web app it is more serious because the web app may be running for days, months or even longer. If your app is running as a CGI, once the number of users (sessions) accessing the app gets to zero, the app will quit and this will release any memory that app is using. However, your app may never reach the point where there are no users so you need to be careful about not leaking memory. And standalone apps generally do not quit.
The first thing to do is to look for circular references in your code. That is, object a has a reference to object b and object b has a reference to object a. When it comes time to call destructors to release memory, neither one can be called because their reference counts cannot reach 0.
The trickier circular references to track down are places where you have made references to framework objects. To help understand this, keep in mind how the web framework manages its references:
- App has references to Sessions
- Sessions have references to Views (Pages and non-implicit dialogs)
- Views have references to Controls (and implicit dialogs and Containers)
- Implicit dialogs and Containers have references to Controls (and other implicit dialogs and Containers)
If at any time you make a reference in your code which makes a reference that goes laterally or up this tree, you must set it to Nil when you are done with it.
For example, if you had a WebContainer with a property which points back to its Session, and the user leaves the session, then when the framework calls the Close method, the Session would get disconnected from the framework but not freed from memory because your class is technically still holding a reference to it and vice versa because the session (indirectly) holds a reference to the container.
Refer to UserGuide:Memory Management to learn more about how Xojo manages memory.
Local Laws
Web apps are sometimes affected by the local laws in your area. For example, the European Union recently passed a directive requiring web sites to ask visitors for their consent before they can install most cookies.