Ускорить поиск строк в 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 в своем приложении.