Как оптимизировать "текстовый поиск" для инвертированного индекса и реляционной базы данных?
Обновление 2015-10-15
В 2012 году я создавал персональное онлайн-приложение и на самом деле хотел изобретать колесо, потому что любопытно по природе, для обучения и улучшения навыков работы с алгоритмом и архитектурой. Я мог бы использовать apache lucene и других, однако, как я уже говорил, я решил создать свою собственную мини-поисковую систему.
Вопрос: Так ли вообще невозможно улучшить эту архитектуру, за исключением использования доступных сервисов, таких как elasticsearch, lucene и других?
Оригинальный вопрос
Я разрабатываю веб-приложение, в котором пользователи ищут определенные заголовки (скажем, например, book x, book y и т.д.), данные которых находятся в реляционной базе данных (MySQL).
Я следую принципу, что каждая запись, извлеченная из db, кэшируется в памяти, так что приложение имеет меньше вызовов в базе данных.
Я разработал свою собственную мини-поисковую систему со следующей архитектурой:
![Architecture diagram]()
Вот как это работает:
- a) Пользователь выполняет поиск имени записи
- b) Система проверяет, с какого символа начинается запрос, проверяет, есть ли там запрос: получить запись. Если нет, добавляет его и получает все соответствующие записи из базы данных двумя способами:
- Любой запрос уже существует в таблице "Запросы" (которая является своего рода таблицей истории), таким образом, получается запись на основе идентификаторов (Быстрая производительность)
- Или, в противном случае, используя оператор Mysql LIKE %%, чтобы получить записи/идентификаторы (Также сохраните использованный запрос пользователем в таблице истории Запросы вместе с идентификаторами, которые он сопоставляет).
- > Затем он добавляет записи и их идентификаторы в кеш и только идентификаторы к инвертированной карте индексов.
- c) результаты возвращаются в пользовательский интерфейс
Система работает нормально, однако у меня есть основные проблемы Two, что я не смог найти подходящее решение для (пытался за последний месяц):
Первая проблема:
если вы проверяете точку (b), где не найден запрос "история", и он должен использовать оператор Like %%: этот процесс становится time потребляющим, когда запрос соответствует многочисленным записям в базе данных (вместо одного или двух):
- Для получения записей из Mysql потребуется некоторое время (именно поэтому я использовал INDEXES для конкретных столбцов)
- Тогда время для сохранения истории запросов
- Затем время для добавления записей/идентификаторов в кеш и инвертированные карты индексов
Вторая проблема:
Приложение позволяет пользователям добавлять новые записи, которые могут сразу использоваться другими пользователями, зарегистрированными в приложении.
Однако для этого необходимо обновить карту инвертированных индексов и "запросы" таблицы, чтобы в случае, если какой-либо старый запрос соответствует новому слову. Например, если добавлена новая запись "woodX", все же старый запрос "дерево" отображает его. Поэтому, чтобы перехватить запрос "wood" на эту новую запись, вот что я делаю сейчас:
- новая запись "woodX" добавляется в таблицу "records".
- тогда я запустил оператор Like %%, чтобы увидеть, какой уже существующий запрос в таблице "запросы" отображает эту запись (например, "дерево" ), затем добавьте этот запрос с новым идентификатором записи как новую строку: [дерево, новый идентификатор].
- Затем в памяти обновите инвертированный указатель. Введите значение ключа "дерево" (т.е. список), добавив новый идентификатор записи в этот список.
- > Таким образом, теперь, если удаленный пользователь ищет "дерево", он получит из памяти: дерево и дерево X
Проблема здесь также потребление времени. Согласование всех историй запросов (в табличных запросах) с добавленным словом занимает много времени (чем больше совпадающих запросов, тем больше времени). Тогда обновление памяти также занимает много времени.
То, что я считаю мышлением для исправления этой проблемы времени, заключается в том, чтобы вернуть желаемые результаты пользователю first, а затем пусть приложение POST a ajax вызовите необходимые данные для выполнения всех этих задач UPDATE. Но я не уверен, что это плохая практика или непрофессиональный способ делать что-то?
Так что за последний месяц (немного больше) я попытался подумать о лучшей оптимизации/модификации/обновлении для этой архитектуры, но я не эксперт в области поиска документов (на самом деле это моя первая мини-поисковая система, когда-либо построенная).
Я был бы признателен за любые отзывы или рекомендации относительно того, что я должен сделать, чтобы достичь такой архитектуры.
Спасибо заранее.
PS:
- Это приложение j2ee, использующее сервлеты.
- Я использую MySQL innodb (поэтому я не могу использовать полнотекстовый поиск)
Ответы
Ответ 1
Я бы настоятельно рекомендовал Sphinx Search Server, который лучше всего оптимизирован для полнотекстового поиска. Посетите http://sphinxsearch.com/.
Он предназначен для работы с MySQL, поэтому это дополнение к вашей текущей рабочей области.
Ответ 2
Я не претендую на то, чтобы иметь решение, но вот мои идеи.
Во-первых, я, как и вы, за долгие запросы LIKE %%: я бы выполнил запрос, ограниченный несколькими ответами в MySQL, например, дюжину, вернуть его пользователю и подождать, чтобы узнать, хочет ли пользователь больше совпадающих записей или запускает в фоновом режиме полный запрос, в зависимости от ваших потребностей в индексации для будущих поисков.
В более общем плане, я думаю, что хранение всего в памяти может привести, в один прекрасный день, к слишком большому потреблению памяти. И поскольку поисковый движок становится все быстрее и быстрее, когда он хранит все в памяти, вам нужно будет обновлять все эти кеши, когда данные будут добавлены или обновлены, и это, безусловно, займет больше времени.
Вот почему я думаю, что решение, которое я видел в "программном обеспечении с открытым исходным кодом" (я не помню его имени), не так уж плохо для текстового поиска в сообщениях: каждый раз, когда данные вставляются, таблица под названием "Слова" хранятся следы каждого существующего слова и другая таблица (пусть говорят "WordsLinks" ) связь между каждым словом и сообщениями, которые появляются.
Такое решение имеет некоторые недостатки:
- Каждая вставка, удаление, обновление в базе данных намного медленнее
- Должен предвидеть выбор данных для поисковой системы: если вы решили сохранить два слова, которые вы никогда не сохраняли, слишком поздно для уже записанных данных, если только вы не начнете полную обработку данных.
- Вы должны позаботиться о DELETE, а также UPDATE и INSERT
Но я думаю, что есть некоторые большие преимущества:
- Время вычисления, вероятно, совпадает с "решением памяти" (в конечном итоге), но оно разделяется в каждой базе данных "Создать/обновить/удалить", а не во время запроса.
- Поиск целого слова или слов "начиная с" происходит мгновенно: при индексировании поиск в таблице "Слова" является дихотомическим. И запрос таблицы WordLinks очень быстрый либо с индексом.
- Поиск нескольких слов одновременно может быть простым: собрать группу "WordLinks" для каждого найденного Word и выполнить пересечение на них, чтобы сохранить только "Идентификаторы базы данных", общие для всех этих групп. Например, со словами "дерево" и "лист", первый из них может содержать записи таблицы {1, 4, 6}, а второй - {1, 3, 6, 9}. Таким образом, с пересечением просто хранить только общие части: {1, 6}.
- "Подобно %%" в таблице с одним столбцом, вероятно, быстрее, чем много "Like %%" в разных полях разных таблиц. И каждый движок базы данных обрабатывает некоторый кеш: таблица "Words" может быть немного достаточной для хранения в памяти.
- Я думаю, что существует небольшой риск проблем с производительностью и памятью, если данные становятся огромными.
- Поскольку каждый поиск выполняется быстро, вы можете даже искать синонимы. Например, найдите "сеть", если пользователь ничего не нашел с "ethernet".
- Вы можете применять правила, например, расщеплять слова верблюда для генерации, например, трех слов "дерево", "X", "woodX" из "woodX". Каждое "слово" очень легкое для хранения и поиска, поэтому вы можете многое сделать.
Я думаю, что решение, в котором вы нуждаетесь, может быть сочетанием методов: например, вы можете сохранять облегченные UPDATE, INSERT, DELETE и запускать "Words" и "WordsLinks", подавая TRIGGER.
Просто для анекдота я увидел программное обеспечение, разработанное моей компанией, в котором было принято решение сохранить "все" (!) в памяти. Это позволяет нам рекомендовать нашим клиентам покупать серверы с 64 ГБ оперативной памяти. Немного дороже. Это объясняет, почему я очень осмотрителен, когда вижу решения, которые могут в конечном итоге привести к заполнению памяти.
Ответ 3
Я должен сказать, я не думаю, что ваш дизайн очень хорошо подходит к этой проблеме. Последствиями, которые вы видите сейчас, являются последствиями этого. И кроме того, ваше текущее решение не масштабируется.
Вот возможное решение:
-
Перепроектируйте свою базу данных, чтобы содержать только авторитетные данные, но не производные данные. Таким образом, все записи кэша должны исчезнуть из MySQL.
-
Храните данные только в течение продолжительности запроса в памяти в приложении. Это значительно упрощает дизайн вашего приложения (думаю, условия гонки) и позволяет масштабировать до разумного числа клиентов.
-
Введите слой кэширования. Я настоятельно рекомендую использовать установленный продукт, а не строить его самостоятельно. Это освобождает вас от всей настраиваемой логики кэширования в вашем приложении и даже делает работу намного лучше.
Вы можете взглянуть на Redis или Memcached для слоя кеширования. Я думаю, что стратегия LRU должна соответствовать здесь. В зависимости от того, насколько сложны ваши запросы, может иметь смысл и выделенный механизм индексированного поиска, такой как Lucene.
Ответ 4
Я уверен, что это может быть реализовано в MySQL, но было бы гораздо меньше усилий, чтобы просто использовать существующую поисковую базу данных, такую как Elasticsearch. Он использует библиотеку Lucene для реализации инвертированного индекса, имеет обширную документацию, поддерживает горизонтальное масштабирование, довольно простой язык запросов и так далее. Я думаю, что было довольно много работы, чтобы зайти так далеко, и будет еще больше работать с кешами, условиями гонки, ошибками, проблемами производительности и т.д., Чтобы сделать решение "класс производства".