Reader fields (again)
Reader Fields and how to handle them to balance security and performance is a never ending topic. Let us have a a closer look what actually happens. Let us assume we have a view with 500,000 document, where a particular user has access to 77 documents (which is not so uncommon in big organizations). In the first case the view is sorted by some criteria (case number, date or whatever) that scatters the 70 documents all over the database.
When the user e.g. opens that view from the web requesting the default 25 entries, the Domino server actually will more than 350,000 documents. It needs to instantiate a document object and evaluate access control rules for it. These are costly (read time and memory) operations. Performance will not meet any user expectation. Ironically this is hardly discovered by developers since they test with a few hundred documents and very often have universal access. With a little change of the view layout, the situation changes completely. We take the same view but categorize it by e.g. by @Unique(DocAuthors:DocReaders) which would list all documents for a specific reader (remember: an Author field includes Read Access Rights).

Now the Domino Server does an index search which is very fast going after a build index and reads exactly 25 documents. So with a little change in the view layout we removed 99.9929% of the document reads (bad news for hardware sellers). Looks good.... but you will say: wait a second. One user can have read access because her name is in a Reader/Author field, she might be member of a group or have a specific role, the single category only will show me one of that entries at a time.
(UI created with Balsamic Mockups). I have tested this variation over-and-over by giving users both options and monitoring what they choose: access by business role as described here or (see below) flat anything. Business users pick the business role access in a high 90% range of cases for their daily work.
The code version: Create a $$ViewTemplate for your view. Do not add a $$ViewBody field (or the embedded view element). Add a RichText field "Body" and a webQueryOpen agent. That wqo agent writes into that body field which effectively becomes our view display. For creative minds: you are not limited by a table display. The agent looks up all Roles/Groups/Names of the current user by leveraging @UserNameList and makes one call to getViewEntriesByKey. The trick here: a) use ViewEntries not Documents b) have the UNID in one column and use a LotusScript list not to output a document twice c) use a sorting Dojo Table to get the stuff sorted (optional). The nice side effect of the agent: you can output as many lines as you deem fit.
Some sample code:
Sub Initialize Dim s As New NotesSession Dim doc As NotesDocument Dim db As NotesDatabase Set doc = s.DocumentContext Set db = s.CurrentDatabase Dim rt As NotesRichTextItem Set rt = New NotesRichTextItem(doc,"Body") Call test1(doc, rt) 'Just one test Dim v As NotesView Dim catList As Variant Set v = db.GetView("catByAccess") catList = doc.GetItemValue("UserNameList") Call RenderViewToRT(v, rt, catList) End SubSub RenderViewToRT(v As NotesView, rt As NotesRichTextItem, catList As Variant) 'catlist is an array with the list of categories to lookup Dim i As Integer Dim k As Integer Dim m As Integer Dim vec As NotesViewEntryCollection Dim ve As NotesViewEntry Dim colVals As Variant Dim curVal As Variant On Error Goto Err_RenderViewToRT Call rt.AppendText(|<table class="data">|) Call rt.AddNewline(1) 'One lookup at a time For i = 0 To Ubound(catList) Step 1 Set vec = v.GetAllEntriesByKey(catList(i)) If vec.Count > 0 Then Call rt.AppendText(|<tr class="success"><td colspan="7">|+Cstr(vec.Count)+| entries found for |+catList(i)+|</td></tr>|) Set ve = vec.GetFirstEntry Do Until ve Is Nothing Call rt.AppendText(|<tr>|) For k = 0 To Ubound(ve.ColumnValues) Step 1 colVals = ve.ColumnValues(k) Call rt.AppendText("<td>") If Isarray(colVals) Then For m = 0 To Ubound(colVals) Call rt.AppendText(colVals(m)) Call rt.AppendText("<br />") Next Else Call rt.AppendText(colVals) End If Call rt.AppendText("</td>") Next Call rt.AppendText(|</tr>|) Set ve = vec.GetNextEntry(ve) Loop Else 'This is for demo only --- in production this info is useless Call rt.AppendText(|<tr class="failure"><td colspan="7">No data found for |+catList(i)+|</td></tr>|) End If Next Call rt.AppendText("</table>") Call rt.AddNewline(1) Exit_RenderViewToRT: Exit Sub Err_RenderViewToRT: Print Error$ & " in line " & Cstr(Erl) Call rt.AppendText(Error$ & " in line " & Cstr(Erl)) Call rt.AddNewline(1) Resume Exit_RenderViewToRT End Sub
provided by Julian Robichaux at nsftools.com.
- QUERY_STRING:Text, Single Value, Editable: to capture the Query String
- UserNameList:Names, MultiValue, computed:
tmpUsedHere := @DbColumn("Notes":"Cache";"";"LookforGroupsAndRoles";1); tmpThisUser := @Trim(@Replace(@UserNamesList;"*";"")); @Keywords(tmpUsedHere;tmpThisUser) - :
(Note: in this sample there is no processing of the UNIDs). YMMV
Update: Some more links to the topic on developer works:
- Using and understanding Reader Names fields in IBM Lotus Notes and Domino
- Lotus Notes/Domino 7 application performance: Part 2: Optimizing database views
- List of all performance related articles






Comments
Posted by mark At 18:28:45 On 02/28/2009 | - Website - |
Because it's rare that developers have the foresight to put all those lists in singular fields, and even more rare that such a field name would be consistent enough across applications to make a single tutorial apply univerally.
Posted by Nathan T. Freeman At 10:51:46 On 03/01/2009 | - Website - |
The Notes client (in a trad. notes app) is doing the filtering. That is, its scanning the view and downloading the entire view to the client, and the client itself deals with the reader fields before passing the valid documents to the client process.
So in the scenario above, if its a notes app, there will be a HUGE amount of network traffic, delay, latency, the whole 9 yards - before anything happens.
On a web-based application, all that effort happens on the server (thus killing server performance).
So if you have a notes app thats got lots (100k or above) of documents, been running for years, and is now painfully slow, reader/author fields might be the reason.
(And this is why its not a good idea to use reader/author fields in a "large" Notes directory)
---* Bill
Posted by Bill At 19:39:51 On 02/28/2009 | - Website - |
Posted by Henning Heinz At 20:59:37 On 02/28/2009 | - Website - |
Sub Postsave(Source As Notesuidocument)
'The purpose of this code is:
'1. Populate the DocAuthors field with ALL the valid document Authors (not just what @Author would say)
Dim ws As New NotesUIWorkspace
Dim thisdoc As NotesDocument
Dim authorArray As Variant
Set thisdoc = Source.Document
Redim authorArray(0)
Forall item In thisdoc.Items
If item.IsAuthors Then
If item.name <> "DocAuthors" Then
'Ignore the target field
authorArray = Arrayappend(authorArray, item.values)
End If
End If
End Forall
thisdoc.DocAuthors = Fulltrim(Arrayunique(authorArray))
Call thisdoc.Save(True, False, True)
End Sub
I suppose extending that to Readers fields would be easy enough with the same approach. I will freely admit this approach could get ugly performance-wise with really big forms, but for most applications where SuperNTF is likely to be used it seems OK.
Posted by Kevin Pettitt At 02:47:40 On 03/03/2009 | - Website - |
Posted by Stephan H. Wissel At 03:50:44 On 03/02/2009 | - Website - |