IBM®
Skip to main content
    Country/region select      Terms of use
 
 
   
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks  >  Lotus  >  Forums & community  >  Best Practice Makes Perfect

Best Practice Makes Perfect

A collaboration with Domino developers about how to do it and how to get it right in Domino

I'm in the process of reviewing the Domino Development training materials, and I think it needs an additional unit on performance. Here's my first stab at it -- I wonder whether I could get comments about any important items I left out, anything I got wrong, or anything I listed that isn't really critical. Your feedback is invaluable! Thanks.

INTRODUCTION

A common experience with new developers, is that they create an application that works great for them, but after it's in use for a while, they discover that three factors have caused their application's performance to degrade to the point of near uselessness. These three factors are:

  • Increasing number of documents.
  • Increasing number of users.
  • Huge numbers of deleted documents.
However, this slowdown isn't inevitable. If you're careful designing your application, you can retain reasonable performance with large numbers of documents and users, and avoid excessive deletions.

DESIGN FOR PERFORMANCE

The following isn't everything there is to know about building high-performing applications. We've concentrated on issues likely to be encountered by new developers, and those that are most likely to cause problems that aren't evident during development and testing, but only emerge later on.

Note: we use the term "expensive" here to denote something that will degrade performance -- something that takes a long time to execute or calculate.

  • The more fields you put on your forms, the more they will slow your application – not only is the form itself slower, but it takes longer to index views, so the application is slower to open and navigate.
  • “Computed for display” fields only affect the performance of the forms they are on – not view performance. Use Computed for Display when possible instead of Computed fields.
  • The more documents your database stores, the longer it will take to index your views. Databases containing millions of documents are possible, but only if the greatest care is taken in the design will an application perform acceptably with more than a hundred thousand or so.
  • The more you modify documents, the longer it will take to index views. Each time a view is used, the index must be brought up to date with any documents that were created or modified since it was last used.
  • The more you modify documents, the longer it will take to replicate your application. This is particularly a problem when you have many users with local replicas, using your application offline. If your application is tying up their replication, their email is delayed, they can't finish up and disconnect from the network, etcetera.
  • Therefore, avoid modifying documents unnecessarily.
  • Deleting a document and then recreating “the same” document is three times as bad in terms of performance, as just modifying the existing document.
NOTE: One frequent situation we see, are Notes applications that load their data from some other source – a text file or relational database. Every night, an agent runs. The agent deletes every document in the database, then reads the outside data source and creates a new Notes document for each record. This is the worst possible thing you can do for performance. There are algorithms available to do this synchronization with the least necessary change to your documents, which you can easily find by searching on the dW Notes forums (see Resources appendix).
  • More views will slow your application even if those views aren't used much. Think about the tasks your users need to perform and what views will be useful. Delete useless views.
  • The more columns there are in a view, the longer it takes to index the view. Include only those columns users need.
  • The more complex your column formulas are, the longer it takes to index the view. Consider moving particularly complex formulas into a computed field on a form, so that your views can just refer to the field -- particularly if the same column is used in multiple views. There's a tradeoff here because having more fields also slows things, so exercise judgment.
  • A view with a column re-sort is less expensive in terms of performance than two separate views, but a re-sort is not free. Avoid unneeded re-sorts, and use the column option “Defer index creation until first use” (new in version 8.0) to speed up views that have re-sorts.
  • A full-text index can take up a lot of room on disk, but it's usually worth it in terms of performance. That's because if you don't have the index, users are forced to use slower searching methods that tie up the server and take longer to give them results.
NOTE: A new database property in version 8.0 lets you turn off "full-text" searching of a database with no index. This decreases the amount by which users can tie up the server, but does not eliminate this problem as there's another searching technique that causes similar issues.
  • It's possible to create views that display the same document several times. For instance, you may have a multi-valued “Category” field and you categorize the view by this field, making each document appear under each category listed in that document. This is often a very useful thing to do, and we're not saying not to do it. But do be aware that this sort of view is expensive, in direct ratio to how many times each document appears in it.
  • Reader fields are somewhat expensive, especially in cases where the view contains many documents, but very few to which the user does have access. Of course, sometimes you must use Readers fields, but be aware of the cost, and consider ways to limit the number of documents or avoid using views (e.g. by mailing a doclink to the user who needs to see a document).
  • Categorized views are a better-performing choice when you have reader fields, provided you don't use the view option "Don't display empty categories." [note: confirm wording of this option].
  • The use of @Now or @Today in the selection formula of a view, or in the column formulas of views or folders, is very expensive, and should be avoided if at all possible. Investigate alternatives, such as:
    • Just sort the view in reverse order by date, so that today's documents are at the top.
    • Use a view categorized by date, and use @SetViewInfo to restrict the display to today's documents only.
    • The same kind of categorized view, embedded in a page, with a “single category” formula.
    • Use a folder instead of a view. Run an agent overnight which finds all the documents that should be in the view, and places them there. Set the accesses of the folder so that users may not manually add and remove documents.
    • Don't use the GetNthDocument method to iterate through a collection of documents. GetFirstDocument and GetNextDocument are better for this.
    • Don't automatically use the “NoCache” option on every @DbLookup and @DbColumn command. When you do this in a keyword formula, it slows your form significantly, and most keyword lists don't change every day. The cost of NoCache every time a document is opened, often isn't worth the convenience of not having to close and reopen the application to see an updated list once in a great while. Think about whether you really have to have that data "up to the second."
    • If your keyword formula calls @DbColumn, then uses @Unique to remove the duplicates, this is a sign that you're probably doing it wrong. Instead, use a categorized view or one with "Unique keys for ODBC Access" [confirm name of option] turned on, so that the keyword values are already unique. Once you get many documents, @DbColumn can take a long time, but returning 20 category headings takes about the same amount of time no matter how many documents are in the categories.
    • For many types of list fields, their keyword lookups don't have to be done when the document is opened in read mode. Use a keyword formula such as the following to avoid the performance hit when users are just viewing a document. [insert formula here]
    TESTING

    Finally, a key technique for making sure your design performs well going forward, is to actually test it under as realistic conditions as you can manage.

    • Estimate how many documents the database will ultimately have in it, and create twice that many test documents. It's usually possible to create realistic test data by creating a single test document, and a formula agent to make copies of the document assigning random values to certain key fields. [insert an example of such an agent here].
    Note: When testing the design of an application that's already in use, make sure to do your testing on a copy of the application -- not a replica -- so that there's no chance of your test documents replicating into the production environment. It's also best to use a dedicated testing server, in case your application does anything that crashes the server or ties up the its resources.
    • Bear in mind that accessing data on a server is slower than accessing data locally, so performance testing done with a local database doesn't reflect performance in actual use.
    • Bear in mind that end users' machines and network connections may not be as fast as yours. Test performance on a lower-end system with a poor connection to see what the real user experience will be like.
    • It may be difficult to simulate conditions of high user load if you are doing all the testing by yourself. There are partner products for this, and [any other suggestions?]...

Andre Guirard | 31 May 2007 08:50:18 AM ET | Home, Plymouth, MN, USA | Comments (7)


 Comments

1) Don’t auto refresh fields
Russ Mayes | 5/31/2007 12:28:28 PM

This is a good list. One thing that immediately came to mind for you to consider is warning about the "automatically refresh fields" setting for forms (We are still in 6.5, so if it's better in later versions, please ignore me).

2) few things...
JY Riverin | 5/31/2007 1:00:02 PM

Hi Andre,

I think it's very complete.Good job!

Maybe a few things:

Agent trigger. I have a customer who had set the trigger on "when documents are modified or pasted". It was very demanding much of the time because there was no documents to process.

In an agent, check every single documents in a db to validate new documents. Instead, create a new view and only processes those new documents.

