— ...Для практических целей теорию поисков надо модифицировать. Больше того, надо коренным образом изменить главную посылку и вернуться к первоначальной концепции Потерянного и Найденного.
Р. Шекли. «Обмен разумов»
Предлагаемая статья адресована администраторам и разработчикам баз данных, чьи задачи связаны с управлением и программированием доступа к разнородным источникам информации, в том числе нереляционной природы. В качестве примера источника слабоструктурированных данных мною выбран Microsoft Index Server (начиная с Windows 2000, Microsoft Indexing Service).
Алексей Шуленин системный инженер отдела бизнес-приложений российского представительства Microsoft. Имеет сертификаты MCSE, MCDA, MSS. С ним можно связаться по адресу: rusdev@microsoft.com. |
Можно было бы сказать, что Windows 2000 будет поставляться вместе с Index Server 3.0 (во всяком случае, MajorVersion в реестре устанавливается в 3), но, как известно, все перечисленные серверы приложений вошли в нее по умолчанию на правах служб. В дальнейшем под термином «Index Server» мы будем также подразумевать и Indexing Service в составе Windows 2000.
В качестве объектов поиска Index Server выступают отдельные слова, словоформы и фразы в содержании документа, а также его свойства (автор, название, тема и др.). Области поиска могут располагаться как на локальном компьютере, так и в сети, в том числе на машинах без установленной службы поиска. Index Server работает на Windows NT Server 4.0 как компонент Internet Information Server либо на Windows NT Workstation 4.0 c Peer Web Services. В документации не содержится каких-либо ограничений на количество или объем индексируемых файлов. Во всяком случае решения на его основе, оперирующие миллионами документов общим объемом несколько десятков гигабайт, не являются чем-то исключительным. Если по соображениям масштабируемости систему предстоит развертывать не на одном, а на нескольких Web-серверах, следует рассмотреть поисковые возможности Microsoft Site Server.
Существенным этапом в истории Index Server стал выход в декабре 1998 г. Microsoft SQL Server 7.0. Во-первых, в этой версии появились собственные механизмы полнотекстового поиска по строковым и текстовым полям, во многом основанные на принципах работы Index Server. Во-вторых, OLE DB-провайдер для Index Server (MSIDXS, известный также под названием Monarch) распространил возможности универсального доступа к данным на область полнотекстового поиска. Ранее приложения, работающие с документами, и приложения, работающие с данными, представляли собой два довольно различных класса задач. С выходом OLE DB-провайдера для Index Server появилась возможность на практике объединить документы и данные, используя стандартные технологии доступа и инструменты разработки независимо от типа ресурса.
Index Server глазами DBA
Полнотекстовый каталог можно смело уподобить базе данных, каталоги — таблицам, файлы — записям. Полнотекстовый каталог является высшим уровнем иерархии объектов внутри Index Server. Каталог состоит из подкаталогов. В качестве каталога может выступать виртуальный или физический каталог: локальная папка файловой системы либо сетевой ресурс, такой, как папка общего доступа (shared folder). Сразу после установки Index Server в нем будет содержаться системный каталог System и, если на данной машине задействована служба WWW, каталог Web. К последнему относятся все виртуальные каталоги IIS, в свойствах которых отмечен флажок Index this resource. В случае Windows 2000 каталог System находится в системном разделе диска. Каталог Web по умолчанию находится в Inetpub. Вновь создаваемым каталогам администратор сам назначает место на диске. В файловой системе каталог выглядит как папка Catalog.wci, которая хранит всю индексную информацию о подкаталогах, относящихся к данному каталогу, и содержании находящихся в них файлов. Соответствующая информация хранится в разделе реестра HKEY_LOCAL_MACHINESYSTEM CurrentControlSetControlContentIndex. Каталоги перечислены в подразделе Catalogs, подкаталоги для каждого каталога — в подразделе Scopes. Эта информация полезна тем, что некоторыми настройками работы Index Server можно управлять только через реестр. Но в большинстве случаев этого практически не требуется.
Признак каталога представляет собой двухбитную величину. Младший бит равен 0, если каталог не индексируется (Include in index = No в его свойствах при просмотре с консоли Index Server), и 1 — в противном случае. Старший бит равен 0, если это виртуальный каталог, и 1, если физический. Например, параметр 3 соответствует виртуальному каталогу, участвующему в индексе.
Таким образом, в случае Index Server индексы создаются не на уровне «таблиц», а на уровне «базы данных», причем наша полнотекстовая «база данных» может иметь всего один индекс. В свойствах каталога (закладка General) видны текущие размеры индекса и кэша свойств. Конкретные «таблицы» указываются впоследствии в тексте запроса. В отличие от обычных индексов, присущих СУБД, операции модификации «записей» (создание, удаление и изменение файла в индексированном каталоге) не вызывают немедленного изменения индекса, так как, учитывая специфику его структуры и возможные размеры, это сильно замедляло бы работу остальных файловых служб. Существует два способа обновления индекса: сканирование и обновление на основе уведомлений. Первый способ состоит в том, что Index Server обходит все подкаталоги данного каталога, помеченные к индексированию, и выбирает из них файлы, удовлетворяющие подключенным фильтрам. Для каждого подкаталога операция сканирования может быть вызвана явно из административного интерфейса (контекстное меню каталога -> All Tasks -> Rescan ...). В случае полного сканирования (full rescan) все содержащиеся в нем документы переиндексируются. Эта операция выполняется автоматически для нового подкаталога, добавленного в каталог. Очевидно, что при большом количестве документов процесс полного сканирования может занять довольно много времени, поэтому к нему стоит прибегать только при крайней необходимости (например, при восстановлении после сбоя). Дифференциальное сканирование (incremental rescan) отличается от полного тем, что выбираются документы, изменившиеся с момента последней индексации. Дифференциальное сканирование будет проведено, например, если на какое-то время остановить службу контекстного индексирования CiSvc.exe, а затем вновь запустить ее. Для томов формата NTFS 5.0 Index Server определяет модифицированные файлы на основе Update Sequence Number (USN) из журнала изменений файловой системы, поэтому затраты на сканирование в этом случае минимальны. В процессе нормальной работы службы преимущественно используется второй способ обновления индекса. При этом Index Server полагается на уведомления операционной системы, направляемые ему всякий раз при модификации файла, находящегося в его зоне ответственности. Очевидно, что этот способ требует наименьших временных затрат, но, к сожалению, он работает, только если каталог находится под управлением NT. Иначе (для Windows 9x, Novell NetWare, Unix) сканирования не избежать. Если от каталогов уведомлений не приходит, периодичностью обхода можно управлять с помощью параметра ForcedNetPathScanInterval (по умолчанию, 2 часа) вышеуказанного раздела реестра. Если же обновления документов производятся слишком часто, буфер уведомлений переполняется, и новые уведомления попросту теряются. В этом случае при следующем обходе Index Server обнаружит изменившиеся документы, не отраженные в индексе, и проиндексирует их. Т. е. вместо механизма уведомлений здесь будет работать дифференциальное сканирование, соответственно, обновление индекса произойдет с некоторым запозданием.
Документы, помеченные к (пере)индексированию, выбираются из очереди и подвергаются воздействию фильтра. Фильтром в терминологии Index Server называется динамическая библиотека, которая извлекает из документа его содержимое (в виде потока текста) и свойства (автор, размер, дата создания и т. д.). Нужный фильтр определяется по расширению документа. Например, для расширения .doc параметр HKEY_CLASSES_ ROOT.docPersistentHandler имеет параметр {98de59a0-d175-11cd-a7bd-00006b827d94}. HKEY_CLASSES_ ROOTCLSID{98de59a0-d175-11cd-a7bd-00006b827d94}PersistentAddinsRegistered{89BCB740-6119-101A-BCB7-00DD010655AF} равно {f07f3920-7b8c-11cf-9be8-00aa004b9986}, откуда из HKEY_ CLASSES_ROOTCLSID {f07f3920-7b8c-11cf-9be8-00aa004b9986}InprocServer32 получаем название библиотеки фильтра — C:WINNTSystem32offfilt.dll. По умолчанию, Index Server содержит фильтры для документов формата HTML (nlhtml.dll), Microsoft Office (offilt.dll) и MIME (mimefilt.dll), а также для бинарных и простых текстовых файлов (непосредственно в query.dll). Фильтры можно рассматривать как подключаемые модули, и никто не мешает написать фильтр для нужного типа файлов. Например, если у нас есть книготорговый сайт, где каждый документ — аннотация книги, то в свойства можно включить ее стоимость. Для этого необходимо реализовать интерфейс IFilter, описанный в http://msdn.microsoft.com/isapi/ msdnlib.idc?theURL=/library/psdk/ indexsrv/ixufilt_8msl.htm. Параметр DLLsToRegister упомянутого выше раздела ContentIndex содержит список библиотек, автоматически регистрируемых при старте службы индексации, в том числе фильтры. На самом деле фильтры выполняются в контексте дочернего процесса CiDaemon, который порождается CiSvc. Это сделано для того, чтобы неправильно написанный фильтр не остановил основной процесс Index Server. Если фильтр не задействуется дольше значения, заданного параметром FilterIdleTimeout, то для экономии памяти он выгружается. Другая ситуация — когда самодельный фильтр работает, но «спотыкается» или, наоборот, зацикливается на каком-нибудь документе. Параметры FilterRetries и FilterRetryInterval регулируют количество попыток проиндексировать документ и интервалы между ними. Параметр MaxFilesizeMultiplier = n ограничивает размер текстового потока на выходе фильтра: если он превышает размер файла в n раз, файл считается поврежденным и отбрасывается. Кстати, для файлов, чей размер больше MaxFilesizeFiltered, фильтр возвращает только свойства, но не содержимое.
Возвращаемый IFilter::GetText поток текста подается на вход делителя (Word Breaker), который разбивает его на слова. Здесь в действие вступают библиотеки локализации. Они разбирают грамматические конструкции, присущие языку. Например, для США и западноевропейских языков — это infosoft.dll, для японского языка — msir2jp.dll и т. д. Сведения об установленной языковой поддержке можно найти в подразделе Language раздела ContentIndex. Так, ...Language <язык>WBreakerClass содержит CLSID словоделителя, а в HKEY_ CLASSES_ROOTCLSID{<данный CLSID>}InprocServer32 — имя соответствующего файла. По умолчанию, для определения языка документа, Index Server использует системную информацию о региональных установках или значения, указанные в метатеге MS.Locale (для HTML-файлов). Написание собственного модуля выделения слов — задача более нетривиальная, чем добавление фильтра. Тем не менее уже сегодня несколько отечественных производителей программного обеспечения предлагают свои расширения для полнотекстового поиска в Index Server, SQL Server и Site Server с учетом специфики русского, украинского и других языков стран Содружества. После словоделения происходит «подавление помех»: из текста выбрасываются незначащие слова (noise words), такие, как предлоги, артикли, междометия и т. д. Список незначащих слов для каждого языка хранится в файле, указанном в ...Language<язык>NoiseFile. Например, для английского языка файл называется noise.eng, для немецкого — noise.deu и т. п. По умолчанию, они лежат в system32. Это обычные текстовые файлы, которые можно редактировать в зависимости от специфики конкретного приложения. К зависящим от языка компонентам относится также дифференциатор — модуль варьирования лексической формы слова (stemming), генерирующий производные слова от исходной основы. Например, от drink порождаются drinking, drank, drunk и т. д. Он напрямую не относится к процессу построения индекса, но задействуется при обработке запроса. Полнотекстовый запрос к Index Server точно так же подвергается разбиению на слова и отбраковке незначащих слов; кроме того, те слова, по которым не требуется точного текстового совпадения, проходят через дифференциатор для генерации списка словоформ. CLSID дифференциатора указан в ...ContentIndexLanguage<язык>StemmerClass.
После выделения значащих слов процесс построения индекса сохраняет их в памяти в виде списков слов (word lists). Списки слов можно рассматривать как временные оперативные индексы по кэшированным данным. Очевидно, что в зависимости от объема памяти они возможны только для ограниченного числа документов. Максимальное количество списков в памяти контролируется параметром MaxWordLists раздела реестра ...ContentIndex (по умолчанию, 20). Списки начинают создаваться, если объем свободной памяти превышает MinWordlistMemory (по умолчанию 5 Мбайт). Максимальный объем памяти, которая может отводиться под список, задается в MaxWordlistSize (измеряется в порциях по 128 Кбайт, по умолчанию 2,5 Мбайт). Как водится, чем больше размер кэша, тем быстрее (в общем случае) выполняются запросы. Если реально списки начинают занимать больше памяти, чем MinSizeMergeWordlists (по умолчанию 1 Мбайт) или их количество превышает MaxWordLists, они выгружаются на диск в так называемые «теневые индексы» (shadow indexes). Несмотря на то, что теневые индексы лежат на диске, они не сохраняются после перезапуска Index Server. Когда теневых индексов становится больше, чем MaxIndexes (по умолчанию 25), некоторые из них объединяются друг с другом. Главный индекс (master index) также хранится на диске, но он имеет гораздо более эффективную структуру хранения по сравнению с теневыми индексами как с точки зрения сжатия информации, так и по скорости ее выборки при обработке запросов. Обычно теневые индексы добавляются в главный индекс по расписанию. MasterMergeTime указывает количество минут после полуночи, когда должен инициироваться процесс построения главного индекса. Кроме того, он может вызываться, если совокупный размер теневых индексов превышает MaxShadowIndexSize, если свободного места на диске, где хранится каталог, остается меньше MinDiskFreeForceMerge и если количество файлов, относящихся к каталогу, изменившихся с момента последнего обновления главного индекса, превысило MaxFreshCount. Наконец, его можно вызвать принудительно, выбрав All Tasks -> Merge из контекстного меню каталога. Процесс построения главного индекса требует довольно больших затрат времени и ресурсов, поэтому он устроен так, что если Index Server в какой-то момент будет остановлен, то в следующий раз процесс возобновится не сначала, а с того места, на котором был прерван. Итак, содержание документов хранится в списках слов, теневых индексах и главном индексе. Свойства документов сразу после этапа фильтрации попадают в кэш свойств. Это дисковая структура, в которой Index Server также сохраняет некоторую служебную информацию и те атрибуты документов, которые он может установить, не применяя фильтра, например путь, размер и т. п. Для ускорения выборки кэш свойств может 64-килобайтными страницами загружаться в память. Максимальное число страниц в памяти контролируется PropertyStoreMappedCache. Кэш свойств, списки слов, теневые индексы и главный индекс составляют содержимое полнотекстового каталога.
Index Server API
Традиционным способом подготовки статических запросов к службе полнотекстового поиска является использование .ida/.idq-файлов. Idq-файл определяет полнотекстовый запрос. Он состоит из секций, в которых указываются имена полей и свойств, а также сам запрос. Этот файл затем может вызываться из обычной HTML-страницы или ASP-сценария: