Полнотекстовый поиск по файлстримовским полям. Введение

Полнотекстовым поиском в СУБД называется возможность отыскивать не только точные совпадения строки, но и ее вариации по словоформам в соответствии с правилами заданного языка, а также поиск синонимов в соответствии с тезаурусом, поиск фразы в терминах близости искомых слов и прочая нереляционная хрень. Поскольку нередки случаи, когда СУБД используется как помойка документов, в которой время от времени требуется что-то отыскать, не помня ни даты, ни названия, вообще ничего, кроме каких-то обрывочных фраз из содержания, производители уже достаточно давно озаботились встраиванием соответствующих возможностей в свои детища. Полнотекстовый поиск в составе SQL Server появился в версии 7.0, вышедшей в конце 1998 г. (см., напр., http://www.infocity.kiev.ua/db/content/db024.phtml , п.3.3). Полнотекстовый поиск работает над полями строковых и текстовых типов (1). Также поддерживаются поля типа image / varbinary(max) (2). В отличие от (1) предполагается, что в (2) лежит не текст, а голая бинарщина – содержание файла в формате какого-нибудь текстового редактора типа MS Word, Adobe Reader и пр. На конвейере полнотекстовой обработки стоят по порядку следующие станки, станки, станки:

Filter – для (2) преобразует бинарщину в поток текста в зависимости от формата документа;

Word breaker – подхватывает текст с выхода Filter или непосредственно из поля (1) и выделяет в нем границы слов в соответствии с правилами языка, т.е. разбивает на слова;

Stemmer – подхватывает слова с выхода брейкера и преобразует их к некоей начальной форме. Сейчас по барабану, является ли ей именительный падеж для существительного или инфинитив для глагола или корень для всего – не суть. Стеммер можно себе представить в виде справочника, айдишниками которого выступают некие корневые основы всех слов языка, от каждой из которых тянутся всевозможные производные от нее существительные, прилагательные, глаголы и пр. вместе с их падежами, временами, лицами, числами и всем остальным, что мы проходили когда-то по русскому. Или по английскому. Или еще по какому-нибудь, ибо как уже было сказано, стеммер для каждого языка свой. Как и брейкер. Кстати, справочником я его обозвал для образности. Как вы понимаете, засунуть все словоформы великого и могучего в справочник нереально по соображениям объема и трудоемкости. Точно так же никто не пихает в стеммер целиком любой другой язык . Слава богу, в каждом языке существует грамматика, которая позволяет худо-бедно алгоритмизовать этот процесс, хотя и не до конца. Таким образом стеммер представляет собой алгоритмически-справочный модуль, где в алгоритмах зашиты его правила, а в справочники вынесены исключения. Примерно по такому же приципу устроен брейкер.

На выходе со стеммера строится полнотекстовый индекс. Идейно он представляет собой так называемый inverted index, т.е. справочную таблицу, в которой хранится а) РК записи исходной таблицы с текстовым полем; б) начальная форма слова, возвернутая стеммером; в) порядковый номер этого слова в текстовом поле (у нас же брейкер побил его на слова, так что не составляет труда определить, каким оно стоит по порядку. Порядковые номера используются для определения расстояния между словами при поиске близко отстоящих друг от друга слов. В реальности там еще смотрится, встречается ли между словами граница предложения, если да, фактическое расстояние увеличивается; граница параграфа – расстояние еще больше увеличивается и т.д., но это уже детали. Следующая запись reference table – а) тот же РК исходной таблицы, б) начальная форма следующего по порядку слова с брейкера, в) порядковый номер + 1. И т.д., пока не переберем все слова в текстовом поле данной записи исходной таблицы, после чего перейти на ее следующий РК, растереть, повторить. На самом деле не более одного полнотекстового индекса может быть создано над таблицей, поэтому туда еще будут добавляться айдишники колонок, timestampы для инкрементного обновления и пр.

Есть еще довески к этому процессу. Один называется noise words и содержит часто попадающиеся слова, не несущие смысловой нагрузки, типа предлогов, союзов и неопределенного артикля «блин». Noise words не попадают в индекс, но учитываются при подсчете порядкового номера. Второй – это тезаурус, сиречь справочник синонимов. Обе эти штуки еста справочники в физическом смысле слова, поскольку алгоритмизировать что тот, что другой процесс, понятно, нереально. Эти справочники свои для каждого языка, и их можно редактировать.