Declare only variables that you will use. I've seen a developer who had one database to open on 23 servers. Instead of doing a loop, he had declared 23 dbs objects, 23 views objects and 23 documents objects.

Search on dbs that are not indexed.

As you said, readers fields and @Now/@Today should be mentionned.

It's not a performance tip, but AVOID hard-coding server name and stuff like that it's a pain in the a.. when you have to change the server name.

Also in readers fields, when it's possible AVOID putting one username, use role or group name instead.

JYR

3) it’s a good start, but...
Charles Robinson | 5/31/2007 1:13:25 PM

A warning about "store form in document" could be useful, too. That not only causes database bloat, it can cause new developers some confusion when they change the form and their documents don't also change. Since you talk about deleting documents, a discussion of deletion stubs and the impact of the Replciation > Space Savers setting for "remove documents not modified in the last X days" would also be helpful.

I use "generate unique keys in index", but I have learned that if there are replication/save conflicts the view will be empty. Depending on what you're doing your Keyword field will either generate an error ("Entry not found in index" or something like that) or return an empty list. I didn't find this documented anywhere, I figured it out on my own.

Your recommendation of using @SetViewInfo also jumped out at me because it's a tricky thing to use correctly. Since it stays in effect for any view you switch to it's easy to end up with the filter getting "stuck". I've even had it filter other databases, such as my mail, and people tend to panic when it looks like their e-mail has disappeared.

It may be outside the scope of discussion for new developers, but the Lotus Connectors LSX can be a tremendous boon to performance and I don't think many developers are aware of what it has to offer. That's at least in part due to the fact that the documentation is poor (I'm being nice). We've had this discussion many times before, and at the risk of beating a dead horse it would be great to see that documentation updated to the help structure used for Notes, Designer and Administrator, and to see it referenced as a tool that could be used for performance.

4) Cool. Can we have the one for old developers next? :P
Matt Vargish | 5/31/2007 3:36:26 PM

This is a terrific start. And a great idea. A comprehensive list of obstacles -- and not just common-sense stuff but also the ND specific eccentricities -- could certainly save new developers some of the years it otherwise takes to accumulate this knowledge.

So I throw one in that I already owe to you (and Charles): Don't loop through huge document collections without deleting the object reference:

{ Link }

View indexing is something that could use more detail on the developer side too -- some scenarios for using the discard & update settings effectively?

When modifying docs in a view, proper use of AutoUpdate and Refresh...

Also: stampall -- when you can. And removeall. Together even.

And I'm only half-kidding about the old developers bit: I think it be great if some of the really advanced-topic performance optimizing info came directly from those that have the white-box info. Perhaps an update to the old redbook (Performance Considerations for Domino Applications)? For example, how does LS bind a 400 function script library to a form with 20 buttons? What happens if a share-action isn't compiled but needs to load large libraries? What are the performance implications of using Evaluate()vs looping/testing constructs on large lists?

5) More
Matt Vargish | 5/31/2007 4:13:44 PM

Some more:

Don't keep recreating the same expensive objects (view, db, etc.) - use global or static or instance vars. (but note which are static and can be re-created: uiworkspace, session ?)

When walking a view or entry collection use ColumnValues if possible (but don't add every field on the doc for this purpose)

Use GetItemValue and ReplaceItemValue over dot-notation if performance is major concern.

Oh, and a biggie: don't save it if you don't have to! See this all the time: For all docs in db; update value that probably didn't change; save document regardless. You mention unnecessary modification but this one might need an illustration...

Is the print/messagebox memory allocation "feature" still around? (don't print counters in loops without MOD x = 0, e.g.): { Link }

6) My contribution
Ricardo Lucio da Silva | 5/31/2007 4:55:10 PM

Always, always, always remove a reference to an object from memory, before reusing the variable, for example when you traverse a NotesView/DocumentCollection, because lotusscript caches object references.

Example:

This code gradually degrades performance if you run it against 100,000 documents

Set Doc = View.GetFirstDocument

Do While Not Doc Is Nothing

'Do sth with the Doc variable

'The following line will cache the current doc reference

Set Doc = View.GetNextDocument( Doc )

Loop

This is other code is much faster, because you only keep two doc references alive:

Set Doc = View.GetFirstDocument

Do While Not Doc Is Nothing

'Hold the next doc reference

Set NextDoc = View.GetNextDocument( Doc )

'Do sth with the Doc variable

'Remove the current reference

Set Doc = Nothing

'Set the current reference as the next one, previously held

Set Doc = NextDoc

'Remove the duplicate reference

Set NextDoc = Nothing

Loop

7) Putting it all together
Sasa Brkic | 6/1/2007 2:57:53 AM

