понедельник, 20 апреля 2009 г.

Полнотекстовый поиск: MS Full-Text Search

Вторая часть серии о полнотекстовом поиске несколько задержалась - работа не давала продохнуть. Впрочем, это лирика, приступим к делу.

Итак, я не буду повторять массу открыто доступной информации о Microsoft Full-Text Search (общее описание от Microsoft (eng/рус) и Wikipedia, архитектура), не буду и переписывать простые и не очень примеры. Сосредоточимся на следующем сценарии: у нас имеется некоторое не слишком сложное приложение (веб-приложение, два-три слоя, до несколько Гб данных (до десятка-другого), пара-тройка вспомогательных сервисов) и нам необходимо обеспечить "интеллектуальный поиск" для некоторых сущностей этого приложения. "Интеллектуальность" поиска - маркетинговый прием, которым sales/accounts привлекают пользователей. С чисто технической точки зрения за ним может скрываться следующее:

  • находить не только точные вхождения слов из поискового запроса, но и словоформ (мама/маме/мамы/маму ...)
  • находить не только слова, введенные пользователем, но и синонимы
  • ранжировать результаты поиска по релевантности
  • искать не только по атрибутам сущности, но и в теле файла, который с этой сущностью связан
  • выполнение сложных запросов (вроде "чтобы вот эти два термина находились поблизости", задание весов для слов в поисковом запросе и много чего еще)

Из описания можно заключить что скорее всего этот функционал все-таки в большей степени ориентирован на конечного пользователя (и скорее всего "продвинутого пользователя") и врядли будет использоваться в каких-то вспомогательных сервисах (хотя как знать ...). Теперь рассмотрим насколько сложно/просто будет все это сделать и отгрузить заказчику. Обратимся к следующим основным вопросам: изменения в архитектуре приложения, поддержка/сопровождение и альтернативы.

Архитектура

Здесь Microsoft потрудилась на славу: технология достается нам бесплатно не только в смысле денег (идет в комплекте с SQL Server и есть даже у SQL Server Express (with Advanced Services)) но и в смысле интеграции. Вся сложность ложится на плечи SQL Server-а:

  1. Создаем полнотекстовый каталог
  2. Создаем полнотекстовый индекс
  3. Используем специальные функции (CONTAINS, CONTAINSTABLE, FREETEXT, FREETEXTTABLE) для обращения к индексам из T-SQL кода

Таким образом, код приложения может быть полностью абстрагирован от того, каким образом получены данные: с применением полнотекстового поиска или без него. В обмен на эту простоту на стороне СУБД нас поджидает:

  1. Необходимость проектирования физического уровня: сколько каталогов нам нужно и где они будут храниться
  2. Логический уровень: какие индексы нам нужны, какие колонки индексировать
  3. Подробности алгоритмов обработки данных: стоп-список (noise words (в SQL Server 2005 - фиксированный, один на сервер, в SQL Server 2008 можно создавать пользовательские)), алгоритм разбиения на слова (word breaker), выделение словоформ (stammer), фильтрация содержимого (content filters)

Первые два уровня для начала/в несложных случаях, пожалуй, можно отдать на откуп SQL Server-у, а вот третий пункт таит в себе массу подводных камней, которые могут значительно усложнить жизнь "среднестатистическому разработчику":

  • стоп-список: слова, входящие в него, попросту игнорируются и не включаются в индекс (поэтому неудивительно, что найти их тоже не получится)
  • разбиение на слова: зависит от выбранного языка (локали), может быть нейтральным (по пробелам/знакам препинания)
  • выделение словоформ: зависит от языка, список доступных языков можно узнать из системного представления sys.fulltext_languages (зависит от версии SQL Server, в 2005м нужно было некоторые языки (в том числе и русский) регистрировать вручную). Если выберем "неправильный" язык (например в поле, индексируемом согласно правилам английского языка, будем хранить русский текст), то словоформы работать не будут - все слова будут искаться "как есть" (буквально). Проблемы также возникнут с синонимами и "похожестью" слов.
  • фильтрация содержимого: фактически дает возможность проиндексировать документы, хранящиеся в БД (в поле типа image/varbinary(max)). Хорошая новость заключается в том, что поддерживаются все форматы, которые "понимает" Windows, на которой выполняется SQL Server - нужно лишь задать специальное поле, в котором для данной строки будет храниться формат потока (фактически расширение файла).

