Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. MongoDB
Code

Полнотекстовый поиск в MongoDB

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Sergey Zhuk (you can also view the original English article)

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

Начиная с  версии 2.4, MongoDB добавила экспериментальную возможность поддержки Полнотекстового Поиска, используя текстовые индексы. Эта возможность теперь стала полноценной частью продукта (и более не является экспериментальной). экспериментальной). В этой статье мы изучим возможности полнотекстового поиска в MongoDB, начиная с самых основ.

Если вы впервые сталкиваетесь с MongoDB, то я бы порекомендовал вам ознакомиться со  следующими статьями на Envato Tuts+, которые помогут вам понять основные базовые концепции MongoDB:

Основы

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

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

  • Рассмотрим  поиск вашей любимой темы в Wiki.  Когда вы вводите поисковый запрос в Wiki, поисковый движок отдает результаты всех статей, связанных с ключевым словом или фразой, по которой вы искали (даже если эти слова использовались глубоко внутри статьи). Эти поисковые результаты отсортированы по релевантности из их совпадения поисковому запросу.
  • В качестве  другого примера рассмотрим социальную сеть, где пользователь может  искать все посты, содержащие слово cats или более сложный вариант, когда нужно выбрать все посты, у которых в комментариях есть слово cats.

Перед тем как мы двинемся дальше, нужно рассмотреть несколько общих терминов, связанных с полнотекстовым поиском, которые вы должны знать. Эти термины приметными к любой  реализации полнотекстового поиска (а не только в MongoDB).

Стоп слова

Стоп слова являются нерелевантными словами, которые должны быть отфильтрованы  и убраны из текста. Например: a, an, the, is, at, which и другие.

Стемминг

Стемминг это процесс уменьшения слов до их основ. Например, такие слова как standing, stands, stood и подобные, имеют одну общую основу stand.

Оценка

Относительный рейтинг для измерения из наиболее релевантных результатов поиска

Алтернативы  полнотекстовому поиску в MongoDB

До того как MongoDB получил возможность иметь текстовые индексы, нам приходилось таким образом проектировать наши данные, чтобы была возможность поддерживать поиск по словам или использовать регулярные выражения для реализации подобного  функционала. Однако использование любого из этих подходов имеет свои собственные ограничения:

  • Во-первых, ни один из них не поддерживает возможности стемминга, стоп слова, ранжирование и прочее.
  • Использование поиска по ключевым словам требует создание индексов с несколькими ключами, которые не являются эффективными по сравнению с полнотекстовыми.
  • Использование  регулярных выражений так же неэффективно с точки зрения производительности, так  как эти выражения не эффективно используют индексы.
  • В  добавлении ко всему этому, ни одна из этих техник не дает возможности поиска по фразам (как например поиск 'фильмы, изданные в 2015') или взвешивание результатов поиска.

Помимо этих подходов, для более передовых и сложных поисковых приложений, если альтернативные решения типа Elastic Search или SOLR. Но использование любого из этих решений увеличивает архитектурную сложность приложения.

Обратите внимание что полнотекстовый поиск в MongoDB не позиционируется как полноценная замена поисковым решениям как Elastic, SOLR, и другие. Однако он может использоваться эффективно в большинстве приложений, построенных на MongoDB.

Представляем Полнотекстовый Поиск в MongoDB

Используя полнотекстовый поиск, мы можете определить текстовый индекс на любом поле документа, значением которого является строка или массив строк. Когда мы создаем текстовый  индекс на поле, MongoDB создает токены и стеммы для текстового содержимого этого поля, и в соответствии этому настраивает индексы.

Чтобы это лучше понять, давайте больше углубимся в практическую часть часть. Я хочу, чтобы вы следовали статьи вместе со мной, пробуя все примеры в командной строке mongo. Сначала мы добавим немного тестовых данных, которые мы будем использовать на протяжении всей статьи, а затем мы обсудим ключевые концепции.