As a target audience for these tips, I find them very valuable. We are preparing for a design of a potentially large application and performance issue are something we want to address immediately.

I have already seen most of these tips, but not all. On the other hand, there is a lot of other performance tuning techniques (how to use views etc.). Problem for a new developer is not that there is no information, but it is either scattered across numerous blogs / forum messages / web sites or is buried deep inside 1000 pages of some reference Domino book.

I think that a RedPaper (RedBook is probably too big) format would be perfectly suited for this. If IBM decides to write "Domino Application Tuning for New Developers" I'll apply for Residency right away :-)

Regards,

Sasa

8) re: Putting it all together
Andre Guirard | 6/1/2007 8:41:04 AM

There is a performance redbook (link on the right) which is old but still valuable. It's not specific to new developers, but it includes that information.

9) re: Putting it all together
Sasa Brkic | 6/1/2007 11:08:12 AM

Hi Andre,

Thanks for the pointer - now I have something to read over weekend.

Sasa

10) Security and performance...
Richard Schwartz | 6/3/2007 6:53:26 PM

re 'Categorized views are a better-performing choice when you have reader fields, provided you don't use the view option "Don't display empty categories." [note: confirm wording of this option].'

You should mention the trade-off: displaying empty categories can save performance, but reader fields are there for the purpose of hiding data and displaying empty categories can expose data that you want hidden.

And this brings to mind the importance of mentioning the checkbox in the ACL for creating personal folders/views, which has implications for both performance and security. Here are four important points to make about this.

#1 Most people do not understand the real function of the checkbox for private folders/views, which does not prevent people from creating them. It just stops them from creating them on the server. Without that checkbox, they are created locally.

#2 Private views and folders that are stored on the server are also built and indexed on the server, so they impact server performance. Private views and folders that are stored locally are built and indexed locally using data retrieved from the server, so they don't impact server performance as much.

#3 Private categorized views and folders that are stored on the server are built and indexed using the server's permissions, not the owner's permissions, which means that they can expose data that is protected by reader fields. Private views and folders that are stored locally are built and indexed using the owner's permissions, so reader fields are respected and no data can be exposed this way.

#4 Private folders and views that are stored on the server will be automatically backed up by whatever backup program is used on the server, just like any shared folders and views. Private folders and views that are stored locally are only backed up if the owner's PC hard drive is backed up.

So, it's a trade-off. Making sure that the check-box is turned off for most users can be important for both performance and security, but it carries greater risk that someone's work in creating a private folder or view is lost because it might not be reliably backed up.

11) Hardcore
Mika Heinonen | 6/5/2007 6:52:18 PM

Excellent reading, I might continue with some more hardcore methods to get Domino to run like a DB2 or Oracle ERP application:

- Disable unread marks from all databases. They don't work properly anyway, and nobody should use Notes Client anyway. They don't count the number of times a document has been read, nor do they synchronize correctly when you remote a database icon from your workspace, etc... If you want unread marks, put their counters in a seperate database, and present the results via an server based agent for the web users.

- Don't use lazy field operations in LotusScript: doc.FieldName(0) and doc.FieldName=value is BAD, doc.GetItemValue "FieldName" and doc.ReplaceItemValue "FieldName",value is GOOD. The latter syntax is much faster and provides seamless data type compatibility, and also stores field names with correct case-sensitive naming in the documents.

