If you're going for raw speed, the easiest way to boost performance is to throw hardware at it. Specifically, RAM. The XPages runtime has been highly optimized by IBM, but as of 8.5.2 the default settings are optimized for scalability rather than speed. So if, instead of maxing out the total number of concurrent users the application can support, you want to maximize the performance for each individual user, here's what I recommend in terms of server configuration:
- Add as much physical RAM to the box as you can talk the admins into installing. Obviously, a fast processor helps as well, but RAM is comparatively cheap, and unless your app is extremely complex (or heavily used), 8 GB is going to be plenty... but if you can get 16 GB, even better.
- The default Java heap size is ridiculously low. Whatever amount of RAM you end up with, set the HTTPJVMMaxHeapSize parameter in the server's notes.ini to a fourth of that. If the server has 8 GB, for example, HTTPJVMMaxHeapSize=2048M. Be sure not to remove the corresponding setting, HTTPJVMMaxHeapSizeSet=1, or Domino will reset the heap size the next time it loads.
- If you can swing it, run the program files on an SSD. I don't actually recommend going solid state for the data, but if Domino itself is installed on an SSD, you'll be amazed how fast the whole HTTP stack runs.
But beyond hardware, there are several development techniques that will maximize speed.
- First and foremost, become intimately familiar with the so-called "scope variables". The nature and use of these are documented in detail elsewhere, so I won't belabor that here. But the better you understand what information is appropriate to store in each scope - and that you can store complex object structures, not just "string-to-string" mapping - and the more you take advantage of this, the faster the application's response will be. This is because you're limiting disk I/O as much as possible... the more frequently the application can just pull whatever data the user is requesting from RAM, instead of querying the database every time, the faster the application will be. This is why you want lots and lots of RAM. :) Of course, you don't want to be gratuitous with your memory storage, which is why it's crucial to understand which scope is appropriate for which type of data... when you get the hang of this, Domino will clean up these memory caches for you automatically.
- If you find that you're running out of memory, but you're confident your scope storage is reasonably optimized, tweak the application properties to clear the application scope periodically. The higher the frequency you set, the more you can cache in all the scopes without bursting the heap... but if you find you're needing to set this to a frequency of less than an hour, revisit what you're actually storing, because it's probably out of control.
- Change the server page persistence setting to keep the current page in memory. Strictly speaking, keeping all pages in memory provides the best performance, but it tends to be downright wasteful of memory. When only the current page is kept in memory, as a user navigates from page to page (as opposed to firing events against the page they're already on... even if it's a full refresh event), it serializes the state of the previous page to disk. If a subsequent event is fired against that previous page, then that page's state is retrieved from disk and loaded back into memory. But this scenario only occurs if the application loads a new page in a new window or tab, and the user later switches back to the original window and interacts with it again. In typical navigation scenarios, once the user has opened a new page, the previous page instance no longer exists... even if they navigate back to the same page again, it's a new instance of that page. Hence, keeping all pages in memory essentially guarantees at least a temporary memory leak. All of that storage is eventually released, of course, but in the meantime the server is consuming memory it will never need again that would be better served storing whatever you're explicitly caching in scope variables.
- Minimize the use of SSJS (server-side JavaScript). Every time any SSJS expression is evaluated, Domino parses the expression in realtime into an abstract syntax tree, then runs Java code that is a rough approximation of how the JavaScript specification states that code matching the detected syntax should run. The more you move your logic directly into true Java classes, the less expensive it is to execute, so it runs faster. Each time, this differential is minuscule, but aggregated over the entirety of a complex application, it adds up enough to be perceptible to end users. Even without moving your code to Java, however, you can reduce SSJS usage simply by making the most of EL (expression language). For instance, rather than binding an image's src attribute to:
"#{javascript:return '/' + database.getFilePath() + '/images/' + currentDocument.getUniversalID() + '/$FILE/' currentDocument.getItemValueString('thumbnail');}"
...you can intersperse standard EL expressions throughout an otherwise hardcoded string:
"/#{database.filePath}/images/#{currentDocument.universalID}/$FILE/#{currentDocument.thumbnail}"
Not only does this reduce (and, often, eliminate) the amount of quote escaping required, making the code easier to maintain, it's more efficient for the server to evaluate each expression. In the above example, it's actually slightly faster to evaluate those three separate EL expressions than to evaluate the single SSJS expression they replaced.
- Wherever possible, change your expressions from dynamic bindings to page load bindings. This is just a fancy way of saying that ${database.title} is always better than #{database.title}. The $ means that the expression is only calculated once per page instance... # means it's recalculated as often as needed - sometimes several times within the same request, depending upon what value is bound to the expression. So obviously you can't just change all your expressions to $... input controls bound to form items, for instance, should remain #. But if you have computed text displaying the application's title (as in the above example), that value is obviously unlikely to change during any given page instance, so change it to a $ expression, and then the server only has to evaluate it once. Combine this optimization with thorough scope caching, and it's easy to see how this can really speed up page load times and event execution response. This gets particularly noticeable when dealing with repeat controls: each time a user navigates to the next page of a repeat, if that repeat's value is a # binding, it has to recalculate that value... change it to a $, and it only loads the collection once. Even if the event the user triggered has nothing to do with the repeat, the server has to recalculate all # bindings, so if the repeat's value is a #, it still has to pull the collection again even though the user wasn't explicitly interacting with the repeat.
- If you have the luxury of using the Extension Library, check out the Remote Service control. This allows you to easily define a JSON-RPC API for the page, which lets you call server-side code from client-side JavaScript. Because this skips posting the entire form (as all standard events do), it's incredibly efficient. There are many situations where this allows you to perform the desired operation faster than you could with a standard event... just keep in mind that, because it's not posting the form, the server is unaware of anything that may have occurred since the last event - most notably, any field values that have changed - so limit use of this to situations where you can manually send only the information the server needs in order to execute the necessary code and send back the desired response.
I'm sure there are other ways to optimize performance that I'm forgetting at the moment, but hopefully that's enough to get you started. :)