Самая большая проблема, конечно же, заключается в правильной обработке мультилокальных данных (и в нашем глобальном плоском мире с каждым днем все меньше и меньше приложений, который могут обойтись поддержкой одного языка). Альтернатив целых две:

  1. Хранить данные для всех языков в одних и тех же полях вперемешку - проще всего, но теряется значительная часть функциональности
  2. Для каждого языка создавать отдельное поле/поля (например Title_EN, Title_RU и т.д.) - функциональность остается при нас, но придется довольно много "плясать" вокруг этих данных (в том числе и в приложении - чтобы правильно сохранить/загрузить)

Что бы мы ни выбрали в конечном итоге, за языковыми настройками индексации каждого поля (как и за collation ;-) ) надо следить.

Поддержка/сопровождение

Здесь хорошая новость заключается в том, что полнотекстовые каталоги являются частью базы данные и, как следствие, входят в состав резервной копии/восстанавливаются. Более сложным является вопрос поддержания полнотекстовых индексов в актуальном состоянии. Здесь есть два основных варианта:

  • автоматическое наполнение/обновление (CHANGE_TRACKING AUTO): сервер сам будет следить за актуальностью индексов - хорошее решение со многих точек зрения, но могут возникнуть некоторые неожиданные побочные эффекты, связанные с тем, что к нашей базе данных будет обращаться еще один (системный) процесс, накладывающий некоторое количество блокировок
  • наполнение/синхронизация индексов по запросу (CHANGE_TRACKING MANUAL): больше ответственности, но и больше уверенности в том что и когда мы делаем - может потребоваться в основном при большой загрузке, когда мы хотим максимально разгрузить сервер БД в течении рабочего времени и готовы смириться с некоторой степенью неактуальности информации в индексах

Довольно сложно дела обстоят и с мониторингом/оптимизацией производительности. Microsoft не разглашает внутреннюю структуру хранения полнотекстовых индексов поэтому на них не распространяется опыт оптимизации "обычных" индексов. Единственное, что можно сказать наверняка: чем больше индексов (в том числе и полнотекстовых) "навернуто" на таблицу, тем больше времени будут занимать операции добавления/обновления/удаления данных.

Ниже приведено сравнение планов добавления записи в две одинаковые таблицы, отличающиеся только тем, что для одной из них создан полнотекстовый индекс по одному из полей с автоматическим обновлением. Та часть плана первого запроса, которая не влезла на экран, полностью совпадает с планом второго запроса.

image

Выводы делаем сами (впрочем, я думаю и так понятно, что бесплатного в этом мире ничего не бывает).

Альтернативы

Если не рассматривать в качестве альтернативы переход на другую СУБД (в PostgreSQL, Oracle и MySql есть аналогичные технологии), то остается применение неких внешних по отношению к нашему приложению сервисов, производящих индексацию содержимого. Тут можно упомянуть:

  • Windows Search - встроен в Windows (следовательно "бесплатен"), позволяет индексировать файлы, хранящиеся на компьютере
  • Google Desktop - аналогичный продукт от Google
  • Apache Lucene / Solr - индексатор / поисковый сервер от Apache Foundation

Первые два продукта скорее ориентированы на индексацию файлов и подойдут скорее в том случае, если перед нами поставлена задача индексировать какие-то документы (довольно широкая тема), в то время как последний - именно внешний индекс, который может "втянуть" почти все, что угодно, физически представляет из себя веб-службу, поддерживает уйму разного функционала, распределенные индексы, очень гибко настраивается и много чего еще. Об этом чудо-средстве я планирую написать отдельный пост (а может быть и не один).

 

HTH

AlexS

2 комментария:

  1. есть еще один вариант для хранения мультилокальных данных - данные хранить в Xml, и использовать аттрибут xml : lang.
    Небольшой пример есть здесь
    www.simple-talk.com -- sql -- learn-sql-server
    -- sql-server-full-text-search-language-features

    ЗЫ: -- надо заменить на слеш, а то возникли проблемы с валидацией :(

    ОтветитьУдалить
  2. Спасибо за наводку - почитал, теперь надо обмозговать.

    ОтветитьУдалить