- Enable Transactional Logging. It's faster. While some single user operations are slower with it, the true power of TL comes to shine with multiple users. It also provides detection of corrupted view indexes and alert notification about those.

- Don't put more than the absolute minimal needed number of fields in a document. Split the document over several databases, the more databases, the better. A server based web agent will present the complete document in less than 0.3secs, and you will never have to worry about database sizes anymore (64GB max).

- Don't write Notes applications for customers at all, but only Domino web applications. Notes applications are still OK for some quick and dirty internal team databases, but even global company inside applications should be web only. As soon an application has any potentential information for an customer, make it web only.

- Don't ever use Java applets in web applications. HTML is much faster and cross-platform compatible. Actually, try to avoid Java wherever you can, it's made to make the life of developers easier, not the users, and that's BAD.

- When modifying documents with an server based agent, do not use doc.save unless any field value has changed. Also don't change field values in the in-memory doc if their old value is identical to the newly assumed value. Ok, hard to explain without code, just do it like this (or better):

Sub Initialize

'...

changes = False

updatefield doc2, "SomeField", SomeValue, changes

updatefield doc2, "AnotherField", AnotherValue, changes

If changes Then doc2.Save True, False

'...

End Sub

Function UpdateField (doc As NotesDocument, fieldname As String, fieldvalue As Variant, changes As Boolean)

s = doc.GetItemValue (fieldname)(0)

If s <> fieldvalue Then

doc.ReplaceItemValue fieldname, fieldvalue

changes = True

End If

End Function

12) Personal folders
Steve Howells | 6/6/2007 6:55:27 AM

Excellent document; performance is a real issue for us.

How about a paragraph on personal folders. Customised searches which dump results into personal folders are great, but as the user base grows, the number of personal folders stored on the server grows. How does that affect performance? We currently have 250+ and growing users each with 3 search folders. Thats 750+ personal folders. We can't use desktop personal folders because then we can't use PutAllInFolder.

So what kind of impact does 750+ server based personal folders have on the performance of that database?

13) Other considerations
Erik Brooks | 6/6/2007 1:29:56 PM

I would talk about database properties, too, particularly:

- Disabling unread marks

- Don't support specialized response hierarchy

- Don't overwrite free space

- Optimize Document Table Bitmap

The last one in particular can be a ***HUGE*** performance enhancement for DBs with many docs. Unfortunately it's been broken since ND6.x (and is still broken in ND7, and supposedly broken in ND8 Beta 2 as well - not sure about Beta 3). It works 99.95% of the time, but occasionally it can cause a document to remain in a view index when it no longer matches the view's selection criteria. Nasty, nasty stuff when that happens.

If you're interested, Andre, it's SPR#: DCOE6N82TP / APAR#s: LO21763 & LO13523

Various internal Domino templates use that feature as well, including the NAB (which might make you wonder how many other customers' PMRs are chasing a red herring because of this bug).

But, I digress...

You should also include analysis of the various search techniques, e.g.:

NotesDatabase.FTSearch

...versus...

NotesDatabase.Search

...versus...

NotesView.GetDocumentByKey / NotesView.GetAllDocumentsByKey

...versus...

iterating doc-by-doc in a view using NotesView.GetFirstDocument / NotesView.GetNextDocument and evaluating against each doc

There are pros/cons to each approach, and cases when any of those might be the best way to find docs for a particular piece of code.

14) Alternative to @Today
Dietrich Willing | 6/6/2007 9:19:33 PM

Another alternative to @Today is @TextToTime("Today"). This is very useful in reducing the indexing load on views.

 Add a Comment
Subject:
   
Name:
Comment:  (No HTML - Links will be converted if prefixed http://)
 
Remember Me?     Cancel

Search this blog 

Disclaimer 

    About IBM Privacy Contact