Метод DigestSearch для баз данных Lotus Domino

В данной статье представляется DigestSearch, альтернативное решение для работы с документами IBM Lotus Notes Profile и для выполнения одиночных, высокоскоростных операций поиска. Для поиска в серверных базах данных из Notes-клиента DigestSearch в два раза быстрее любых других доступных методов поиска, превосходя по характеристикам как полнотекстовый поиск, так и метод LotusScript GetDocumentByKey.

Метод DigestSearch лучше всего может быть описан как решение, альтернативное методу поиска LotusScript View.GetDocumentByKey для работы с документами Profile. Его главным предназначением является поиск одного или более документов с использованием одиночного поискового слова (например, номера карточки социального страхования (SSN), номера телефона) или поиск уникального порядкового номера сохраненной SQL-записи (Structured Query Language).

Главным преимуществом DigestSearch является то, что для поиска в больших базах данных (особенно в серверных базах данных из Notes-клиента) он превосходит все другие традиционные методы по скорости. Фактически, в зависимости от сложности поиска, метод DigestSearch может быть в 20 раз быстрее! И DigestSearch не требует каких-либо представлений (view) для выполнения операций поиска, поэтому вы можете уменьшить размер вашей базы данных, удалив ненужные представления. Количество документов в вашей базе данных не сильно влияет на скорость операций поиска с DigestSearch.

Главным недостатком DigestSearch является то, что он принимает только одно ключевое слово без групповых символов. В этом отношении метод похож на метод LotusScript View.GetDocumentByKey и на @DBLookup. То есть, если для вас важна производительность и поисковые слова предсказуемы, подумайте над использованием метода DigestSearch. Реализация DigestSearch в вашей базе данных не требует каких-либо значительных изменений дизайна или модификаций в существующих документах. Хотя в методе все еще есть место для улучшений (особенно в области индексации и поиска по нескольким ключевым словам), он может обеспечить существенное повышение производительности уже сегодня.

Примеры баз данных, рассмотренные в данной статье (Digestprofile.nsf (база данных документов Profile), Testindex.nsf (база данных Demo Index), Digest2.nsf (DigestSearch для простых операций поиска) и Demonab.nsf (демонстрационный каталог Domino)), расположены в файле Digest_dbs.zip, который доступен для загрузки в разделе Download.

В данной статье мы покажем два способа использования метода DigestSearch:

  • Как эффективную замену документов Profile и других типов документов временного хранения.
  • Как метод поиска в Domino Directory и возврата персональной информации для всех пользователей указанной группы.

В данной статье предполагается, что вы являетесь опытным программистом на Notes/Domino.

Производительность

При сравнении скорости поиска метода DigestSearch и традиционных методов поиска Domino, результаты которого показаны в следующих двух таблицах, вы можете увидеть, что при использовании одиночного ключевого слова для поиска одного документа, DigestSearch превосходит все другие методы, особенно если база данных размещена на сервере, и вы выполняете поиск из Notes-клиента. В нашем тесте производительности мы измеряли время, требующееся для получения описателя объекта для 100 документов. Мы измеряли результаты примера поиска в Domino Directory и эмулировали поиск, состоящий из нескольких этапов. Вы можете выполнить свой собственный тест, используя тестовый агент Performance в базе данных Digest Search 2 (Digest2.nsf).

Примечание: Этот тест производительности не предназначался для общего сравнением скорости методов; результаты применимы только для конкретной задачи поиска членов группы.

В первой таблице эмулируемые пользователи выполняли операцию поиска из Notes-клиента, а база данных размещалась на сервере:

Метод поиска  Время в секундах 
DigestSearch 2.9
Db.Search 13.1
Db.FTSearch 6.1
View.GetDocumentByKey 5.8
@DBLookup 12.1

Во второй таблице приведены результаты поиска из Notes-клиента в локальной базе данных:

Метод поиска  Время в секундах 
DigestSearch 1.2
Db.Search 9.7
Db.FTSearch 2.8
View.GetDocumentByKey 0.9
@DBLookup 1.2

Как можно увидеть, при локальной работе @DBLookup является вторым по скорости методом; но при работе с серверной базой данных он является предпоследним по скорости. Но на результаты мог повлиять возврат описателя документа вместо текста. Вторым интересным фактом является то, что Db.Search только на 30% медленнее при поиске на сервере, в то время как другие методы становятся медленнее как минимум в два раза.

Примечание: В этом тесте мы использовали @DBLookup с параметром Cache и с несколькими ключевыми словами, скомбинированными в одном и том же запросе, что не всегда возможно для реальных операций поиска.

Как работает DigestSearch

Метод DigestSearch реализуется полностью на LotusScript. Его основной код занимает примерно 30 строк. Вы можете использовать DigestSearch для поиска в фоновом режиме, а его синтаксис и функциональность напоминают метод View.GetAllDocumentsByKey("searchword", True). Главное сходство между этими двумя методами состоит в том, что оба они принимают одно ключевое слово в качестве параметра и возвращают один или более совпадающих документов. Отличия состоят в том, что DigestSearch не требует представлений для выполнения поиска и всегда ищет точное совпадение с искомым словом.

Метод DigestSearch вызывается при помощи следующей команды LotusScript:

Set doc=FastSearchByKey(db, "searchword")

Метод называется DigestSearch, потому что он использует значение уникального дайджеста (однонаправленная функция) для представления искомого слова. Этот уникальный ключ является закодированным искомым словом, представленным как строка из 32 символов, которая, в свою очередь, используется как Universal ID (UNID) для документа. Таким образом, DigestSearch на самом деле не выполняет поиск в базе данных; он просто проверяет, существует ли документ с конкретным UNID.

Вот простой пример выполнения операции поиска. Если вы поймете, как работает алгоритм, будет легко его изменить и настроить:

  1. Пользователь ищет номер телефона +1 212 12345678, для того чтобы найти имя его владельца.
  2. +1 212 12345678 преобразуется в дайджест при помощи функции @Password. Результат - строка дайджеста/хэша 3F915F67F52D35053113AAB40385FE46.
  3. Сценарий проверяет, существует ли в базе данных документ с UNID 3F915F67F52D35053113AAB40385FE46.
  4. Если документ найден, поиск завершается. Сценарий считывает поля из документа и отображает их пользователю, либо просто открывает документ в пользовательском интерфейсе. Если документ не найден, пользователь видит сообщение о том, что документ с таким ключевым словом не существует.

В следующем коде показан упрощенный рабочий пример @formula для выполнения поиска по дайджесту:

seachrword:="+1 212 12345678";
fullname:=@GetDocField(@Middle(@Password(seachword);1;32);
"FullName");@Prompt([Ok];"Result for "+searchword;
@If(@IsError(fullname);"Document "+searchword+" does not exist";
"Fullname: "+fullname))

А вот код агента LotusScript, используемый в предыдущем коде:

Option Public
Use "DigestSearchLib"
Sub Initialize
Dim session as New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument, curdoc as NotesDocument
Dim workspace As New NotesUIWorkspace
Dim uidoc As NotesUIDocument
Dim searchstring As String

Set uidoc = workspace.CurrentDocument
Set curdoc=uidoc.CurrentDocument
Set db=session.CurrentDatabase

searchstring=doc.inputfield(0) ' Ввод номера телефона в форму
' Выйти, если номер телефона не указан
If searchstring = "" Then Exit Sub
Set doc= FastSearchByKey(db, searchstring) ' Perform DigestSeach and
' возвратить описатель документа
If Not doc Is Nothing Then
curdoc.FullName=doc.FirstName(0)+" "+doc.LastName(0)
' заполнить поля текущего документа информацией 
' из найденного документа
curdoc.Address=doc.Address(0)
curdoc.Job=doc.JobDescription(0)
Else
' Документ не найден, проинформировать об этом пользователя
Msgbox "Info for "+searchtxt+" not found"
End If
End Sub

В фоновой библиотеке сценариев при работе этого агента выполняется следующий процесс: искомое слово преобразуется в дайджест при помощи метода @Password. Этот шаг формирует UNID-совместимую строку из 32 символов.

ev=Evaluate(/@Password("/+skey+/")/)

Библиотека проверяет, существует ли документ с этим UNID:

On Error 4091 Goto wrongiderr4091 Set digestdoc= digestdb.GetDocumentByUNID(Mid(ev(0),2,32))

Примечание: Вы должны обработать возможные ошибки Invalid universal ID (Неверный универсальный ID), которые будут возникать, если для искомого дайджеста нет соответствующего документа.

Если digestdoc не возвратил ошибку 4091 Invalid universal ID, сценарий передает описатель документа назад в вызывающую функцию:

Set FindDocByDigestKey=digestdoc
Exit Function
wrongiderr4091:
Set FindDocByDigestKey=Nothing

Сценарий показывает значения из найденного документа пользователю:

curdoc.FullName=doc.FirstName(0)+" "+doc.LastName(0)

Этот код показывает всю библиотеку DigestSearchLib LotusScript, содержащую ядро метода DigestSearch:

