Ускорить поиск строк в MongoDB
Я пытаюсь использовать MongoDB для реализации словаря естественного языка. У меня есть коллекция лексем, каждая из которых имеет несколько словоформ в качестве поддокументов. Это то, что выглядит как одна лексема:
{
"_id" : ObjectId("51ecff7ee36f2317c9000000"),
"pos" : "N",
"lemma" : "skrun",
"gloss" : "screw",
"wordforms" : [
{
"number" : "sg",
"surface_form" : "skrun",
"phonetic" : "ˈskruːn",
"gender" : "m"
},
{
"number" : "pl",
"surface_form" : "skrejjen",
"phonetic" : "'skrɛjjɛn",
"pattern" : "CCCVCCVC"
}
],
"source" : "Mayer2013"
}
В настоящее время у меня есть коллекция из 4000 лексем, и каждый из них имеет в среднем список из 1000 словоформ (в отличие от всего 2 выше). Это означает, что у меня есть 4 000 000 уникальных форм слова в коллекции, и я должен иметь возможность искать их в течение разумного промежутка времени.
Обычный запрос будет выглядеть так:
db.lexemes.find({"wordforms.surface_form":"skrejjen"})
У меня есть индекс на wordforms.surface_form
, и этот поиск выполняется очень быстро.
Однако, если я хочу иметь подстановочные знаки в моем поиске, производительность неистовая. Например:
db.lexemes.find({"wordforms.surface_form":/skrej/})
занимает более 5 минут (после чего я сдался). Как упоминалось в этом вопросе, поиск регулярных выражений по индексам, как известно, является плохим. Я знаю, что добавление якоря в регулярном выражении помогает много, но это также сильно ограничивает возможности моего поиска. Даже если я готов сделать эту жертву, я заметил, что время ответа может сильно варьироваться в зависимости от регулярного выражения. Запрос
db.lexemes.find({"wordforms.surface_form":/^s/})
Заканчивается 35.
Лучшие результаты, которые я получил до сих пор, фактически были, когда я отключил индекс, используя hint
.
В этом случае ситуация значительно улучшается. Этот запрос:
db.lexemes.find({"wordforms.surface_form":/skrej/}).hint('_id_')
занимает около 3 секунд.
Мой вопрос: есть ли что-то еще, что я могу сделать, чтобы улучшить эти поисковые запросы? Как бы то ни было, они все еще немного медленны, и я уже рассматриваю возможность перехода на MySQL в надежде получить производительность. Но я действительно хотел бы сохранить гибкость Mongo и избежать всей утомительной нормализации в РСУБД. Какие-либо предложения? Считаете ли вы, что я столкнусь с некоторой медлительностью, независимо от механизма БД, с таким количеством текстовых данных?
Я знаю о Mongo new текстовый поиск, но преимущества этого (токенизация и выведение) в моем случае не актуальны (не для упоминание мой язык не поддерживается). Неясно, действительно ли текстовый поиск быстрее, чем при использовании regex.
Ответы
Ответ 1
Как предложил Derick, я реорганизовал данные в моей базе данных, так что у меня есть "wordforms" как коллекция, а не как поддокументы под "lexemes".
Результаты на самом деле были лучше!
Вот некоторые сравнения скорости. Последний пример с использованием hint
намеренно обходит индексы на surface_form
, который в старой схеме был быстрее.
Старая схема (см. оригинальный вопрос)
Query Avg. Time
db.lexemes.find({"wordforms.surface_form":"skrun"}) 0s
db.lexemes.find({"wordforms.surface_form":/^skr/}) 1.0s
db.lexemes.find({"wordforms.surface_form":/skru/}) > 3mins !
db.lexemes.find({"wordforms.surface_form":/skru/}).hint('_id_') 2.8s
Новая схема (см. Ответ Derick)
Query Avg. Time
db.wordforms.find({"surface_form":"skrun"}) 0s
db.wordforms.find({"surface_form":/^skr/}) 0.001s
db.wordforms.find({"surface_form":/skru/}) 1.4s
db.wordforms.find({"surface_form":/skru/}).hint('_id_') 3.0s
Для меня это довольно хорошее доказательство того, что реорганизованная схема сделает поиск быстрее и будет стоить лишних данных (или требуется дополнительное соединение).
Ответ 2
Одна из возможностей заключается в том, чтобы хранить все варианты, которые, как вы думаете, могут быть полезны в качестве элемента массива - не уверены, возможно ли это, хотя!
{
"number" : "pl",
"surface_form" : "skrejjen",
"surface_forms: [ "skrej", "skre" ],
"phonetic" : "'skrɛjjɛn",
"pattern" : "CCCVCCVC"
}
Я бы, вероятно, также предлагал не хранить 1000 словоформ с каждым словом, но оберните это, чтобы иметь меньшие документы. Чем меньше ваши документы, тем меньше MongoDB должен будет читать в памяти для каждого поиска (если условия поиска не требуют полного сканирования, конечно):
{
"word": {
"pos" : "N",
"lemma" : "skrun",
"gloss" : "screw",
},
"form" : {
"number" : "sg",
"surface_form" : "skrun",
"phonetic" : "ˈskruːn",
"gender" : "m"
},
"source" : "Mayer2013"
}
{
"word": {
"pos" : "N",
"lemma" : "skrun",
"gloss" : "screw",
},
"form" : {
"number" : "pl",
"surface_form" : "skrejjen",
"phonetic" : "'skrɛjjɛn",
"pattern" : "CCCVCCVC"
},
"source" : "Mayer2013"
}
Я также сомневаюсь, что MySQL будет лучше работать с поиском случайных форм слов, поскольку он должен будет выполнить полное сканирование таблицы так же, как и MongoDB. Единственное, что может помочь, это кеш запросов - но это то, что вы могли бы легко создать в своем пользовательском интерфейсе поиска /API в своем приложении.