Performance Engineering Notes/Domino Applications
Is your Notes/Domino app slow? | "Within a few hours, MartinScott made some suggestions for changes to the application design that made an immediate improvement" |
Find out how MartinScott's Application Performance Tuning expertise can quickly identify immediate performance improvements in your applications.
Note: a finalized version of this paper is Performance Engineering Domino Application Designs, The View, September/October 1999. Contact www.eview.com to subscribe to one of the most comprehensive journals on Notes/Domino technology.
Performance Engineering Notes/Domino Applications
Notes is very complex with a huge array of capabilities, some of which are very sensitive to non-optimal usage. There is a lot of documentation on server performance, but application performance is not well documented or understood. This document explains actual performance tuning techniques used successfully on large-scale Notes application designs.
This document explores
- How to identify what is slowing down an application
- How to optimize the performance of an application
When pursuing the techniques in this article, note that most are accompanied by tradeoffs in maintainability, usability, or development effort.
1. Identifying Application Performance Problems
Two approaches for identifying application performance problems are:
- Monitoring utilization of server/workstation resources
- Performance debugging your code
You should use both of these approaches in combination until you are confident that you have identified the problems.
1.1 Monitoring Resource Limitations-- "Where does it hurt?"
Performance of a Domino application on the server is always bound by one or more of the following factors:
- Disk access
- Processor
- Memory
- Network
With monitoring tools, you can assess disk, processor, memory, and network utilization to isolate which of the above factors bounds your application's performance. While the performance monitor utility is running, take your application through the actions that cause unacceptable performance. To properly simulate load balancing, this may involve multiple simultaneous test users. Then return to the server console and see which of the above factor or factors indicated peak utilization during the test. You can also run the monitoring software on your Notes client and perform the testing at your workstation. For server-based applications, however, the most accurate results are achieved when the application is tested on the server so that all factors are in effect.
When simulating user load, look into using a tool like Domino Server.Load, which started shipping with Domino 4.6.3. Domino Server.Load allows you to run a script (a simulated workload) in your own environment to obtain server capacity and response metrics. It can help evaluate the requirements for additional CPU, memory, or disk storage upgrades.
Later, after you've increased application performance using the techniques in this document, a point will be reached where a different factor is now limiting the performance. At that point, reassess and re-tune the environment if performance is still not acceptable.
1.2 Determining the Problem
Once you've identified some limiting factors of performance, you don't really know the problem. The following table shows where to focus attention in a Domino application depending on which factor is limiting application performance. The improvement potential column is only a subjective rating based on experiences of the author.
Limiting Resource | Area of focus | Improvement Potential * = little * * * * = significant |
Disk access | Optimize view designs Optimize code implementation Optimize server .INI file cache settings* Take advantage of browser caching Upgrade to faster drives* | * * * * * * * * * * * * * |
Processor | Upgrade CPU (on server and on workstation) Optimize code implementation Optimize server .INI file processor and agent settings* Disable server screen saver* Take advantage of browser caching | * * * * * * * * * * * * * * |
Memory | Increase server RAM* Optimize server .INI file memory settings* | * * * * * * * |
Network | Increase network capacity* Convert Notes client apps to Domino web apps Take advantage of browser caching | * * * * * * * * * * |
The right column indicates the subjective rating for improvement potential. Some of these areas of focus are detailed in the following sections in this document. All of these improvements should be considered, and caveats are mentioned where applicable.
1.3 Analyzing Performance by Debugging
If your application's performance problems are due to slow running LotusScript routines, you can find out exactly where by turning on the LotusScript debugger (File...Tools...Debug LotusScript). For large scripts, or scripts with long loops, place breakpoints around suspected problematic code blocks and note the delay when stepping through each these blocks. You'll soon discover at least one block where your delay is occuring. Place more breakpoints within the suspect block, and try to isolate the particular loops or lines of code that are causing delay. In particular, take note of any single line of code that incurs a noticeable delay when stepping line by line. For example, getView() frequently takes on the order of one second, which is painfully slow if you use it in a long loop.
1.3.1 Debugging LotusScript in Scheduled Agents and Dialog Boxes
Debugging LotusScript that is in a scheduled agent or a dialog box is a bit more complex, because the debugger cannot be used in those scenarios. For scheduled agents, temporarily set "When should this agent run?" to "Manually From the Actions Menu" and set "Which document(s) should it act on" to "Selected Documents". Then select in a view the target document(s) upon which this agent should act. Run the agent from the Actions menu while the LotusScript debugger is turned on.
1.3.2 Debugging Web Query LotusScript and Java Agents
To debug LotusScript WebQueryOpen and WebQuerySave agents, simply make sure that the agent type is set to "Manually From the Actions Menu". Open the target document in Notes, and then run the agent from the Actions menu. The NotesSession.DocumentContext property will reference the target document without any changes to your agent's code, and you'll be able to use the LotusScript debugger. CGI variable fields, however, won't evaluate as they would on the web. To work around this, you can modify the values of these variables manually in the long editable field at the bottom of the debugger while stepping through code.
For debugging Notes Java agents in your Java development environment, see Bob Balaban's March 1998 article on www.Notes.Net Debugging Java Agents.
1.3.3 Debugging Agents on the Server
Sometimes your agent will only run on the server, or the performance problem might only exist when it runs on the server. If you want to measure the performance of your agent as it runs on the server, try writing to agent logs. Each agent has an associated log that is accessible through the menu command Agent\Log... when the agent is selected in the agent design list. To use this feature for tracking performance, add code at the beginning of your script to open an agent log. Use the LogAction method throughout your code using the same logic as when setting breakpoints using the debugger.
Dim agentLog As New NotesLog("Agent log")
Call agentLog.OpenAgentLog
'...some code
Call agentLog.LogAction("Break 1")
'...suspected slow code
Call agentLog.LogAction("Break 2")
'...more code
Call agentLog.Close
After your agent has run with the logging in place, the agent log will contain output such as:
1/25/98 12:26:37 PM TestAgent Agent starting
1/25/98 12:26:42 PM Break 1
1/25/98 12:27:22 PM Break 2
In this example output, you can conclude that the suspected slow code between Break 1 and Break 2 took 40 of the 45 seconds total run time. Then, if you still couldn't determine which lines of code were causing your performance problems, you would move the LogAction calls and retest.
1.4 Watching Remote Procedure Calls (RPC's)
On Notes 4.5 and higher for Windows 95, you can take advantage of an undocumented feature of the Notes client to debug performance. Add the following entries in your NOTES.INI file (only try this on Windows operating systems):
Client_Clock=1
Debug_Console=1
Debug_Outfile=<path to filename>
The next time you bring up Notes you will notice new windows opening along side Notes. Don't close/exit the debug windows directly; bring down Notes, remove or comment out the parameters, and restart Notes. Keep in mind that this feature is unsupported by Lotus, so there's no public documentation and no guarantee it will work on future versions of Notes. These windows display the Remote Procedure Calls (RPCs) being sent to the server. You'll see how your client communicates with the server, and exactly what is happening while the yellow lightning bolt is displayed at the bottom left corner of your Notes window. Here is an example of what you might see when opening a complex database:
(145-5768) OPEN_DB: 28 ms. [126+210=336]
(146-5768) OPEN_NOTE: 193 ms. [28+64536=64564]
(147-5769) OPEN_COLLECTION: RCV_UNREAD[263] 97 ms. [120+26556=26676]
(148-5769) SET_COLLATION: 21 ms. [14+12=26]
(149-5769) OPEN_NOTE: 83 ms. [28+18178=18206]
(150-5770) GET_SPECIAL_NOTE_ID: 22 ms. [16+16=32]
(151-5770) OPEN_NOTE: 53 ms. [28+1172=1200]
(152-5770) READ_ENTRIES: 48 ms. [54+3654=3708]
(153-5770) GET_SPECIAL_NOTE_ID: 7 ms. [16+16=32]
(154-5770) OPEN_NOTE: 17 ms. [28+1172=1200]
(155-5770) OPEN_NOTE: 75 ms. [28+16762=16790]
(156-5771) OPEN_NOTE: 46 ms. [28+17012=17040]
(157-5771) OPEN_NOTE: 180 ms. [28+43942=43970]
(158-5771) DB_MODIFIED_TIME: 24 ms. [14+44=58]
(159-5771) OPEN_COLLECTION: 39 ms. [32+34=66]
(160-5771) FIND_BY_KEY: 23 ms. [538+38=576]
(161-5771) READ_ENTRIES: 53 ms. [50+238=288]
(162-5771) OPEN_DB: 21 ms. [126+210=336]
(163-5771) DB_MODIFIED_TIME: 53 ms. [14+44=58]
(164-5772) OPEN_COLLECTION: 34 ms. [32+34=66]
The format of an RPC entry in the log is (Sequential Number), RPC_NAME, time to complete, [bytes_sent+bytes_received=total].
For your application, observe the total time elapsed and the number of RPCs generated for a particular user action on the Notes client. Attempt to match up what you think your code is doing to what the RPC log indicates it is actually doing. This will help identify which lines of code or design features are causing significant delays that you might not have been aware of. Common RPC names are briefly identified in the table below.
RPC | Description |
Open_Session | Authenticate with the server and establish a session |
Open_Database | Find and open a database |
Open_Note | Send the contents of a note (data document or design element) |
Open_Collection | Open a view |
Read_Entries | Send a list of information from a view or search, usually follows Open_Collection |
Find_By_Key | A view lookup via DBLookup |
Get_Special_Note_ID | Send info from the ACL |
Close_dB | Close DB session |
1.5 Determining the Problem
From debugging and RPC analysis, you'll have some ideas as to what specific actions in Notes are impeding the performance of your Notes application. The following table presents some common reasons for various symptoms of poor performance.
Performance symptom | Possible problem |
Opening a view |
|
Creating/opening a document |
|
Saving a document |
|
Creating/opening/saving document via web browser |
|
2. Improving Application Performance
This section includes several performance improvement techniques in the areas of views, code, forms, tables, fields, caching, security, and database size.
2.1 View cleanup and index settings
Frequently, the view indexing process on a server is overloaded due to database design characteristics. Consider that every time a document is saved, Domino refreshes the index for any view selecting that document. Large, all-selecting views, particularly those with time functions and multiple categorization, can decrease server-to-user response time significantly. Long delays (> 5 seconds) when saving a document whose form triggers no particularly complex validation or QuerySave event logic is frequently the result of excessive disk activity on the server. This disk activity in a large Domino application is caused by many views being updated as a result of the document update, and is magnified when multiple users save documents frequently. A large all-selecting view with a time function in a categorized column has been shown to cause document save response time to increase ten-fold on a view of only 400 documents.
Time to Index a View
2.1.1 View Maintenance
Eliminate unused, redundant or duplicate views. A design analyzer tool is useful for locating references to views and other design elements. Specifically, try to minimize the total number of view-documents that are re-indexed whenever a document is saved. To do this, implement several small views that can replace the need for one large view. A good example of this technique is to break down an aggregate view into 26 views, according to the first letter, A through Z. This provides more efficient view index refreshing than one all-inclusive view. This is because one of 26 small views will be re-indexed each time a document is saved, much faster to index than a single large view.
Minimize the number of views. During design, you may have created a new view for each lookup or LotusScript routine that uses a view. That may be the most easily maintainable model, but those extra views can cost a lot of performance. Combine columns from any hidden views that have identical selection formulas. A design analysis tool can help to quickly scan selection formulas. Extend this technique to include even the visible user views if you don't foresee them changing much. Note, however, if a new column needs to be added it is likely that all column numbers referenced against that view will need to be updated throughout your code. So this technique is only recommended as a last resort to improve performance.
2.1.2 View Design
Tailoring views is a great, fundamental tool in Notes. There are costs to each feature, however. Use these suggestions to help you make decisions about what kinds of views to make.
Remove any occurrences of @Now and @Today in view column formulas. Often, developers attempt to use @Today in a view column to measure elapsed days, or aging, of documents in approval processes. In some cases, when such a column is removed, the time to open the view from a web browser was reduced by 70%. To add this functionality in a more efficient manner, replace the time-based column formula with a reference to a new field, say ElapsedDays. Then run an agent on a nightly schedule that updates ElapsedDays based on @Today. A disadvantage of this technique is that all documents will be modified, causing excessive replication activity.
In fact, where feasible, all formulas in view columns should be migrated to new computed fields on forms. These fields can then be directly referenced in view columns for optimal view performance. This is usually best implemented after the application design is somewhat finalized, so that during development you can still change formulas easily without having to run data conversion macros.
Sorted columns take longer to index than non-sorted columns, and categorized columns longer still. If a view has no sorted / categorized columns, and the design is changed so that there is now one such column, there will be a noticeable slowdown in indexing time. If a second column is turned into a sorted / categorized column, there will be an additional slowdown, less severe than the first slowdown, but still noticeable. Each additional sorted / categorized column will add to the indexing time, but each slowdown will be less severe than the previous one.
There is a direct relationship between the number of columns and the number of documents, to the size of the view index. Index size, however, generally does not cause degradation in performance.
Documents appearing in multiple categories increase the size of the view index and the time to refresh it considerably.
The more information contained in SUMMARY fields, the larger the view index. The only way to remove this flag is via the LotusScript isSummary property of the NotesItem class.
2.1.3 View Placement
It is possible to split a database onto multiple servers, and have different views on the different servers. For instance, the read-only views could be on a Statistics, Printing, Searching, Managers' replica of the database. The Working views could be on the users' replica of the database. The two replicas would see each other as Editors only, so that they would not trade view design.
The Working Users' Database would constantly be updating because of user traffic. However, with the Managers' views taken out, there would be far less updating to be done. Furthermore, the Managers aren't even accessing this copy of the database, so that eliminates that group of users for authentication purposes.
The Managers' Database would only update after replication. You can set these two replicating servers up so that the replicator on the Users' Server is never used. The replicator on the Managers Server can do all the work. The Managers Server probably has a much smaller base of users to authenticate, and its indexer only needs to run after the replication cycle (every couple of hours, probably).
Views can also be made Shared, Private on First Use or Private. These view indices will be stored on the server if the user has the database ACL privelege "Create Personal Views", otherwise such views are stored locally on the users' desktop.dsk files. To remove the burden of indexing personal views on the server, either disable the "Create Personal Views" privelege or check the view design checkbox to "Store in Desktop". Also, depending upon the size of the view index, the users may find that their desktop.dsk files grow alarmingly!
2.2 Optimize code implementation
In general, move any application processing to the most server-efficient feasible point of implementation. For a Domino web application, points of implementation in order of decreasing server processing efficiency are shown in the table below:
Point of Implementation | Point of Execution | Use for... |
1. JavaScript (forms, source files) | Browser |
|
2. Formulas (computed for display fields) | Server (Notes core) |
|
3. LotusScript/Java scheduled agents | Server (Agent process) |
|
4. LotusScript/Java triggered agents (WebQueryOpen, WebQuerySave) |
|
1) Use JavaScript or HTML where possible instead of Formulas.
2) Use Formula fields where possible instead of LotusScript/Java agents.
3) Use scheduled agents to perform periodic batch processing on any documents that do not need to be immediately updated. This may be applicable for mail notification, data exporting to an outside application, or other scenarios where it is too costly to perform such functionality in real-time. Minimize the number of times the agent process is started, not just the amount of total work the agent must perform.
2.3 JavaScript versus Formulas (web only)
Any processing that can be distributed from the server to the browser via JavaScript will result in a direct increase in server performance. This includes field validation, simple field setting, URL redirection, etc. Two examples of where you can use JavaScript instead of formulas are input validation and dynamic keywords.
2.3.1 Input validation via JavaScript (web only)
Letting the Domino server perform input validations requires server processing to submit, recalculate, and return the error message. Consider also that Domino must process all fields on the document once for input translation formulas and a second time for computed formulas before any field validation formulas can be evaluated. Catching blank required fields on the browser before submitting will reduce the extra submit incurred by a form that is missing required data and that must be resubmitted. If the validation logic on your form fields does not require any live lookups to other Domino data, the validation code can be most efficiently implemented with JavaScript on the user's browser. Such a script would quickly check required fields or input validation rules with no hit to the server. An additional benefit of this design is that business rules can be stored and looked up in Notes documents for easy maintenance, rather than hard coded in input validation formulas.
The following code provides a button that only submits the form if the Amount field is not empty, otherwise it prompts the user to fill in the field with a pop up dialog followed by automatically placing the cursor in the Amount field.
<SCRIPT language="JavaScript">
function Save() {
if (document.forms[0].Amount.value != "") {
document.forms[0].submit();
} else {
alert('Please enter the Amount of this purchase Requisition before submitting.');
document.forms[0].Amount.focus();
}
}
</SCRIPT>
<INPUT type="button" value="Submit" onClick="javascript:Save();">
Caveats:
- Like HTML, JavaScript is exposed to the user, who can save it to a file, edit it, reload it into the browser, and submit the modified web form. This allows a savvy user to bypass JavaScript validation. Therefore, be careful not to rely on JavaScript as a security feature.
- When the database property "Web Access: Use JavaScript when generating pages" is enabled, performing field validation in JavaScript is tricky, as it requires a manual call to Domino's _doClick function. For details on this technique, see Lotus Notes and Domino Advisor, July 1998, Leverage JavaScript and Notes Formulas in a Single Button (Example 1), by Jamie Magee.
2.3.2 Dynamic Keywords via JavaScript (web only)
Frequently, a form includes one keyword field whose options are dependent on the value of another field. For example you may have a field named City, whose choices depend on the value selected for a field named State. One simple technique is to add an HTML attribute "onClick=form.submit();" to the State field, and then turn on the database property "Web access: Use JavaScript when generating pages". This causes the form to submit, recalculate, and reload with the new Cities the moment that the State field is changed. For optimal performance however, you might consider linking the two fields dynamically via JavaScript on the form. This reduces server submit, save and reply hits that would otherwise be required if the Domino server has to perform this operation for you.
Solutions:
A) Replace this implementation with dynamic keywords using JavaScript dynamically generated from computed for display fields. JavaScript would be generated on the fly by a computed for display field. For more information on this technique, see LOTUS NOTES & DOMINO ADVISOR, September 1997, Enhancing Domino Forms with JavaScript by Rose Kelleher.
Disadvantages:
- MS IE3 does not support the changing of keywords in an HTML SELECT tag.
- This adds a slight additional delay in form load time for large keyword sets, since a line of JavaScript code will exist for each possible keyword option in the VP Organization field. However, this additional load time is frequently less of a performance hit than the time to reload the entire document.
B) Keep the keyword processing on the server using the Notes formula, but replace the HTTP submit action with a _doClick action that triggers a ViewRefreshFields command. For more information on this technique, see LOTUS NOTES & DOMINO ADVISOR, November 1998, Refresh Fields on Keyword Change by Andre Guirard.
Disadvantages:
- Still requires a hit to the server
2.4 LotusScript versus Formulas
There are some places where you have to have to use formulas, such as selection formulas, column formulas, hide formulas, form formulas, replication formulas, and field value formulas. In other places, such as Field entering and exiting events, you have to use LotusScript. There are, of course, certain types of processing where you must use LotusScript to achieve your system's specified functionality. Although LotusScript is often required for certain types of programming, @Functions can implement many classes of processing, including simple sorting, looping (for reading data only), reading/writing child/parent documents and other documents by UNID. LotusScript/Java actions that cannot be done in Formula language include:
- Create or delete a database.
- Take action when the user enters or exits a field.
- Creating documents in the backend.
- Performing complex actions in a loop.
- Multi-threaded algorithms.
- Debugging by stepping through code.
- Looking up more than 64k of data.
- Programmatic network access
- Process Rich Text.
- Manipulate ACLs.
- Create or update a full text index.
- Process a database without knowing its name.
- Process fields/items on a document without knowing their names.
- Interact with the file system, an external application, OLE object, DLL or API.
In cases where you can use either a LotusScript or Formula implementation, there are many factors that govern which will perform better. These factors are explained below.
2.4.1 WebQueryOpen and WebQuerySave Events (web only)
On the Notes client, the user's workstation bears the load of executing LotusScript form events. However, the WebQueryOpen and WebQuerySave events run on the Domino server. Multiple web users opening and saving documents using forms containing WebQueryOpen and WebQuerySave events causes a lot of processing on the server. In these cases, Formula language agents are generally much faster than LotusScript agents. This is because
- Formula language can sometimes execute many times faster than an equivalent LotusScript routine.
- The execution of a Formula does not utilize an agent thread. Each server-based asynchronous LotusScript agent (e.g., WebQueryOpen, WebQuerySave) invocation takes up a thread, which represents a significant portion of CPU and memory utilization. Formulas, which are part of the Notes core, do not require nearly the processing overhead as LotusScript agents.
Reduce the number of LotusScript agent invocations by using Notes Formula language to implement any code that does not need to be implemented in LotusScript. For example, the following LotusScript WebQueryOpen script could be replaced by the addition of the view and the computed field below.
Sub Initialize
Dim s As NotesSession
Dim db As NotesDatabase
Dim v, unidview As NotesView
Dim collect As NotesDocumentCollection
Dim ItemDoc As NotesDocument 'The current document being opened
Dim ItemScheduleDoc As NotesDocument
Dim parentdoc As NotesDocument
Dim parentID As String
Dim parentdocstatus As String
Set s = New NotesSession
Set db = s.currentdatabase
Set ItemDoc = s.DocumentContext
Set unidview = db.GetView("RequisitionNumberLookup")
parentID = ItemDoc.ReqNbr_1(0)
If (parentID <> "") Then
Set parentdoc = unidview.GETDOCUMENTBYKEY ( parentID)
If parentdoc.HasItem("Status") Then
parentdocstatus = parentdoc.Status(0)
ItemDoc.Status = parentdocstatus
End If
End If
tablestring$ = "[" & "<TABLE BORDER=0 WIDTH=300 ALIGN=CENTER>"
tablestring$ = tablestring$ & "<TR><TH>Delivery Date<TH>Quantity<TR>"
schedulekey$ = ItemDoc.ReqNbr_1(0) + Trim$(Str$(ItemDoc.LineItemNumber(0)))
Set v = db.GETVIEW("ItemScheduleLookup")
Set collect = v.GETALLDOCUMENTSBYKEY ( schedulekey$)
Set ItemScheduleDoc = collect.GETFIRSTDOCUMENT
homepagename$ = ItemDoc.HomePage(0)
While Not ItemScheduleDoc Is Nothing
mydate$ = ItemScheduleDoc.ItemDeliveryDate(0)
schedulelink$ = "<a href=""" + "/" + homepagename$ + "/By+DocUniqueID/" & _
ItemScheduleDoc.UNIVERSALID & "?EditDocument"">"
tablestring$ = tablestring$ & "<TD ALIGN=CENTER>" & schedulelink$ & ItemScheduleDoc.ItemDeliveryDate(0) & "</a>"
tablestring$ = tablestring$ & "<TD ALIGN=RIGHT>" & Str$(ItemScheduleDoc.ItemDeliveryQuantity(0)) & "<TR>"
Set ItemScheduleDoc = collect.GETNEXTDOCUMENT ( ItemScheduleDoc )
Wend
ItemDoc.Schedule_Display = tablestring$ & "</TABLE>]"
End Sub
View: ItemScheduleLookup, third column formula |
sep := "!~@"; @Text(@DocumentUniqueID) + sep + @Text(ItemDeliveryDate) + sep + @Text(ItemDeliveryQuantity) |
Field name: Schedule_Display, type: computed for display, text |
REM "only update if the doc is being loaded"; @If(@isDocBeingLoaded; Schedule_Display; @Return("")); REM "get status of parent req, and write to this LineItem"; parentStatus := @DbLookup("":"NoCache"; ""; "RequisitionNumberLookup"; ReqNbr_1; [statusColumnNumberGoesHere] ); FIELD ItemDocStatus := parentStatus; REM "build an HTML table of child Schedule documents with a single lookup to optimize performance"; tableRowData := @DbLookup("":"NoCache"; ""; "ItemScheduleLookup"; ReqNbr_1 + LineItemNumber; 3); @If(@isError(tableRowData); @Return(""); ""); REM "parse out results of the single lookup into three columns, using the same separator as defined in the view column"; sep := "~!@"; var_unid := @Word(tableRowData; sep; 1); var_date := @Word(tableRowData; sep; 2); var_quantity := @Word(tableRowData; sep; 3); tablestringHead := "[" + "<TABLE BORDER=0 WIDTH=300 ALIGN=CENTER><TR><TH>Delivery Date<TH>Quantity<TR>"; REM "splice the three columns from above into the HTML for table rows"; tablestringBody := "<a href=\"/" + HomePage + "/By+DocUniqueID/" + var_unid + "?EditDocument\"><TD ALIGN=CENTER>" + var_date + "</a><TD ALIGN=RIGHT>"+ var_quantity + "<TR>"; tablestringHead + tablestringBody + "</TABLE>]" |
2.4.2 Processing Multiple Documents
According to the Lotus Notes Knowledgebase, the LS Walk the View method was the fastest method for processing multiple documents if the number of documents being updated was very small compared to the total number of documents in the database (less than 1% of the documents in the database). From 1% to 15%, our tests found the best method was LS DbSearch. And, from 15% up, @Formulas were the fastest. The following results are from the Notes Knowledgebase.
- LotusScript Walk the View method excels at running against just a few documents within a large database. Specifically, less than 1% of the total documents in the database.
- LotusScript DbSearch was the fastest LS method when the percentage of documents was between 1% and 15% of the total number of documents in the database.
- Once the percentage of documents being updated exceeds 15%, @Formulas were the fastest kind of formula.
- We found that the LotusScript Full Text Search method was very fast, but not as fast as LS DbSearch.
- @Formulas using Full Text Search were also very fast. For small subsets of documents, they outperformed @Formulas which did not utilize Full Text Search; however, for those small subsets they were still not as fast as LS Walk the View or LS DbSearch. For larger subsets, @Formulas not using Full Text Search are faster than those which use it.
- Almost all tests involved either filling a single field with a hardcoded value, or filling a single field with a value looked up from another database. When we looked up multiple fields, instead of just one, we found that LotusScript methods were affected less than @Formulas. This difference, however, is small compared to the performance differences noted above.
- Simple Actions perform as do @Formulas.
2.5 Formula Performance Tips
2.5.1 @Db Functions
@DbLookups and @DbColumns are incredibly useful, but they can be quite a performance drag on a large application. Use these rules of thumb to minimize the cost of your @Db functions, without reducing functionality.
- Minimize the number of @Db functions in any form.
- Have @Db functions refer to views on the same database where possible. Keep your data in one place if not too big.
- If there is a group of fields which are returned via @DbLookup, use one @DbLookup to return a BigField (which contains a list of the contents of all of those fields), and then parse that field out in your document using @Word.
- Look up a column number, not a field name or column name.
- Use the "NoCache" option whenever it won't affect logic of the lookup. This option has no effect on the HTTP (web) server.
- For documents which are often just read (as opposed to almost always being edited), you should use @IsDocBeingLoaded as part of the formula, or put the @Db functions into buttons, not into fields.
- Use column number instead of field name for DbLookup and DbColumn calls. Looking up by FieldName requires the lookup to go into the document and find the field. Column number allows the lookup to pull the value directly off the view index, which is much faster.
- When trapping for lookup errors, load the lookup result into a temporary variable to check for an error, rather than performing the lookup twice. For example:
_ret := @DbLookup("":"NoCache"; ""; "my view"; ProjectName; 2);
@If(@isError(ret); "Error: Could not find Project " +ProjectName+ ". Please create one."; _ret)
2.5.2 Controlling when formulas run
Controlling when intense computations occur can significantly improve domino processing time. For example, use Computed when Composed for any formulas that only need to be executed upon creation of a document.
Another means to minimize the execution of redundant lookups of any data field D dependent on field A is to restrict the lookup formula from executing if field A has not changed since the last time the lookup performed. To do this, add a field old_A before field A which traps the previous value of A. Also add a computed for display number field is_A_Being_Changed before the field old_A, with formula (A != old_A). In the lookup formula for D, only perform the lookup @IF(is_A_Being_Changed; @DbLookup(...); D). If the field A has not changed, the existing value for D will be retained.
2.6 LotusScript Performance Tips
These LotusScript performance tips are excerpts from from the Lotus Notes Knowledgbase and the Lotus Notes 4.6 Best Practice Guide.
2.6.1 GetView method
Streamline your use of the GetView() method. This method is particularly costly from a performance standpoint. Consider declaring view objects and other frequently used objects at the module level (database, form, or view declarations section). This allows such objects to be accessible from other scripts within the module, reducing the need to re-assign their values.
2.6.2 Autoreload property
Suppose you're using both the NotesUIDocument and NotesDocument classes in a form or subform. When AutoReload is enabled (it is enabled by default), every time you change a value on the back end document (using the NotesDocument class), field data is reloaded into the user interface. To improve performance, before your script does any updates use the following statement to disable AutoReload:
source.AutoReload = False
At the point in your script where it finishes doing updates, use the following statement to perform a manual reload:
source.Reload
2.6.3 Explicit types
Assigning explicit variable types results in faster code than assigning variant as the generic type. This is particularly true when performing integer operations, where an iteration with integer declared can perform up to 200 percent faster than the same operation with variant declared. The difference is less dramatic with string operations.
2.6.4 GetNthDocument vs GetNextDocument
Looping through a NotesView or NotesDocumentCollection using GetNthDocument results in increasingly sluggish performance as the loop progresses. The decreased performance will occur if you use GetNthDocument to loop through a large NotesDocumentCollection or NotesView. Using GetFirstDocument and GetNextDocument, instead, will result in improved performance. Two sample scripts from the Lotus Notes Knowledgebase are illustrated below. The second script (using GetFirstDocument and GetNextDocument) will complete more quickly than the first script (using GetNthDocument).
Script 1: Using GetNthDocument:
Dim session As New NotesSession
Dim col As NotesDocumentCollection
Dim db As NotesDatabase
Dim doc1 as NotesDocument
Dim x as Integer
Set db = session.CurrentDatabase
Set col = db.AllDocuments
While Not ( doc1 Is Nothing )
x = x + 1
Set doc1 = col.GetNthDocument(x)
Wend
Script 2: Using GetFirstDocument and GetNextDocument:
Dim session As New NotesSession
Dim col As NotesDocumentCollection
Dim db As NotesDatabase
Dim doc1 as NotesDocument
Dim doc2 as NotesDocument
Set db = session.CurrentDatabase
Set col = db.AllDocuments
Set doc1 = Col.GetFirstDocument
While Not ( doc2 Is Nothing )
Set doc2 = col.GetNextDocument(doc1)
Set doc1 = doc2
Wend
2.6.5 For loops are faster than Do loops
In the following example, the first fragment runs 60 percent faster than the second fragment.
Fragment A
For y=1 to 15000
z=z+1
Next
Fragment B
Do
z=z+1
y=y+1
Loop While y<= 15000
2.6.6 Avoid using arrays to store intermediate results
Storing data in arrays rather than in non-array variables can degrade performance up to 50 percent. If possible, avoid using arrays to store intermediate results. In the following examples, code fragment A runs twice as fast as code fragment B.
Fragment A
for i=1 to 100
sum = sum + x(i)
next
t(1)=sum
Fragment B
for i=1 to 100
t(1)=t(1)+x(i)
next
2.6.7 Searching an array for a string element
To find out if a string is a member of an array:
1. Create a single string of all the strings in the array.
2. Use INSTR.
3. Bracket each string in the array with a delimiter -- for example, a carriage return.
This search technique does not work if the resulting string is more than 32K.
2.6.8 Indexing arrays
Index the first bound of an n-dimensional array with the innermost loop, and index the last bound with the outermost loop. In the following examples, code fragment A runs 400 percent faster than code fragment B.
Fragment A
for y=0 to 2
for q=0 to 5000
z=z+x(q,y)
next
next
Fragment B
for q=0 to 5000
for y=0 to 2
z=z+x(q,y)
next
next
2.6.9 Tips for using strings
Avoid overusing copy and append operations. For example, the operation x$=x$+"a" is expensive in terms of performance.
If you know the size of a string in advance, pre-initialize the string.
Comparing strings
Because comparing strings is slower than comparing integers, use an integer operation where possible.
Example 1
In the following examples, code fragment A is 50 percent faster than code fragment B.
Fragment A
If (Asc(x$) = Asc("A"))
Fragment B
If (Left$ (x$, 1) = "A")
Example 2
In the following examples, code fragment A is 30 percent faster than code fragment B.
Fragment A
If (Asc(Mid$ (x$, 1, 1) = "A"))
Fragment B
If(Mid$(x, 1, 1)= "A")
Example 3
In the following examples of checking for a null string, code fragment A is 30 percent faster than code fragment B.
Fragment A
If (Len (x$) = 0)
Fragment B
If (x$ = "")
2.6.10 Tips for faster file operations
Read entire structures at one time. File operations are faster if you read an entire document, then operate on individual records, rather than if you read one line at a time. In the following examples, code fragment A runs about 600 percent faster than code fragment B.
Fragment A
Buffer$=Input$(Lof(1),1)
Do
j=Instr(i, buffer$, chr$(13))
If (j<>0) Then
y$(myindex)=Mid$(buffer$, i, j-i)
i=j+1
Else
y$(myindex)=Right$(buffer$, Len(buffer$)-i)
End If
myindex=myindex+1
Loop While (i<len(buffer$)) And (j<>0)
Fragment B
Do
Input #1, y$(myindex)
myindex=myindex + 1
Loop While eof(1) = FALSE
2.6.11 Simplify if statements
This statement always results in both conditions being evaluated.
if a=x and b=y
This statement runs 40 percent faster than the previous example because the second condition evaluates only if the first is true.
if a=x then if b=y
2.6.12 Miscellaneous LotusScript Performance Tips
- The back-end extended class syntax is slightly faster than front-end methods when setting values on a single document.
- AppendToTextList is slow especially with many values in the field, so do not use it in a loop to fill a field with multivalues.
- Using the ColumnValues property is about 10 percent faster than getting a handle to a document and using the extended class syntax-- e.g., x=doc.fieldname.
- When comparing sets, use lists instead of arrays - loop through one list and use the IsElement function to see if your target value is in the other list. This has been shown to result in a performance improvement from about 45 minutes to 45 seconds comparing two lists of about 2300 items.
- Do not update fields with a value that is already in the field. This causes needless processing and replication. However, this check also takes a small amount of time. So if most values being written are likely to be different from existing values, it might not be worth performing the check; just do the overwrite.
- Use Forall for referencing array elements - Forall is 75 percent faster than For when referencing all elements in a 1-dimensional array, and 50 percent faster when referencing elements in a 2-dimensional array. Similar performance improvements occur in arrays with higher numbers of dimensions.
- Avoid using the Redim statement - Compute array size before declaration if possible.
- Choose the correct division operation - Regular division, using the forward slash (/), always returns a float, even if operands are integers. Use a backslash (\) for integer division where you do not want a floating point result.
2.7 Static and Dynamic Table implementation
Large tables, particularly those with editable fields, are known performance killers when rendering documents through a Notes or web client. For large static tables, try aligning data using a tab-formatted area of the form instead of using an actual Notes table. Or, if you really want to keep a large Notes table, minimize the size of the largest table by breaking it down into multiple smaller tables. Five 10 x 10 (row x column) tables load about twice as fast as one 50 x 10 table. You can hide the whitespace between multiple smaller tables and they will appear as one continuous table.
For tables with a dynamic number of rows, developers often create a single table as large as they think the user will ever need, and programmatically hide the unused rows based on a row counter field value. That technique doesn't perform well beyond about 200 table cells. Instead, try the following techniques for dynamic tables.
2.7.1 Computed subforms
A technique that works well for forms that require dynamic tables (such as timesheets, purchase requisitions, etc.) is to insert a subform based on a formula. Say a timesheet should support time capture for up to 20 projects, one per table row, on a single form. A computed when composed field called TimeTableSubform should include the text formula "Subform10". Insert a subform based on a formula, and set that formula to TimeTableSubform so that it references the value in the computed when composed field. Create a subform for the 20 row version of the table ("Subform20"), then one for the 10 row version ("Subform10"). Implement an action bar button that increases the number of line item rows from 10 to 20, by setting the TimeTableSubform value to "Subform20", and then executing a script or formula to re-open the document, such as @Command([ViewSwitchForm]; form) (not an available command for new documents until they are saved and reopened), the LotusScript NotesUIWorkspace.EditDocument() method in R4.5 and later, or @Command([OpenDocument]; unid). In Notes versions prior to 4.64, the ViewSwitchForm command may open the wrong document if the document's position in the view has changed since it was opened. For 10 rows, this is a faster performing form to load than one that always includes the 20 row version of the table with hide-when formulas to suppress the unused rows.
Disadvantages:
- Make sure not to end up with a form that requires more waiting time due to reloading new subforms. Possibly, allow the user to select which version of a tabled form they want upon compose.
- Requires more design maintenance if the table format is changed, since the change must be propagated across all the subforms that can represent that table.
2.7.2 Dialog Box Data Entry
Another technique for dynamic tables is to allow the user to add/edit/remove table data through a Dialog Box user interface. This technique is documented in the The View's May/June 1998 article, Designing Friendlier, Easier UIs for Dynamic Tables, by Betsy Thiede.
2.7.3 Editable Rich Text Table with Data Extraction
In this technique, give the user an editable field into which a default formula uses @DbLookup to retrieve a rich text table stored in a hidden system document. In this editable field, the user can enter any text data into any cell in any order. The user can hit the tab key or a button you provide while the cursor is in the last cell to add a new row. When the user saves the document, a script routine can parse out the text from the table cells into Notes fields for data validation, reporting, or view display purposes. The following script from the Lotus Notes KnowledgeBase demonstrates the capture of text from cells in a table. Note that the document must be saved before the code will work:
Dim w As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Dim doc As NotesDocument
Dim bodytext As Variant
Dim x As Integer, y As Integer
Dim asciichr As Integer
Set uidoc = w.currentdocument
Set doc = uidoc.document
bodytext = doc.body
y = 1
For x = 1 To Len(bodytext)
asciichr = Asc(Mid(bodytext,x,1))
If asciichr = 9 Or asciichr = 10 Then
cellnumber = cellnumber + 1
Messagebox("Cell " & Str(cellnumber) & " contains """ & Mid(bodytext,y,x-y)) & """"
y = x + 1
End If
Next x
Disadvantages:
- Less control over what the user does in terms of adding or deleting rows and columns.
- Cannot easily mix computed data and editable data in the same table.
2.7.4 Computed Passthru HTML Tables (web only)
To significantly reduce the rendering time of a Domino web form with non-editable data in tables, consider replacing all native Notes tables with pure HTML table code on the form or in a computed for display field to be rendered dynamically based on field values. Here is a formula example that will generate an HTML table for 5 columns of virtually unlimited rows, depending on the number of values in the multi-value fields assigned to c1 through c5.
REM "==== FORMAT SETTINGS ====";
borders := 1;
fontsize := "2";
rowalign := "top";
REM "==== COLUMN DATA LISTS ====";
REM "c1..c5 should be assigned the Notes multivalue fields or lookup/column formulas pertaining to the five table columns respectively";
c1 := ItemNumber;
c2 := Description;
c3 := @Text(Acct);
c4 := @Text(Percentage);
c5 := @Text(Acct2);
REM "==== COLUMN HEADINGS ====";
h1 := "Line";
h2 := "Item Description";
h3 := "Account 1";
h4 := "%";
h5 := "Account 2";
BoldHeading := 1;
REM "==== COLUMN ALIGNMENT ====";
a1 := "right";
a2 := "left";
a3 := "left";
a4 := "right";
a5 := "left";
REM "============== DON\'T CHANGE ANYTHING BELOW ==========================";
B1 := @If(BoldHeading=1; "<B>"; "");
B2 := @If(BoldHeading=1; "</B>"; "");
HeadRow := "<TR VALIGN=" + rowalign+ "><TD ALIGN="+ a1+ "> <P>"+ b1+ "<FONT SIZE="+ fontsize+ ">"+ h1+ "</FONT>"+ b2+ "<TD ALIGN="+ a2+ "><CENTER> <P>"+ b1+ "<FONT SIZE="+ fontsize+ ">"+ h2+ "</FONT>"+ b2+ "</CENTER><TD ALIGN="+ a3+ "><CENTER> <P>"+ b1+ "<FONT SIZE="+ fontsize+ ">"+ h3+ "</FONT>"+ b2+ "</CENTER><TD ALIGN="+ a4+ "> <P>"+ b1+ "<FONT SIZE="+ fontsize+ ">"+ h4+ "</FONT>"+ b2+ "<TD ALIGN="+ a5+ "> <P>"+ b1+ "<FONT SIZE="+ fontsize+ ">"+ h5+ "</FONT>"+ b2;
RawRow := "<TR VALIGN="+ rowalign+ "><TD ALIGN="+ a1 +"> <P><FONT SIZE="+ fontsize+ ">"+ c1 +"</FONT><TD ALIGN="+ a2 +"> <P><FONT SIZE="+ fontsize+ "> "+ c2 +"</FONT><TD ALIGN="+ a3 +"> <P><FONT SIZE="+ fontsize+ ">"+ c3 + "</FONT><TD ALIGN =" + a4 +"> <P><FONT SIZE="+ fontsize+ ">"+ c4 +"</FONT><TD ALIGN=" + a5 +"> <P><FONT SIZE="+ fontsize+ ">"+ c5 +"</FONT>";
Rows := @Implode(RawRow; @Char(13));
TableTag := "<TABLE"+ @If(borders=1; " BORDER"; "") +">";
Table := TableTag + HeadRow + Rows + "</Table>";
Table
For tables with editable data fields, modify the formula HTML to include INPUT tags. However, Domino requires that the Notes form actually contain a field for every HTML field submitted. Therefore you still have to create the fields on the Notes form, but they can be hidden. In Domino 5.0, the native Notes implementation of a table will lend itself to faster loading and HTML conversion.
Disadvantages:
- Pass-thru HTML tables are more difficult to maintain than native Notes tables.
2.7.5 OLE or Java Applet Tables
Another technique that can provide virtually unlimited rows on a form table is to include an embedded object or applet on your form design. Combined with Field Exchange (Notes/FX), LotusScript OLE programming, or Java and JavaScript, an embedded object or applet's values can be extracted into the Notes document for validation, computations with other Notes data, and view-level reporting.
Disadvantages:
- OLE-based techniques (OLE/OLE2/ActiveX) only work natively on Windows versions of Notes or Internet Explorer.
- OLE applications must be installed on the user's workstation.
- Browser or Notes client must support Java version used in an applet.
- Additional overhead and load time are required for an object or applet.
2.8 Graphics Placement and Caching (web only)
Proper placement of graphics can speed up the loading of Domino pages. Rather than maintaining multiple copies of a logo, graphical button, or other reused image, attach the image file in a dedicated document accessed through an "images" view. Then use the HTML <IMG SRC> tag on your Notes form to point to the URL of the image file document. This results in a single point of maintenance as well as a consistent URL for all instances of the image, which allows the browser retrieve the image file from the browser cache after the initial retrieval. This also facilitates a single point of maintenance for graphics that may change. For graphics that are unlikely to change, consider placing the image file in the Domino HTML directory. Such placement affords the fastest HTTP service than when those Notes bitmap graphics have to be converted to JPEG or GIF format on the fly by Domino. In R5, you can design shared resources for this purpose. Images on a form or page in R5 can be stored in their native format. For performance, you can re-insert any embedded graphics from your R4.x applications into the R5 native format. Note, however, that R4.x clients will not be able display those graphics.
Images with fewer colors and smaller resolutions will be smaller and therefore load faster. Almost any image scanned with a scanning software's default settings is going to be unacceptably large. Use 50 to 72 dots per inch (dpi) resolution for images, and only use 256 colors for photograph quality images. Also keep in mind that JPEG is generally a more efficient format for photographs, while GIF format renders sharper icons.
For most web applications, try a browser disk cache of at least 5 MB, and a RAM cache of 1 MB. For intranet applications with many graphics, suggest to your users that they increase their browser cache settings. This will allow faster rendering of, and fewer server requests for, frequently accessed content. Make sure that on MS IE, however, you don't set the cache settings such that the browser never reloads a previously visited page, and user sees what appears to be an old version of a document.
2.9 Framesets
Frames have the potential to improve page loading time since they isolate the portions of a page that will change. Framesets are possible in any version of Domino. R4.6 ships with a frameset template, which allows you to easily add a frameset to any existing Notes database or create one from scratch, without any knowledge of frameset HTML. R5.0 includes a native frameset design element that works in the Notes client as well as the web.
Disadvantages:
- Standards within your organization may require only 640 x 480 screen resolution, which may not support viewing frames comfortably without multiple scroll bars.
- Frames can be confusing to unaccustomed users, especially when printing and bookmarking.
2.10 Optimize content storage format (web only)
In an extremely high volume database, it is important to use the most efficient content storage format that affords acceptable maintainability and security. The following list shows different formats of Notes content storage in order of decreasing performance.
- Pages that take advantage of the Domino Command Cache
- HTML flat file
- HTML field in Notes document
- "Treat document contents as HTML" form property
- Text marked as "Pass-Thru HTML"
- Plain text
- Rich text
Clearly, the fastest performance when serving a form to the browser will be when the browser pulls the form content from its own cache, bypassing the network and the Domino server.
2.10.1 Domino Command Cache (from the Notes Knowledgebase)
If formulas exist on your forms, and you are using Notes 4.61 or later, you can take advantage of the Command Cache Formula Analyzer by adding the following line to the server's NOTES.INI file
DominoAnalyzeFormulas=1
In Domino R5.0, this parameter is enabled by default. In 4.61 and subsequent 4.6x builds, it is not. This setting will cause Domino to analyze all formulas for their dependencies through the Formula Analyzer. Rather than exclude all pages that contain any formulas from the Command Cache, the Formula Analyzer intelligently examines the formula on the page and decides its level of volatility. Domino can decide immediately whether the command is too volatile to be cached. For example, if the Web page contains @Now, Domino will not cache the page.
Because of its conservative nature, the Formula Analyzer errs on the side of not caching pages in order to guarantee the correctness of the page returned. You may decide that certain pages can be cached where the server determines that they cannot be. Lotus has provided controls so that you can override the cache behavior where appropriate. The following fields can control the use of the cache to some extent:
- $CacheOptions -- If the value of this field is the text string "0", the page is not cached. A value of text string "1" will cause the page to be cached.
- $CacheValid -- The value of the numeric text string N will be evaluated and will protect the response from validity checks for N seconds. This setting can be globally set by using the NOTES.INI setting "DominoDefaultCacheValid=N." The default for the HTTP server is N=0.
The $CacheValid field lets you tell the cache that this page should be considered valid for a certain number of seconds regardless of what Domino determines the cache strategy to be. Consider a simple home page that is being continually edited. The Command Cache's default strategy would cause the cache entry to become invalid each time the page is edited. You consider it acceptable that the home page is not continually updated as a tradeoff for performance. You can communicate this to Domino by creating a $CacheValid field on the document with a value of "180." This means that the results of the page should be considered valid for 180 seconds. After that time, the normal validity checks will take place.
2.10.2 HTML Flat Files
For Notes forms that do not use dynamic keywords and do not undergo frequent design changes, consider copying from your browser the source HTML generated by Domino and placing it in a flat file in the domino\html directory. The new URL to this static version of the form is simply http://server\FastForm.html. The benefit of this technique is that domino does not have to access the Notes database, evaluate security, form properties, and formulas, and render the form into HTML for each create request. Instead, it can simply hand the HTML file to your browser, which is much faster in extremely high volume environments. The submit process still goes through the Domino HTTP server as long as you don't change form field names or the UNID of the form element. This technique was used to support a feature for visitors to create documents on the Nagano 1998 Olympics website created by IBM with Domino 4.5.
Disadvantages:
- Form changes require that the resulting HTML be resampled and saved into the HTML flat file.
- Not as secure as a Notes form, since anyone with access to the server file system can access the HTML file containing the form. They cannot, however, access documents saved with this form, since they are submitted to the Domino HTTP server for secure storage in the Notes database.
Note that this technique is not helpful after a user's first visit to a form if no formulas exist on the form, since the Domino Command Cache will pull the form HTML out of memory for subsequent hits.
2.10.2 Native Notes Formats
The existence of a field named HTML on a Notes document will cause Domino to serve the contents of that field to the web client as HTML, bypassing everything else such as form properties, events, fields, and static rich text.
The form property "Treat document contents as HTML" yields similar results, but not as good performance as the HTML field. When this property is set, Domino converts all data on the document, the form, and subforms, to HTML. Domino ignores embedded navigators and folders and any embedded views that don't have the property "Treat view contents as HTML" selected. Pass-Thru HTML, [ ] delimited text, and the "HTML" style yield similar performance, but act selectively upon the document contents.
Plain text and rich text are evaluated on the fly by Domino. Rich Text incurs the added task of evaluating any embedded text styles, tables and graphics.
2.11 Replace Rich Text Fields With Text Fields (web only)
Some fields appear to be best presented to the user using Notes rich text fields to yield multi-row editable fields on the web. However, Domino takes more time and storage space to render and save a rich text field than it does a plain text field. Follow these steps to make the Description field (Notes text data type) for a document capable of multi line input, but not rich-text:
- Create the plain text "Description" field. Make it editable for Notes clients and both hidden and editable for Web browsers.
- Create a field called "htmlDescription". Make it text, Computed-for-Display.
- Set the Value formula for htmlDescription to
"<TEXTAREA NAME=\"Description\" ROWS=5, COLS=60 WRAP=VIRTUAL>"+Description+"</TEXTAREA>"
- Set the input translation formula for the "Description" field to @trim(@text(htmlDescription)). This will prevent CRLF characters from being returned from the browser.
Make sure to add the leading and trailing square brackets for pass-through HTML.
2.12 Use Internal Redirection (web only)
When specifying a result URL in a $$Return field or a WebQuerySave agent Print statement, use double brackets around the URL (if the page is on the same server) for faster performance. Without the double brackets, the server sends the URL to the browser, which requests the URL from the server, where the request is processed and the resulting page is finally served to the browser. This involves two round trips from the browser to the server. With the double brackets, Domino processes the request and sends the page to the browser immediately.
Disadvantage:
- The resulting URL perceived by the browser will be the URL of the previous page, not that of the newly served page. If the two URLs do not share a common base URL, any page-relative URLs (./ and ../) on the new page will no longer be oriented correctly and will not work. This violates the notion that a page should be self-sufficient and callable from anywhere. To work around this, you may need to convert page-relative URLs to server-relative URLs.
2.13 General Application Design Performance Considerations
From customer feedback, some limited testing, and information about Notes Database Internals, certain tips and techniques have become standard issue for Large Application Development. These points, from the Lotus Notes Knowledge Base, are briefly laid out below.
2.13.1 Design Elements
Minimize the use of subforms and shared fields, as each instance of these items causes Domino to open a new Note in addition to the document and form. Unfortunately subforms and shared fields are the basis of reusable user interface modules in Notes. The lesson here is to not overuse them; more than a handful of subforms or shared fields will likely cause a noticeable increase in the time to render a document.
Use profile documents to store database configuration settings and user-specific data. Profile documents are stored in memory on the Notes client workstation the first time they are used in a database session. After the initial usage, subsequent accesses to the profile document are performed in memory, allowing flexible high performance access to dynamic data. This is especially useful for storing user preferences as well as database information such as the paths to other related databases in an application suite.
2.13.2 Designing for Web and Notes Clients
When designing hybrid applications for both web and Notes clients, you will be tempted to use a single version of each form for both audiences. For complex forms, this will require significant hide formulas, which incurs additional server processing each time a form is loaded. Instead, consider creating a web-only and a Notes-only version of each form (or view or navigator). Use the Design Properties box to hide them from the inappropriate audience. Give both related design elements the same alias name so you can use that alias name in formulas. For example, if you create forms named NotesMain (hidden from web) and WebMain (hidden from Notes 4.6x and later) and assign them each the alias Main, Domino will automatically use the correct one of these forms for any document where the field form contains the value "Main". Domino presents the NotesMain form to a Notes client, and presents the WebMain form to a browser client. This technique gives better performance than multiple hide-when conditions or computed subforms.
2.13.3 Security
Consider using "Private, Shared on First Use" views or folders instead of shared views of documents with Readers restrictions.
According to Walt Simons of the IBM Endicott Integration Center, shared view may open more slowly when the user only has access to a small percentage (<5%) of the documents in a relatively large view (>2000 documents). This is because the server must scan through the documents in the view until it finds enough to deliver a page-full of results (usually 30 on the web, and more in Notes client depending on screen resolution and view fonts). If a user only has access to 10 documents in the entire view of 100,000, the server must visit all 100,000 before it can determine that there are not 30 to display. To counteract this effect set the view property "Collapse when database is first opened" to true and categorize the view to segment the volume of documents in the view so each category has a small enough number to make response times reasonable. Alternatively, create a non-restrictred "summary" document for each restricted document that contains a doclink to the restricted document. The user can quickly navigate the view of all non-restricted documents, and a doclink launch event on the summary form automatically takes them to the restricted document. The tradeoff is that you essentially sacrifice the ability of Notes to exclude from view the records to which a user does not have access. But the improvement in navigation time can be 1000%.
Documents with Readers fields containing the user's explicit user ID display in a view faster than those which use groups or roles.
Allow Anonymous access in the database ACL for web applications where feasible. Domino caches the commands it uses to display pages to Anonymous users, but not to authenticated users.
2.13.4 Fields
Minimize the number of SUMMARY fields in each document. Under Document Properties, if the Field Flag is set to SUMMARY, then that field information is displayable by the view. That means that the view index has to store that field's information. All fields other than Rich Text fields, and fields created by LotusScript routines are SUMMARY fields. This property can be set by LotusScript. Note that a field whose SUMMARY property is False, will be set back to True if the field is displayed through a form and saved. To prevent non-summary fields from becoming summary fields, don't include the field on the form. Instead include a display version (using a different field name) as computed-for-display or editable and put the name of the non-summary field in the field value formula or the default value formula.
The space required for header information for a blank or empty field is approximately 7 bytes. This means that if you have 50 blank fields in 10,000 documents in a database, there is approximately 3.5 mbs of wasted header space / information.
Use @DeleteField in the Input Translation Formula of editable fields which might remain blank. If the field is called, SubjectLine, the formula would be as follows:
@If(SubjectLine = ""; @DeleteField; SubjectLine)
In most formulas, Notes treats a nonexistent field the same way it would treat a blank or empty field; therefore, although you do sometimes need to code around these fields, it is no more coding than you would have to do for the cases where these fields were empty or blank.
Isolate relatively static data from frequently updated data by using different fields for each. During replication, only the fields that change will replicate, and the large static field will not impact the replication process.
2.13.4 Database Size
Note from the chart below that transactions per minute is not affected by the size of the database. Rather, it is a function of the number of users on the server simultaneously. In fact, a larger database that contains its own reference data for lookups will generally perform faster than if the lookup data was partitioned on another database file. This is because lookups to the same database are faster than lookups to other databases.
Conclusion
Domino application performance tuning is an art that combines an understanding of Notes, your intuition, and trial and error. To complicate your performance efforts, there is a tradeoff between performance, functionality, and maintainability/cost. It has been said that of these three desireable characteristics of a software system, you can only pick two. Generally, functionality is a hard requirement. So you're left with tradeoffs between maintainability and performance.
With a new file storage structure and other improvements to the Notes core, Lotus has done their job to improve overall performance in R5. With an understanding of what causes various application performance problems, what can be done, and how it will affect maintainability, you can do your part to optimize the performance of Notes applications for your organization.
In exploring your application for improvement potential, don't forget that the Domino server configuration is usually a large performance factor. Due to its many capabilities, there are numerous settings and options on the server that impact performance, several of which are specific to hardware and operating environment. And unfortunately, there are far too many to include here. For information on improving Domino server performance, see the numerous articles and presentations available at http://www.lotus.com/performance and http://www.notes.net.