При выполнении полнотекстового запроса искомые слова приводятся к их начальным формам при помощи стеммера, которые затем ищутся в полнотекстовом индексе. Если идет поиск по тезаурусу, то из справочника тезауруса выбираются все синонимы и ищутся также их начальные формы. Архитектура полнотекстового поиска представлена в виде красивой и доходчивой диаграммы в BOL: http://i.msdn.microsoft.com/ms142541.fff90d77-4851-44a7-8747-c26445ddcb34(en-us,SQL.100).gif. SQL Serverный движок определяет, какие данные на данный момент нуждаются в полнотектовом индексировании. Ну например, мы сказали ему явно «строй полнотекстовый индекс», он определил, какие страницы данных для этого нужны, подчитал их и выплюнул куда-то в память. На картинке этот модуль обозначен как Gatherer. Семерка и 2000-й выплевывали позаписьно, в 2005-м для быстроты сделано батчами. После этого он пинает Microsoft Full-Text Engine Filter Daemon (MSFTEFD) типа арбайтен. Тот просыпается, матерится, залазит в эту самую память при помощи модуля, на котором написано Protocol Handler, забирает данные, передает их фильтру, и дальше понеслась по описанному выше конвейеру. В 2005-м msftefd.exe и msftesql.exe можно явно пощупать в C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn, ну или где там поставлен SQL Server. Их можно наблюдать как сервисы. msftesql, который SQL Server FullText Search (MSSQLSERVER), там торчит всегда, а msftefd появляется, когда начинается crawling, или (ре)популяция индекса. В 2008-м все еще раз изменилось, потому что теперь The Full-Text Engine is fully integrated into the Microsoft Database Engine. Закономерность можно было предсказать. Семерка и 2000-й вообще не имели собственного полнотекстового сервиса. Для этих дел они юзали mssearch.exe - Indexing Service (https://msdn.microsoft.com/en-us/library/ms689718(VS.85).aspx), основной продукт MS для индексирования и поиска по документам в файловой системе, который шарился промеж инстансами, а также эксченджем, шаропойнтом и вообще всеми, кому не лень. Потом генеральная линия партии совершила очередной крутой перелом. Заклеймили DLL Hell в целом и FTS как яркий пример перегиба на местах, затрудняющий иметь side-by-side installations разных версий компонент для разных надобностей. Колхоз разогнали и вернулись к индивидуальным хозяйствам, благо цены на дисковое пространство позволяют. В 2005-м каждый инстанс получил по собственному msftsfd + msftesql, и хотя последний не сильно отличался от mssearch, это позволило инстансам иметь отдельные настройки, ресурсы и жилплощадь. Оставалось ликвидировать последний перегиб, когда оптимизатор тупо вытаскивает записи по айдишникам и отдает их во внешний процесс, потому что при таком раскладе он мало чего может соптимизировать. В 2005-м полнотекстовый предикат сразу вызывал в плане появление оператора Remote Scan со всеми вытекающими. То есть для оптимизатора full-text engine service был как бы прилинкованный сервер, про устройство которого он ничего не знает и потому отдает вовне запрос как есть. Например, если рядом с contains в where находилась обычная колонка с высокой селективностью, SQL Server никак не мог ее использовать для удешевления плана. От безысходности народ распространял полнотекстовый индекс на эту колонку и переносил условие на нее из where внутрь contains. Калькуляция стоимости оператора также производилась по договоренности с внешним сервисом и оттого особой точностью не блистала. Иногда, если повезет, можно было наблюдать значение в военное время косинуса фи. В 2008-м обособление достигло своей клинической стадии – msftesql, включая компиляцию и выполнение полнотекстовых запросов, построение полнотекстовых индексов, менеджмент и пр. , был засунут внутрь SQL Server по самые помидоры. В принципе, sqlservr.exe и так уже хостит и CLR, и парсер XML, и веб-сервисы, и много чего еще, и теперь еще full-text engine. "А теперь просьба пристегнуться, наш старший DBA Гладченко покажет, как заставить все это добро летать" J. Интеграция полнотекстового движка внутрь SQL Serverного, с одной стороны, увеличила время компиляции, потому что он теперь не может просто отдать полнотекстовый кусок запроса наружу и забыть про него, он должен его рассматривать в комплексе с остальными кусками плана, с другой, это позволяет достичь более эффективных планов выполнения, где полнотекстовые компоненты учитываются другими операторами плана и не наблюдается безысходности, подобной описанной выше. См., напр., https://social.msdn.microsoft.com/Forums/en-US/sqldatabaseengine/thread/24e6e26b-873a-420a-b306-914f6d03b60f/. Здесь оператор Table Valued Function [FulltextMatch] – это предикат contains. Еще одно преимущество интеграции состоит в возможности построения параллельных планов с участием полнотекста. В 2005-м и ранее, внутризапросный параллелизм оптимизатором не рассматривался, едва стоило заикнуться на тему contains или freetext.

Снаружи остался торчать msftsfd , который переименовали в fdhost.exe. Он по-прежнему хостит фильтры, ворд брейкеры и стеммеры, т.е., вообще говоря, внешние компоненты, которые стремно затаскивать in-process с SQL Server. Например, в Office 2003 в mspfilt.dll существовал TIFFозный фильтр, а в 2007 его не стало. Такова селяви. А что делать, если уже подсели? К счастью, неподалеку в Бельвью находилась фирма Captaris, которая тоже писала OCR iFilter, способный воспринимать TIFFы. Понятно, что с ходу затаскивать его в SQL Server неосмотрительно, пускай крутится в песочнице. Такой песочницей является fdhost.exe. Компоненты в песочнице должны просыпаться по мере необходимости, строить или обновлять индекс и снова засыпать. Оформлять всю песочницу как сервис накладно, потому что со временем там может скопиться до черта всяких компонент. Поэтому в СТР6 на нее навесили стартер – легковесный сервис MSSQLFDLauncher, единственной задачей которого является поднимать и гасить fdhost.exe, а также назначать ему credentials, под которыми он будет выполняться. Поначалу для этих надобностей существовала процедура sp_reset_fdhostaccount, но потом управление централизовали в SQL Configuration Manager. Вот здесь http://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=334759 можно почитать, как удивился народ, впервые обнаружив стартер под капотом. Понятно, что сверхполномочный эккаунт ему назначать не след – как-никак песочница. Мало ли. Подробности на эту тему можно почерпнуть в BOL: https://msdn.microsoft.com/ru-ru/library/ms345189.aspx. Обратите внимание, что рестарт сервиса MSSQLFDLauncher не означает автоматом перезапуск fdhost.exe. Для этого применяется процедура sp_fulltext_service 'restart_all_fdhosts'.

Автор: Алексей Шуленин