' ---- начало библиотеки сценариев DigestSearchLib ------
Dim digestdb As NotesDatabase
Dim digestdoc As NotesDocument
Dim lastkey As String
Dim lastgeneratedID As String
Dim lastgeneratedDoc As NotesDocument

Function FindDocByDigestKey(custdb As NotesDatabase,skey As String)_
As NotesDocument ' это главная функция для получения результатов поиска
Dim unid As String
Set digestdb=custdb
On Error Goto errh
unid=CalculateDigest(skey)
If Len(unid)<>32 Then
Set FindDocByDigestKey=Nothing
Exit Function
End If
Set digestdoc = lastgeneratedDoc
' lastgeneratedDoc является глобальной переменной 
' возврат описателя документа вызывающему агенту
Set FindDocByDigestKey=digestdoc
Set digestdoc=Nothing
Exit Function
errh:
Set FindDocByDigestKey=Nothing ' для этого ключевого слова документы не найдены
Resume endas
endas:
End Function

Function IsDigestKeyTaken(unid As String)
' эта функция проверяет, существует ли документ для ключевого слова
On Error Resume Next
' ошибка 4091 означает неверный Universal ID
On Error 4091 Goto wrongiderr4091
' проверяет, существует ли уже документ с уникальным ключевым словом
Set digestdoc= digestdb.GetDocumentByUNID(unid) IsDigestKeyTaken=True
Set lastgeneratedDoc=digestdoc
Exit Function
wrongiderr4091:
IsDigestKeyTaken=False
Resume wrongID
wrongID:
End Function

Function CalculateDigest(skey As String)
'эта функция вычисляет 32-символьный дайджест для ключевого слова
Dim unid As String
'вычислить дайджест для ключевого слова
ev=Evaluate(/@Password("/+skey+/")/)
unid=Mid(ev(0),2,32) 'удалить круглые скобки вокруг сгенерированного дайджеста
lastgeneratedID=unid 'присвоить глобальной переменной новое значение
If IsDigestKeyTaken(unid)=False Then
unid="no doc with that digest yet"
End If
CalculateDigest=unid
End Function

Sub MakeSearchable(sdoc As NotesDocument)
' эта функция устанавливает новый Universal ID и сохраняет документ
unid=lastgeneratedID
sdoc.UniversalID=unid
End Sub
' ----- конец библиотеки сценариев ------

Использование DigestSearch для работы с документами Profile

Вы, возможно, заметили, что Lotus Notes кэширует документы Profile. Такое поведение может быть проблемой, если два или более пользователей одновременно изменяют документ Profile. Из-за проблем кэширования пользователи получают устаревшие значения. Но вы можете решить эту проблему, используя метод DigestSearch вместо стандартной функциональности GetProfileDocument, как показано в следующем фрагменте кода:

Set db=session.CurrentDatabase
' Выполнить поиск и возвратить описатель документа
Set profiledoc = FastSearchByKey(db, "My Profile 1")
If Not profiledoc Is Nothing Then
MsgBox profiledoc.Field1(0) ' показать поле из документа профиля 
profiledoc.Field2=Cstr(Now) ' обновить профиль новым значением поля
Call profiledoc.save(True,False) 'сохранить документ профиля
End If

Вы можете даже использовать язык формул для доступа к новым документам Profile с дайджестом, как показано в следующем коде:

profname:="My Profile 1";
comment:=@GetDocField(@Middle(@Password(profname);1;32); "comment");
createddate:=
@GetDocField(@Middle(@Password(profname);1;32); "doccreated");
@Prompt([Ok];"Result for "+profname; @If(@IsError(comment);
"Profile "+profname+"does not exist";
"Comment: "+comment+@Char(10)+"Created: "+createddate))

К сожалению, вы не можете создать новый документы Profile с дайджестом, используя язык Notes @formula. Вы можете только искать существующие документы и изменять их (используя @SetDocField). В разделе Download данной статьи приведена ссылка на ZIP-файл, включающий базу данных "DigestSearch demo for profile docs" (Digestprofile.nsf), которая содержит исходный код агента "Modify example with Formula" и примеры. Эта база данных содержит также оба кода LotusScript и @formula для тестирования (см. рисунок 1). Нажмите кнопку "Create profile doc" в виде Instructions для создания нового профиля, а затем нажмите кнопку "Find profile doc" для поиска только что созданного вами профиля.

Рисунок 1. Кнопки действия для тестирования профиля

Использование DigestSearch для работы с простыми операциями поиска

Работа с операциями поиска сложнее работы с документами Profile. Число документов Profile часто ограничено; они создаются по требованию и не зависят от других документов. Ситуация меняется, когда вы выполняете поиск в базе данных, содержащей сотни тысяч документов, которые связаны друг с другом и с другими базами данных.

Для поддержания целостности существующих баз данных вы не можете менять UNID документов в этих базах данных напрямую. Следовательно, вам необходима специальная база данных mirror/index, в которой хранятся индексные документы для документов в родительской базе данных.

Для базы данных индексов не нужно какое-либо проектирование; она просто содержит два типа документов:

  • Документы-контейнеры ссылок.
  • Документы-контейнеры ключевых слов.

Один документ-контейнер ссылки (форма SourceRefHolder) существует для каждого индексированного документа в исходной базе данных. Этот документ-контейнер ссылки содержит два динамически создаваемых поля: SKey и REFUNIDs. Поле SKey используется только как напоминание UNID оригинального документа в исходной базе данных; оно не используется для поиска. Поле REFUNIDs является многозначным полем, которое используется для хранения последовательности документов-контейнеров ключевого слова. То есть, поле содержит значения UNID документов-контейнеров ключевого слова.

Количество документов-контейнеров ключевых слов для исходного документа равно количеству полей поиска каждого документа. Эти поля указываются в документе Configuration, как показано на рисунке 2. Каждый индексный документ тоже имеет многозначное поле UNIDs, которое содержит значение UNID всех документов исходной базы данных, соответствующих ключевому слову, присвоенному этому индексному документу.

Рисунок 2. Документ Configuration

Исходный код операции поиска аналогичен примеру с документом Profile, описанному ранее. Отличия таковы:

  • Для поиска используется база данных Demo Index (Testindex.nsf) вместо текущей базы данных.
  • В специальном документе хранится последовательность нескольких ключевых слов.

Предполагая, что база данных Demo Index обновлена и содержит все документы из исходной базы данных (то есть, Domino Directory), операция поиска выдает те же результаты, что и при поиске в исходной базе данных.

Существует три основных способа обновления базы данных Demo Index новыми документами:

  • Выполнение сценария QuerySave с документами в исходной базе данных.
  • Использование запланированного агента (scheduled agent).
  • Использование дополнительной серверной программы, которая в фоновом режиме выполняет обновление сразу же после изменения документов.

Примечание: База данных Digest Search 2 (Digest2.nsf) содержит агент под названием Process database index, который создает индекс для документов из исходной базы данных.

Пример поиска в Domino Directory

В пример базы данных Digest Search 2 включены два агента, выполняющие поиск в Domino Directory. Один агент (Find users by first name) ищет все документы Domino Directory, в которых имя человека совпадает с введенным именем в поле вода. Второй агент (Find group members) ищет все документы Person для людей указанной группы. Для запуска примеров агентов вам необходимы три базы данных, показанные на рисунке 3:

  • Digest Search 2 (Digest2.nsf).
  • База данных Demo NAB (Demonab.nsf); вы можете также использовать копию вашего Domino Directory.
  • Пустая база данных Demo Index (Testindex.nsf) для хранения индекса.

Рисунок 3. Базы данных для примеров агентов

Все три эти базы данных содержатся в ZIP-файле, ссылка на который приведена в разделе Download данной статьи.

В базе данных Digest Search 2 вы должны настроить месторасположение баз данных Demo NAB и Demo Index, как показано на рисунках 2 и 4. После настройки путей к этим базам данных вы должны заполнить базу данных индексов информацией из Demo NAB. Для этого нажмите кнопку Synchronize Index в виде Configuration (см. рисунок 4).

Рисунок 4. Кнопки действия в виде Configuration
Action buttons in Configuration view

После завершения индексации вы можете выполнить операцию поиска. Просто нажмите "Find persons by first name" или "Find all users in a group". Затем введите имя пользователя или название группы и нажмите кнопку OK. В нашем примере в качестве имени пользователя используется John. Вы могли бы изменить его на соответствующее имя, существующее в вашем каталоге (см. рисунок 5).

Рисунок 5. Поиск по имени

Если все настроено правильно, появится окно, аналогичное показанному на рисунке 6.

Рисунок 6. Результаты поиска по имени

