2016-06-07 4 views
1

У меня есть 2 Монго коллекции:MongoDB запросов объединения двух коллекций

компании: Каждая запись является компанией с большим количеством полей (город, страна, и т.д.) ->100k rows

{company_id:1, country:"USA", city:"New York",...} 
{company_id:2, country:"Spain", city:"Valencia",... } 
{company_id:3, country:"France", city:"Paris",... } 

оценки : Имеются блоки дат, и каждый блок имеет оценку company_id +, пример ->100k rows in each block

{date: 2016-05-29, company_id:1, score:90} 
{date: 2016-05-29, company_id:2, score:87} 
{date: 2016-05-29, company_id:3, score:75} 
... 
{date: 2016-05-22, company_id:1, score:88} 
{date: 2016-05-22, company_id:2, score:87} 
{date: 2016-05-22, company_id:3, score:76} 
... 
{date: 2016-05-15, company_id:1, score:91} 
{date: 2016-05-15, company_id:2, score:82} 
{date: 2016-05-15, company_id:3, score:73} 
... 

Цель:

Я хочу, чтобы получить список компаний, которые могут быть отфильтрованы по некоторым полям (страна, город, ...) + свой новый счет (в 2016-05-29), ordered by score descending

То есть: фильтр в сборе, один фильтре + порядка в другой коллекции

Примечание: Там есть индекс scores.date, и мы можем найти/заранее рассчитать легко и быстро самую высокую дату (2016-05-29 в этот пример)

Попытки:

Я пробовал запрос aggregate с использованием $lookup. Когда фильтр завершен (и количество малых компаний), запрос выполняется быстрее.

запроса, как показано ниже: -

db.companies.aggregate([ 
{$match: {"status": "running", "country": "USA", "city": "San Francisco", 
     "categories": { $in: ["Software"]}, dummy: false}}, 
{$lookup: {from: "scores", localField: "company_id", foreignField: "company_id", as:"scores"}}, 
{$unwind: "$scores"}, 
{$project: {_id:   "$_id", 
      "company_id": "$company_id", 
      "company_name": "$company_name", 
      "status":  "$status", 
      "city":   "$city", 
      "country":  "$country", 
      "categories": "$categories", 
      "dummy":  "$dummy", 
      "score":  "$scores.score", 
      "date":   "$scores.date"}}, 
{$match: {"date" : ISODate("2016-05-29T00:00:00Z")}}, 
{$sort: {"score":-1}} 
],{allowDiskUse: true}) 

Но когда фильтр мал или пустой (больше компаний), то $sort часть занимает несколько секунд.

db.companies.aggregate([ 
{$match: {"status": "running"}}, 
{$lookup: {from: "scores", localField: "company_id", foreignField: "company_id", as:"scores"}}, 
{$unwind: "$scores"}, 
{$project: {_id:   "$_id", 
      "company_id": "$company_id", 
      "company_name": "$company_name", 
      "status":  "$status", 
      "city":   "$city", 
      "country":  "$country", 
      "categories": "$categories", 
      "dummy":  "$dummy", 
      "score":  "$scores.score", 
      "date":   "$scores.date"}}, 
{$match: {"date" : ISODate("2016-05-29T00:00:00Z")}}, 
{$sort: {"score":-1}} 
],{allowDiskUse: true}) 

Наверное, потому, что количество компаний, найденных фильтром. 59 строк проще заказать, чем 89K

> db.companies.count({"status": "running", "country": "USA", "city": "San Francisco", "categories": { $in: ["Software"]}, dummy: false}) 
59 
> db.companies.count({"status": "running"}) 
89043 

Я попробовал другой подход, агрегирование по баллам, фильтр по дате, сортировка по баллам (дата индекса + оценка очень полезно здесь), и все очень быстро, до последнего $match когда фильтр компании атрибуты

db.scores.aggregate([ 
{$match:{"date" : ISODate("2016-05-29T00:00:00Z")}}, 
{$sort:{"score":-1}}, 
{$lookup:{from: "companies", localField: "company_id", foreignField: "company_id", as:"companies"}}, 
{$unwind:"$companies"}, 
{$project: {_id:    "$companies._id", 
      "company_id": "$companies.company_id", 
      "company_name": "$companies.company_name", 
      "status":  "$companies.status", 
      "city":   "$companies.city", 
      "country":  "$companies.country", 
      "categories": "$companies.categories", 
      "dummy":   "$companies.dummy"}}, 
      "score":   "$score", 
      "date":   "$date" 
{$match:{"status": "running", "country":"USA", "city": "San Francisco", 
     "categories": { $in: ["Software"]}, dummy: false}} 
],{allowDiskUse: true}) 

, используя этот подход, большой фильтр (предыдущий пример) очень медленно, а маленький фильтр (только {"status": "running"}) быстрее

Любой путь объединить оба ections, фильтр в обоих из них и порядок по одному полю?

+0

Дело для присоединений https://www.mongodb.com/blog/post/joins-and-other-aggregation-enhancements-coming-in-mongodb-3-2-part-1-of-3-introduction – Leo

ответ

1

Как я мог видеть, есть только пара баллов за компанию (не много) в разные даты. Так что это своего рода 1: несколько отношений.

Итак, первое, что мне подсказывает: почему бы не поместить оценки в БД компании?

{ company_id:1, 
    country:"USA", 
    city:"New York", 
    ... 
    scores: [ 
    {date: 2016-05-29, score:90}, 
    ... 
    ] 
} 

Таким образом, структура больше соответствует вашему шаблону доступа, вы можете полностью пропустить часть поиска. Смысл, вы можете определить правильный индекс и использовать find() вместо агрегации.

Помимо этого, мне было интересно, почему вы используете флаг allowDiskUse:true, документы 100k не звучат так много, и они должны полностью помещаться в память даже в ограниченный (128M) буфер конвейерной агрегации.

Чтобы объяснить, почему фильтр (сокр = не очень избирателен, долго = очень избирателен) ведет себя по-разному, в зависимости от коллекции вы начинаете (баллы против компаний)

  • компания первой:
    • short фильтр: многие компании соответствуют критериям, поэтому многие компании должны быть отсортированы (вам нужны все в памяти для сортировки). Если часть результирующего набора записывается на диск, это может занять довольно много времени.
    • длинный фильтр: только небольшой набор компаний совпадают, только несколько компаний, должны быть отсортированы в конце концов, вероятно, полностью в памяти
  • счет первый - дата может иметь влияние, так как он определяет, как многие компании страдает
    • длинного фильтра в конце: результат предыдущих шагов агрегации должен быть найден, чтобы найти соответствующие элементы. Для этого нельзя использовать индекс. Таким образом, операция сопоставления может занять больше времени, поскольку необходимо оценивать больше критериев - вероятно, против данных на диске.
    • короткий фильтр в конце: результат предыдущих этапов необходимо искать только один раз.

Так что вы должны проверить:

  • отключить allowDiskUse, проверьте, если запрос все еще помещается в память или проверить в TMP файлов, независимо от того, как данные действительно записаны на диск
  • ограничить область поиска, уменьшая объем данных, подлежащих обработке
  • изменить схему, чтобы лучше соответствовать вашему шаблону доступа
Смежные вопросы