Для целей этой статьи рассорим коллекцию messages, которая содержит документы следующей структуры:

Давайте добавим несколько тестовых документов используя команду insert:

Создание текстового индекса

Создание текстового индекса очень похоже на создание обычного индекса, кроме того что мы указываем ключевое слово text вместо указания порядка сортировки.

Индексация одного поля

Создадим текстовый индекс на поле subject нашего документа, используя следующий запрос:

Чтобы протестировать только что созданный текстовый индекс на поле subject, поищем документы, используя оператор $text. Мы будем искать все документы, содержащие ключевое слово dogs в поле subject.

Так как  мы используем тестовый поиск, то мы так же хотим получить  некоторую статистику по результатам релевантности документов. Для этих целей мы воспользуемся выражением { $meta: "textScore"}, которое предоставит данные по выполнению оператора $text. Мы так же отсортируем  документы по их testScore, используя команду sort. Больший textScore означает более высокую  релевантность.

Данный запрос возвращает следующие документы, которые содержат ключевое слово dogs в поле subject.

Как видите, первый документ имеет оценку 1 (так как ключевое слово dog появляется дважды в поле subject, в отличие от второго документы с оценкой  0.66. Запрос так же сортирует возвращаемые документы по убыванию  их оценки.

Единственный вопрос, который может возникнуть, так это то, что если мы ищем по ключевому слову dogs, то почему поисковый движок ищет по ключевому слову  dog (без 's')? Помните наше обсуждение стемминга, где любые ключевые слова сокращаются до их основ? Это и является причиной того, что ключевое слово dogs было сокращено до dog.

Индекс на  нескольких полях (составные индексы)

Но чаще обычного, вы будете использовать тестовые поиск по нескольким полям в документе.  В нашем примере, мы добавим составной текстовый индекс на поля subject и content. Продолжим и выполним следующую команду в оболочке mongo:

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

После выполнения этих запросов на создание индекса, попробуйте поискать все документы с ключевым словом cat.

Запрос, представленный выше, выведет следующие документы:

Вы можете видеть, что оценка первого документа, который содержит ключевое слово cat в обоих полях subject и content, является более высокой.

Индексация внутреннего документа  Документ (индексация с подстановкой)

В последнем примере, мы сделали комбинированный индекс с полями subject и content. Но могут быть сценарии, когда вам понадобится, чтобы можно было искать среди любого текстового контента в ваших документах.

Например, рассмотрим сортировку email писем в документах MongoDB. В случае писем, нам нужна возможность искать по всем полям, включая Sender, Recipient, Subject и Body. В таких сценариях вы можете проиндексировать все поля вашего документа, используя спецификатор подстановки $**.

Запрос будет примерно таким (убедитесь только что вы заранее удалили существующий индекс перед созданием нового):

Запрос автоматически настроит текстовые индексы на любых строковых полях ваших документов. Чтобы проверить это, вставим новый документ с новым полем location внутри:

Теперь если вы захотите попробовать поискать с ключевым словом chicago (запрос ниже), то этот запрос вернет  документ, который мы только что вставили.

Здесь я бы хотел обратить ваше внимание на нескольких вещах:

  • Посмотрите, что мы явно не определяли индекс на поле location, после того, как добавили новый документ. Это потому что мы уже определили текстовый индекс на весь документ, используя оператор $**.
  • Индексы с подстановкой  иногда могут быть медленными, особенно в случаях, когда ваши данные  очень больших размеров. По этой причине, планируйте индексы в ваших документах с умом, так как они могут повлиять на вашу производительность.

Продвинутый поиск

Поиск по фразе

Вы можете искать по фразам, например "smart birds who love cooking", используя индексы. По умолчани поиск по фразе использует OR поиск по всем представленным словам, то есть он будет искать все документы, которые содержат любое из слов smartbirdlove or cook.

Запрос выведет следующий результат:

В случае если же вы хотите выполнить запрос по всей фразе целиком (логическое AND), то это можно сделать указав двойные кавычки в тексте поискового запроса.

Этот запрос выведет следующие документы,  которые содержать фразу "cook food":

Поиск по отрицанию

Добавив к ключевому слову префикс - (знак минус), можно убрать все документы, которые содержат это  слово. Например, попробуем поискать любой документ, который содержит ключевое слово rat, то не содержит birds, используя следующий запрос:

Заглянем за кулисы

Одна из важных возможностей, которую я до сих пор не открыл, это возможность посмотреть за кулисы, и увидеть то, каким образом обрезаются лючевые слова, применяется стоп слова, отрицание и прочее. Оператор $explain поможет  нам в этом. Можно выполнить explain запрос, передав true в качестве параметра, что даст вам детальную статистику по выполнению запроса.

Если вы обратите внимание на объект queryPlanner который был возвращен командой explain, то увидите каким образом MongoDB парсит переданную ей поисковую строку. Обратите внимание, что она опускает такие стоп слова как who, и обрезает dogs до dog.

Вы также можете увидеть, какие основы были убраны из нашего поиска и какие фразы мы использовали в секции parsedTextQuery.

Запрос explain будет крайне полезен, когда мы будем выполнять более сложные поисковые запросы и появится необходимость в их анализе.

Взвешенный текстовый поиск

Когда у нас есть индексы на более чем одно поле в документе, то в большинстве случаев одно поле будет более важным (будет иметь больший вес) чем другое. Например, когда вы ищете в блоге, то название блога будет иметь самый большой вес, в отличие от контента в блоге.

По умолчанию вес любого индекса равен 1. Чтобы указать относительные веса для индоссируемых полей, можно включить опцию weights, во время выполнения createIndex команды.

Давайте рассмотрим это на примере. Если вы попробуете поискать ключевое слово cook с нашими текущими индексами, то результат будет содержать два документа, оба из которых  будут иметь одинаковую оценку.

Но теперь давайте изменим наши индексы и добавим в них веса; поле subject будет иметь вес 3,  в то время как поле content будет иметь вес равный 1.

Теперь снова попробуем поискать по ключевому слову cook, и мы увидим, что документы, которые содержат это слово в поле subject, будут иметь больший вес (2), чем другие (которые имеют вес 0.66)

Разбиение текстовых  индексов

По мере роста данных в вашем приложении, размер ваших текстовых индексов тоже будет рости.  С ростом размера текстовых индексов,  MongoDB приходится искать среди всех вхождений индекса во  время поискового запроса.

Чтобы сохранить свой поиск эффективным, по мере роста самих индексов, можно указать максимальное количество индексных вхождений, которые будут просканированы, используя условия равенства  с простым поиском $text. Самый общий пример такого случая, будет выбор всех постов, сделанных за определенный  год/месяц, или поиск всех постов по определенной категории/тегу.

Если вы просмотрите документы, которые мы ищем,  то найдете поле year в них,  которое мы до сих пор еще не использовали. Общим сценарием будет поиск сообщений за год,  вместе полнотекстовым поиском, о котором мы только что узнали.

Для этого мы можем создать составной индекс, который устанавливает индекс на поле year, и текстовый индекс на поле subject. Таким образом мы делаем две важные вещи:

  • Мы логически разделили внутренние данные коллекции на части, разделенные по году.
  • А это в свою очередь сократит текстовый поиск, который будет сканировать только те документы, которые попадают под определенный год (или раздел).

Удаляем индексы, которые уже у нас есть, и создадим новый составной индекс (year, subject):

Теперь выполним следующий запрос, чтобы найти все сообщения, которые были созданы в 2015 и содержат ключевое слово cats:

Запрос вернет только один совпавший документ, как и ожидалось:  Если вы выполните explain этого запроса,  и обратите внимание на executionStats, то обнаружите что totalDocsExamined для этого запроса  равняется 1, что подтверждает что наш новы индекс использовался корректно и MongoDB просканировал только один единственный документ, успешно проигнорировав все остальные документы, которые не попадают под 2015 год.

Текстовые индексы: преимущества 

Что еще могут  текстовые индексы?

Мы прошли длинный путь в этой статье, рассматривая текстовые  индексы. Есть много других концепций, с которыми вы можете поэкспериментировать, используя тестовые индексы. Но из-за рамок этой статьи, мы не сможем обсудить их сегодня в деталях. Тем не менее давайте бегло взглянем на  них:

  • Текстовые индексы предоставляют поддержку нескольких языков, позволяя искать на разных языках, используя оператор $language. MongoDB уже поддерживает около 15 языков, включая французский, немецкий, русский и другие.
  • Текстовые индексы могут использоваться в запросах агрегации. Стадия выборки в агрегации может содержать запрос с полнотекстовым поиском.
  • Можно использовать обычные операторы для выборки, фильтрации, лимитов, сортировки и другие, работая с текстовыми индексами.

Текстовые индексы MongoDB  vs. Внутренний поиск баз данных 

Имейте в виду, что полнотекстовый поиск в MongoDB не является полноценной заменой традиционным поисковым движкам в MongoDB, использование нативных средств MongoDB рекомендуется по следующим причинам:

  • Как было уже ранее сказано, в MongoDB текстовый поиск прекрасно работает для большинства приложений (около 80%), которые построены на MongoDB.
  • Построение  поисковых возможностей вашего приложения на базе данных снижает архитектурную сложность приложения.
  • Текстовый поиск MongoDB работает в реальном времени, без каких-либо задержек или пакетных обновлений. В тот момент, когда вы вставляете новый или обновляете уже существующий документ, текстовый индекс обновляется.
  • Текстовый  поиск, который интегрирован в ядро базы данных, прекрасно работает даже с шардингом и репликацией.
  • Он отлично интегрирован с существующими возможностями Mongo, такими как фильтры, агреация, обновления и другие.

Текстовые индексы: недостатки

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

Возможности, которых  не хватает в текстовом поиске

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

Ограничения в существующих возможностях

  • Составной текстовый индекс не может включать в себя любой другой тип индекса, как например многоключевые  индексы или гео индексы. В добавлении к этому, если ваш составной текстовый индекс включает любые индексные ключи перед текстовым индексным ключом, то все запросы должны содержать операторы равенства для обрабатываемых ключей.
  • Есть несколько ограничений, связанных с запросами. Например, запрос может содержать только одно $text выражение, нельзя использовать $text вместе с $nor, нельзя использовать hint() команду вместе с $text, использование $text вместе с $or требует, чтобы все предложения в $or выражении были проиндексированы.

Минусы производительности

  • Текстовые индексы могут создать дополнительные издержки во время добавления новых документов. Что в свою очередь уменьшает пропускную способность.
  • Некоторые запросы, такие как поиск по фразе, могут быть относительно медленные.

Подводя итоги

Полнотекстовый поиск всегда был одной из самых востребованных возможностей в MongoDB. В этой статье мы начали с ознакомления с полнотекстовым поиском, перед тем как двинуться дальше к основам создания текстовых индексов.

Затем мы рассмотрели составные индексы, индексы с подстановкой, поиск по фразе и негативный поиск. Далее мы изучили такие важные концепции, как анализ текстовых индексов, веса, и логическое разбиение ваших индексов. Можно ожидать значительные обновления этого функционала в будущих версиях  MongoDB.

Я бы рекомендовал вам, попробовать полнотекстовый поиск и поделиться своими мыслями о нем. Если вы уже реализовывали его в своем приложении, просто поделитесь своим опытом. Буду рад видеть ваши вопросы, мысли и предложение, касаемо этой статьи в комментариях.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.