Настройка индекса MongoDB 2.6, запрос с использованием $или, $in, с ограничением и сортировкой

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

$cur = $col->find(
    array (
        '$or' => array(
            array('owner' => $my_id),
            array('owner' => array('$in' => $friends), 'perm.type' => array('$in' => array('P', 'F'))),
            array('owner' => array('$in' => $friends), 'perm.list' => $my_id)
        )
    )
)->limit(10)->skip(0)->sort(array('ca' => -1));

цель состоит в том, чтобы найти первые 10 сообщений, отсортированных по времени их создания в порядке desc, которые:

a). сделанный мной, или си.) сделано моими друзьями с типами разрешений " P "для общественности, или" F " для друзей, или с.) сделано моими друзьями, которые в списке разрешений специально обозначили меня как зрителя.

переменная $friends-это массив идентификаторов пользователей, которые дружат со мной. перманент.имеет 4 значения, которые 'П', 'Ж', 'З', 'С'. перманент.список-это массив идентификаторов пользователей, имеющих разрешение на просмотр этой записи.

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

индексы, которые я создал для этого запроса:

$col->ensureIndex(array('owner' => 1, 'ca' => -1));
$col->ensureIndex(array('owner' => 1, 'perm.type' => 1, 'ca' => -1));
$col->ensureIndex(array('owner' => 1, 'perm.list' => 1, 'ca' => -1));

первый индекс предназначен для первой части критериев запроса, второй индекс предназначен для 2-й критерии, и 3-ий дизайн для 3-го критерия, а это показатель multikey.

типичный пост будет выглядеть так:

{
    "_id": "...",
    "owner": "001",
    "perm": {
        "type": "P",
        "list": []
    },
    "msg": "Nice dress!",
    "ca": 1390459269
}

еще пример:

{
    "_id": "...",
    "owner": "007",
    "perm": {
        "type": "C",
        "list": ["001", "005"]
    },
    "msg": "Nice day!",
    "ca": 1390837209
}

Я знаю об ограничении, существовавшем до MongoDB версии 2.6, которое предотвращает использование индексов при объединении $или с sort(). Вопрос в соответствии с этим http://jira.mongodb.org/browse/SERVER-1205 должен был быть исправлен в 2.6.

и уверен достаточно, explain () теперь показывает использование моих индексов, где это не было раньше в 2.4. Но когда я запускал запрос, он теперь намного медленнее, чем когда он не использовал никаких индексов. explain () показал, что nscanned намного выше, чем ожидалось. После некоторого поиска я нашел эту проблему https://jira.mongodb.org/browse/SERVER-3310

3 ответов


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

порядок полей в индексе должен быть:

1. First, fields on which you will query for exact values.
2. Second, fields on which you will sort.
3. Finally, fields on which you will query for a range of values.

поэтому, следуя этому совету, вы можете попробовать с помощью этих индексов:

$col->ensureIndex(array('owner' => 1, 'ca' => -1));
$col->ensureIndex(array('ca' => -1, 'owner' => 1, 'perm.type' => 1));
$col->ensureIndex(array('perm.list' => 1, 'ca' => -1, 'owner' => 1));

Edit:

из вашего объяснения, если вы тестируете на небольших наборах данных, полная коллекция быстро, потому что MongoDB не нужно проходить через много документы. Вы должны попробовать сделать тест с e.g 10000 документов, чтобы увидеть реальную разницу. Значения для ваших полей в индексах должны быть достаточно разными, чтобы обеспечить избирательность индексов для ваших запросов (например, не все документы от одного владельца).


TL; DR: я считаю, что вы используете неправильный алгоритм/структура данных для инструмента, или наоборот. Я бы предложил использовать подход fan-out как обсуждается в этом так вопрос или мой блог. Извините за бесстыдную рекламу моих предыдущих сообщений, но нет смысла повторять эту информацию здесь.


философия MongoDB, вопреки типичной SQL-философии, должна быть скорее писать-тяжелый. Ты по сути, пытается реализовать алгоритм ранжирования в запросе MongoDB, но философия запросов MongoDB - "запрос на примере". Это не подходит.

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

все, что вы делаете здесь очень трудно контролировать. Вы не только хотите, чтобы MongoDB использовал индекс пересечение (новое в 2.6, работает только с двумя индексами), но вы также объединяете его с $in запросы и составные индексы. Это много, чтобы спросить, и если количество друзей в $in растет слишком много, вам все равно не повезло. То же самое верно, если новость передается слишком многим людям, в худшем случае документ растет за 16 МБ. Растущие документы дороги, сложные запросы дороги, большие документы дороги тоже.

Я предлагаю вам использовать подход fan-out для новостных лент, где вы можете реализовать очень сложный алгоритм ранжирования в коде, а не в MongoDB.

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

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


после 3 дней тестирования и исследований причина, вызывающая неэффективные запросы, теперь ясна. MongoDB в текущей версии (2.6.1) по-прежнему не может оптимизировать запросы, которые используют $или, $in, limit () и sort () одновременно. The https://jira.mongodb.org/browse/SERVER-1205 и https://jira.mongodb.org/browse/SERVER-3310 исправления, каждый только улучшил производительность по запросам, имеющим 3 из 4 операций, перечисленных выше. При введении 4-й операции в запрос, оптимизация выходят в окно. Такое поведение наблюдается при полном сканировании индекса и документа в предложении $or, даже если указан limit(10).

попытка решить эту проблему, разбив предложения $or по отдельности и объединить результаты на стороне приложения, хотя это и возможно, столкнулась с серьезными препятствиями, когда я попытался реализовать разбиение на страницы.

мое текущее решение, таким образом, состоит в том, чтобы придумать эквивалентный запрос к исходному запросу, при этом только 3 из 4 операций. Я решил "сгладить" оператор "$in", превратить каждый элемент в массиве $friends в другое условие "$или " с точным значением владельца для запроса. Поэтому вместо того, чтобы иметь 3' $или 'условия в моем исходном запросе, у меня теперь столько же "$или "условий, сколько у меня есть элементов в моем массиве $friends, плюс 2 других исходных "$или " условия.

запрос оптимизирован. При запуске с explain (), nscannedObjects и nscanned теперь путь вниз, к ценностям, которыми они должны быть. Рассматривая документацию по "$or " с указанием

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

Это может быть приемлемым решением с точки зрения производительности. Надеюсь, это поможет всем, кто столкнулся с теми же проблемами, что и я.