Web app optimization

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

File size matters

Any pictures you use in your project or open and display via code are going to be sent from the server to the user's browser. The bigger those pictures/files are, the longer sending will take and the less responsive your app will be. The smaller the file can be the better.

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:

Private mMyPicture As WebPicture

Add a protected Computed Property:

MyPicture As WebPicture

Put this code in its Get block (there is no code in the Set block making this a read-only property):

If mMyPicture Is Nil Then
  Var 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:

ImageView1.Picture = Images.MyPicture

You can also get the URL like this:

url = Images.MyPicture.URL

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.

Rectangle1.style.value("border-radius") = "50px"

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.

You can also use the WebControl.UpdateBrowser method to force the browser to update instead of waiting or the event or method to end. This is handy when you are making visible page changes in a loop.

General tips

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 150).

That said, once your app needs to host more than that, there are several techniques you can use to increase that number. For instance, you can specify a command line option to increase the number of simultaneous 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.

Ensuring your app can handle all of your users

A Xojo web app has its own web server built-in, but it is not as full-featured as a dedicated web server such as Apache. Tests suggest that a Xojo web app should handle a couple hundred users without a problem, depending on what the app is doing.

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.

Xojo Cloud manages this automatically via load balancing (which means deploying an instance of your app for every core your server has) and a reverse proxy. If you find your app is getting too sluggish because you now have significantly more users than in the past, simply upgrading your Xojo Cloud server to one with more cores will likely solve the problem.

If you are deploying on your own server, you may find that a load balancer, such as HAproxy or Nginx, 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. To help avoid this, set the WebApplication.AutoQuit property to True which will cause your app to quit when the last user exits the app, clearing any memory leaks in the process. Keep in mind that if you have any code that runs outside of a session (such as to do maintenance or to reply to API calls via WebApplication.HandleURL then you may need to consider the state of those operations when quitting.)

Finding memory leaks

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 How Xojo Manages Memory 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.