При нажатии "Find all users in a group" в фоновом режиме происходят следующие события:

  1. Сценарий идентифицирует соответствующий индексный документ (то есть, документ-контейнер ключевого слова) для ключевого слова listname=demogroup.
  2. В этом индексном документе сценарий идентифицирует UNID исходного документа в исходной базе данных.
  3. Сценарий считывает поле Members из исходного документа и для каждого человека из этого поля сценарий выполняет новую операцию поиска с новым сформированным ключевым словом "fullname="+doc.members(x).
  4. Операция поиска повторяется до тех пор, пока не обработаются все люди в списке, а затем в текстовой строке формируется ответ.
  5. MessageBox отображает пользователю полученную текстовую строку.

Когда использовать (а когда нет) DigestSearch

Используйте DigestSearch тогда, когда вам требуется быстро найти один или более документов по одному ключевому слову. Документы Profile и документы для временного хранения данных являются наиболее очевидными областями применения. Аналогично, используйте DigestSearch тогда, когда по каким-либо причинам вы не можете применить традиционные методы поиска, но необходим высокопроизводительный поиск (предпочтительно, для статических данных, например в Domino Directory).

Вот практические примеры ситуаций, когда вы должны подумать об использовании DigestSearch:

  • Найти документ Person по номеру Social Security или телефонному номеру.
  • Найти продукт по его артикулу.
  • При синхронизации с SQL для определения существования записи в базе данных Notes по ее уникальному номеру.
  • Для получения конкретных полей из пользовательских документов в Domino Directory для всех членов конкретной группы.

Вот практические примеры ситуаций, когда не нужно использовать DigestSearch:

  • Скорость операций поиска удовлетворительна в случае применения стандартных методов поиска (<1 секунды).
  • Ваша база данных невелика (<5000 документов).
  • Вы должны определить более чем одно ключевое слово для поиска (например, Form="Document" & Status="Active").
  • Вы должны использовать более гибкие ключевые слова для поиска (например, @Left(Product;2)="Di", @Created=@Today).
  • Количество документов, возвращаемых как результаты поиска для конкретного ключевого слова, больше 500.

Метод DigestSearch первоначально был создан для повышения скорости синхронизации большого количества SQL-записей с базой данных Domino. Каждая SQL-запись имеет свой собственный Unique Sequence Number, и вы можете вычислить также индивидуальный уникальный номер для всех комбинированных полей в записи. Каждая запись существует только в одном экземпляре, что делает их отличным объектом для применения метода DigestSearch. Вы можете избежать кэширования существующих документов (в нашем случае было более 1000000 документов) в массиве или в списке, и время синхронизации может быть уменьшено с 25 минут до 10 минут.

Что нужно сделать в следующей версии DigestSearch

Как упоминалось ранее, в методе DigestSearch есть место для улучшений. Некоторые проблемы легко преодолеть, тогда как для других необходимы новые подходы. Вот некоторые изменения, которые мы хотели бы увидеть в будущих версиях метода DigestSearch:

  • Агент, обновляющий индексную базу данных новыми документами, в настоящее время довольно медленен и не оптимизирован, что может быть исправлено путем добавления пары отслеживающих полей в индексные документы.
  • Проверить возможность реализации инкрементной индексации.
  • Разработать серверное дополнение, которое автоматически добавляет новые и модифицированные документы из исходной базы данных в индексную базу данных.
  • Использовать один индексный документ для хранения ссылок на 4096 исходных документов вместо одного индексного документа для каждого исходного документа. Потребуется дополнительный анализ для определения влияния этого изменения на производительность.
  • Разрешить использование нескольких ключевых слов для поиска (например, по имени и по городу). Можно реализовать эту функциональность путем сравнения результатов из двух или более отдельных операций поиска и устранения несовпадающих результатов. .
  • Сделать возможным использование несколькими исходными базами данных одной индексной базы данных.
  • Рассмотреть возможность реализации технологии поиска B-Tree для улучшения производительности и управляемости индекса. В настоящее время мы исследуем, может ли применение специализированного алгоритма B-Tree сделать возможными поиск с использованием групповых символов и более компактное хранение индексных документов. Вместе со способностью DigestSearch мгновенно искать конкретное ключевое слово, это могло бы стать идеальным решением.

Скорость имеет значение

В нормальных условиях DigestSearch выдает результаты быстрее, чем традиционные методы поиска. Если вам не нужны богатые возможности развитых запросов, предлагаемые FTSearch и DBSearch, но вы ищете альтернативу методу GetDocumentByKey или @DBLookup для документов Profile, загрузите примеры баз данных и посмотрите, полезен ли будет вам метод DigestSearch. С помощью примеров, включенных в эту статью, и примеров баз данных, вы можете протестировать функциональность метода DigestSearch в течение минут и реализовать его для ваших собственных приложений, просто изменив документ Configuration.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3007