Тема: Node.JS + MongoDB
Показать сообщение отдельно
Старый 03.09.2013, 19:27   #10
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Node.JS + MongoDB

Я тут щас aggregation и mapReduce изучаю. Заодно открыл для себя $where.
$where - позволяет стрингой передать сроку кода, например:

Пример player'а:
{
  
_id42,
  
prizes: [
    { 
created123type'foo' },
    { 
created234type'bar' }
  ]

И вот такой query:
players.count({ $where'this.prizes.length >= 20' }, function(errcount) {
  if(!
err) {
    
// count - сколько игроков имеет призов больше чем 20
  
} else {
    ...
  }
}); 
Если в бд вбить:
db.players.find({ $where'this.prizes.length >= 20' }).explain() 
То выдаст факт того что тут ничего естественно не индексируется, индекс тоже игнорируется.
Следственно например для collection'а с 2800 записями, у меня затрачивается 72мс (на простом Mac'е), что ужасно долго для таких запросов.
Следственно в реальном мире лучше кешировать длину массива рядом с массивом.

Но всё же, если у тебя например запрос на 32 записи (например), то такой код - вполне приемлем. Нужно исследовать как там с блоком всего процесса (блокирует ли другие запросы и т.п.).


aggregation - это интерестный монстрик, по получению всяких данные и более сложных манипуляций над документами.
Например у меня есть записи игроков, и у каждого игрока есть любимая команда. Мне нужно получить список всех команд и сколько игроков её указали как любимую:

players.collection().aggregate([
  {
    
$match: {
      
favourite: { $existstrue // ищем всех игроков у которых указан 'favourite'
    
}
  }, {
    
$group: { // групируем все записи
      
_id"$favourite"// по favourite полю
      
total: { $sum// и прибавляем 1 к total
    
}
  }, {
    
$sort: { // потом сортируем по убыванию total
      
total: -1
    
}
  }
], function(
errdata) {
  if (!
err) {
    
//
  
} else {
    ...
  }
}); 
favourite - это id команды простое число.
Всего там 20 команд.
В результате получу массив с до 20 записями такого вида:
{
  
_id 685,
  
total 813

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


mapReduce - это ещё следующий шаг. Когда aggregation предоставляет операторы, и там важна их поочерёдность и т.п., то mapReduce принимает лишь 2 функции и query фильтр.
Первая функция обрабатывает каждый документ (удовлетворяющий query) и если нужно вызывает emit, что передаёт уже обработанные данные (можно и не обработанные) в reduce функцию, где уже дальше мы имеем дело с групированными данными.
Примеров много можно привезти, в моём случае, нужно было узнать сколько призов в среднем выйграл каждый игрок. Есть 2800 записей. И нужно получить общее арифметическое колличества призов:

players.collection().mapReduce(function() {
  
emit('stats'this.prizes.length); // map - для всех запускаем reduce с групированием (_id будет 'stats')
}, function(keyvalues) { // reduce
  
var total 0// всего призов
  
var values.length;
  while(
i--) { // для каждой длины массива призов
    
total += values[i]; // складываем
  
}
  return 
total values.length// и делим на количество значений
}, {
  
query: { confirmedtrue }, // только игрокам кто подтвердил свой аккаунт
  
out: { inline}
}, function(
errdata) {
  if (!
err) {
    
// data[0].value - общее арифметическое длин массива призов у каждого игрока
  
} else {
    ...
  }
}); 
На самом деле это можно было сделать и aggregator'ом, но я хотел попробовать mapReduce.
Снова, для тысячей записей - это не шустро, но для статистики - самое то.
(Offline)
 
Ответить с цитированием
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
pax (04.09.2013), SpiritSound (28.11.2015)