Оптимизация индекса MongoDB при использовании текстового поиска в структуре агрегации
Мы строим упрощенную версию поисковой системы поверх MongoDB.
Примерный набор данных
{ "_id" : 1, "dept" : "tech", "updDate": ISODate("2014-08-27T09:45:35Z"), "description" : "lime green computer" }
{ "_id" : 2, "dept" : "tech", "updDate": ISODate("2014-07-27T09:45:35Z"), "description" : "wireless red mouse" }
{ "_id" : 3, "dept" : "kitchen", "updDate": ISODate("2014-04-27T09:45:35Z"), "description" : "green placemat" }
{ "_id" : 4, "dept" : "kitchen", "updDate": ISODate("2014-05-27T09:45:35Z"), "description" : "red peeler" }
{ "_id" : 5, "dept" : "food", "updDate": ISODate("2014-04-27T09:45:35Z"), "description" : "green apple" }
{ "_id" : 6, "dept" : "food", "updDate": ISODate("2014-01-27T09:45:35Z"), "description" : "red potato" }
{ "_id" : 7, "dept" : "food", "updDate": ISODate("2014-08-28T09:45:35Z"), "description" : "lime green computer" }
{ "_id" : 8, "dept" : "food", "updDate": ISODate("2014-08-27T09:45:35Z"), "description" : "lime green computer" }
{ "_id" : 9, "dept" : "food", "updDate": ISODate("2014-08-27T09:45:35Z"), "description" : "lime green computer" }
Мы хотим избежать использования "offset-limit" для разбиения на страницы результатов, чтобы сделать это, мы в основном используем "метод поиска", изменив предложение "where / match" в запросе, чтобы иметь возможность использовать индекс вместо перебора коллекции для получения желаемых результатов. Для получения дополнительной информации о "методе поиска" я настоятельно рекомендую прочитать http://use-the-index-luke.com/blog/2013-07/pagination-done-the-postgresql-way
Поисковые системы обычно упорядочивают результаты по счету и дате обновления в порядке убывания. Для этого мы используем функцию текстового поиска в конвейере агрегации следующим образом.
db.inventory.createIndex({description:"text", dept: -1, updDate: -1, id:-1})
Первая страница
db.inventory.aggregate( [ { $match: { dept : {$in : ["food","kitchen"]},"$text" : { "$language" : "en", "$search" : "green"} } },{ $project: {score: { $meta: "textScore" }, description : 1, updDate : 1, _id: 1 } }, { $sort: { "score" : -1, "updDate" : -1, _id: -1 } }, {$limit: 2 }] )
{ "_id" : 5, "updDate" : ISODate("2014-04-27T09:45:35Z"), "description" : "green apple", "score" : 0.75 }
{ "_id" : 3, "updDate" : ISODate("2014-04-27T09:45:35Z"), "description" : "green placemat", "score" : 0.75 }
Вторая страница
db.inventory.aggregate( [ { $match: { dept : {$in : ["food","kitchen"]},"$text" : { "$language" : "en", "$search" : "green"} } },{ $project: {score: { $meta: "textScore" }, description : 1, updDate : 1, _id: 1 } }, { $sort: { "score" : -1, "updDate" : -1, _id: -1 } }, { "$match" : { "$or" : [ { "score" : { "$lt" : 0.75}} , { "$and" : [ { "score" : { "$eq" : 0.75}} , { "$or" : [ { "updDate" : { "$lt" : ISODate("2014-04-27T09:45:35Z")}},{ "$and" : [ { "updDate": { "$eq" : ISODate("2014-04-27T09:45:35Z")}} , { "_id" : { "$lt" : 3}}]}]}]}]}},{$limit: 2 }] )
{ "_id" : 7, "updDate" : ISODate("2014-08-28T09:45:35Z"), "description" : "lime green computer", "score" : 0.6666666666666666 }
{ "_id" : 9, "updDate" : ISODate("2014-08-27T09:45:35Z"), "description" : "lime green computer", "score" : 0.6666666666666666 }
И последняя страница
db.inventory.aggregate( [ { $match: { dept : {$in : ["food","kitchen"]} , "$text" : { "$language" : "en", "$search" : "green"} } }, { $project: {score: { $meta: "textScore" }, description : 1, updDate : 1, _id: 1 } }, { $sort: { "score" : -1, "updDate" : -1, _id: -1 } }, { "$match" : { "$or" : [ { "score" : { "$lt" : 0.6666666666666666}} , { "$and" : [ { "score" : { "$eq" : 0.6666666666666666}} , { "$or" : [ { "updDate" : { "$lt" : ISODate("2014-08-27T09:45:35Z")}} , { "$and" : [ { "updDate" : { "$eq" : ISODate("2014-08-27T09:45:35Z")}} , { "_id" : { "$lt" : 9}}]}]}]}]}}, {$limit: 2 }] )
{ "_id" : 8, "updDate" : ISODate("2014-08-27T09:45:35Z"), "description" : "lime green computer", "score" : 0.6666666666666666 }
Обратите внимание на то, как мы упорядочиваем результаты по счету, updDate и id, а во второй фазе сопоставления - как мы пытаемся разбивать их на страницы, используя значение оценки документа, дату обновления и, наконец, идентификатор.
При создании индекса учитывается, что текстовые запросы нельзя охватывать полями префикса текстового индекса, см. Выпуск https://jira.mongodb.org/browse/SERVER-13018, хотя я не уверен, что это применимо к нашему случаю.
Поскольку режимы "executeStats" и "allPlansExecution" не работают в структуре агрегации, см. https://jira.mongodb.org/browse/SERVER-19758 Я понятия не имею, как MongoDB пытается разрешить запрос.
Поскольку пересечение индекса не работает для текстового поиска, см. https://jira.mongodb.org/browse/SERVER-3071 (с разрешением 2.5.5) и http://blog.mongodb.org/post/87790974798/efficient-indexing-in-mongodb-26 где автор говорит, что
As of version 2.6.0, you cannot intersect with geo or text indices and you can intersect at most 2 separate indices with each other. These limitations are likely to change in a future release.
Прочитав несколько раз разделы 3.4(Учебники по текстовому поиску) и 3.5(Стратегии индексирования) из https://docs.mongodb.org/manual/MongoDB-indexes-guide-master.pdf не придя к четкому выводу.
Итак, какова лучшая стратегия индексации для индексирования этой коллекции с точки зрения текстового поиска?
Один индекс для первой фазы матча и еще один для второй (нумерации страниц) фазы?
db.inventory.createIndex({description:"text", dept: -1})
db.inventory.createIndex({updDate: -1, id:-})
Составной индекс, учитывающий поля из обеих фаз совпадения?
db.inventory.createIndex({description:"text", dept: -1, updDate: -1, id:-1})
Ни один из вышеперечисленных?
Спасибо