forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   JavaScript / HTML (http://forum.boolean.name/forumdisplay.php?f=136)
-   -   Node.JS + MongoDB (http://forum.boolean.name/showthread.php?t=18507)

pax 01.09.2013 16:45

Node.JS + MongoDB
 
Посоветуйте пожалуйста чтиво по этому поводу.

Из основных вопросов: Пока не понял как правильно работать с открытием соединения к базе, хочу использовать модуль mongodb и думаю никакие mongoose не надо. Цель - создать REST API для начала.

moka 01.09.2013 22:00

Ответ: Node.JS + MongoDB
 
Не нужно книжек и чтивы. Если работал с mongo то там всё проактически идентично официальным докам (в случае с mongoose это не так).
Я юзал mongoose и потом перешёл на mongo-native от самих 10gen - и не жалею вовсе. Мне эти schemas - нафиг не сдались. Суть mongo - в её динамике, и нефиг пытаться прикручивать правила и структуру к collection'ам подобно это SQL бд.

Офф дока - очень полезна, т.к. как уже говорил почти идентична к mongo-native в использовании.
По факту - коммандная строка для mongo тоже на JavaScript'е ;)

Про соединение, сперва тебе нужно создать объект куда коннектиться:
PHP код:

var mongodb = require('mongodb');
var 
access = new mongodb.Server('127.0.0.1'27017, { auto_reconnecttrue });

new 
mongodb.Db('database'access, { safetrue }).open((function (errdb) {
  if (
err) throw err;
  
// db - хендл для бд (client)
}); 

Далее тебе нужно будет получить сами collection'ы, я это делаю один раз при коннекте в самом каллбеке (точнее я создаю евент и слущаю его, таким образом разные модули получают свои collection'ы:
PHP код:

var users db.collection('users'); 

Как только у тебя есть collection можешь уже работать с данными:
PHP код:

users.insert({ name'test' });

users.find({ }).toArray(function(errdata) {
  if (
err) throw err;

  
console.log(data);
}); 

Вот тут исчерпывающие примеры:
https://github.com/mongodb/node-mong...aster/examples

Есть пару тонкостей:
  • Реконнект при обрыве с бд, я не тестировал, но может даже collection хэндлы нужно будет переполучать (а может и нет). В общем тестируй если такие сценарии бывают.
  • _id - по стандарту mongo держит ObjectID - это своего рода просто string и в консоль по факту будет выводиться просто как string что пиздецки сбивает порой. Так что не забывай что каждый раз если имеешь ID например с REST запроса, нужно создать ObjectID объект из string'и.
  • Если хочешь просто номер для _id (не рекомендуется если предполагается шардинг или репликация бд). То у меня был очень простой выход: заводим collection 'ids' и имеем _id - имя collection и inc - число инкремента. Затем если хотим создать объект в бд делаем:
    PHP код:

    ids.findAndModify({ _id'users' }, { }, { $inc: { inc} }, { new: true }, function(errid) {
      if (
    err) throw err;

      
    // id == твой новый id
    }); 

  • find получает список, и в mongo-native модуле это не callback (это единственный метод с таким исключением). Так что юзаем спец модуль "toArray":
    PHP код:

    users.find({ }).toArray(function(errdata) {
      
    }); 

  • Для REST советую использовать express.js т.к. роутинг будет очень простым и удобным.
  • Валидация - важно. Типы данных бывают разные (если это POST).
  • Middleware - в express есть отличная тема с middeware, так можно написать модуль с парсингом пагинации и юзать его везде. Пример: заводим файл pagination.js и его содержимое:
    PHP код:

    exports.middleware = function(data) {
      return function(
    reqresnext) { // возвращаем middleware функцию
        
    var limit data.limit data.limit 0// limit по стандарту 0
        
    var skip 0// skip тоже нуль

        
    if (req.query.limit) { // если в запросе есть limit
          
    var tmp parseInt(req.query.limit); // парсим
          
    if (tmp != NaN && tmp 0) { // если хорошее число
            
    limit tmp;
          }
        }
        if (
    req.query.skip) { // если в запросе есть skip
          
    var tmp parseInt(req.query.skip); // парсим
          
    if (tmp != NaN && tmp 0) { // если хоршее число
            
    skip tmp;
          }
        }

        if (
    data.max) { // если указан максимальное число на страницу
          
    limit Math.min(limitdata.max); // ограничиваем limit
        
    }
        if (
    data.min) { // если указано минимальное число на страницу
          
    limit Math.max(limitdata.min); // ограничиваем limit
        
    }

        
    req.paginator = { // создаём объект с limit и skip данными в объекте запроса
          
    limitlimit,
          
    skipskip
        
    };
        
    next(); // продолжаем цепочку middleware
      
    }
    }; 

    Далее в коде:
    PHP код:

    var paginator = require('./paginator.js').middleware// получаем handle middleware пагинатора
    app.get('/users'paginator({
      
    limit8,
      
    min4,
      
    max64
    }), function(reqresnext) { // устанавливаем get запрос с пагинатором
      
    users.find({ }, { skipreq.paginator.skiplimitreq.paginator.limit }).toArray(function(errdata) { // ищем в бд с данными из пагинатора
        
    if (errnext(err); // если ошибка, бежим в express хэндлер ошибок
        
    res.json(data); // или шлём данные с бд джонсоном
      
    });
    }); 


Если есть конкретные вопросы - задавай.
Я пишу по работе RESTful API с mongo-native и express'ом ну и другими всякими плюшками.

pax 01.09.2013 22:49

Ответ: Node.JS + MongoDB
 
Правильно ли я понимаю, что для каждого REST запроса надо создавать свое подключение? Мне сначала подумалось создать одно подключение к базе на старте приложения и потом его использовать. Потом я посмотрел некоторые рест библиотеки для монго и там для каждого запроса создается свой экземпляр Db. Такой подход верный?

Еще один вопрос - есть у nodejs возможность запускаться в многопроцессном режиме, это не стоит использовать для REST API?

moka 01.09.2013 23:44

Ответ: Node.JS + MongoDB
 
Не нужно для каждого запроса свой db хэндлер - это оверкил.
Там блокировка на уровне бд будет а не на уровне node, следственно иметь более одного хэндла - смысла нету, только замедляет процесс.
Возможно mongo-native кеширует коннекты и будет выдавать уже подконекченный - но это снова лишние телодвижения.

Относительно мультипоточности - ты говоришь о cluster?
Мы используем - 4 worker'а. Всё работает как полагается, естественно если у тебя есть сессия, то её нужно хранить вне процесса, мы используем connect-mongo, тем самым не важно в какой worker идёт запрос.

По опыту пришёл к такому выводу - если у тебя есть CMS и write'ы очень редкие, и обслуживаются определённой группой людей, следственно таскать с собой функционал для CMS в основном API не оправдано.
Для этого я сделал отдельные запросы для PUSH'ей, это немного идёт против логики REST'а, т.к. /url будет отличаться (я сделал субдомейн для этого, а руты были те же), но зато когда нужно обновить основной API, не нужно было перегружать CMS и т.п.

Но это зависит от ситуации.

cluster'ы - реально помогают, их количество обычно параллельно количеству ядер. Мы на ec2 xlarge (4 CPU) 4 worker'а запускаем - и всё ок.

pax 03.09.2013 10:46

Ответ: Node.JS + MongoDB
 
Еще пара вопросов:

1. Как правильно обрабатывать ошибки, чтобы при их происхождении не падал весь процесс. Можно конкретно для express и mongodb.

2. Я так понимаю что кластер можно использовать как защиту от падения процесса, т.е. кластер при падении одного воркера может запустить нового воркера? И если надо будет перезапустить сервер, то просто необходимо будет перезапустить кластер да?

3. Какой модуль лучше использовать для логирования? Особенно если используется кластер и несколько воркеров.

moka 03.09.2013 13:53

Ответ: Node.JS + MongoDB
 
Цитата:

Сообщение от pax (Сообщение 266375)
1. Как правильно обрабатывать ошибки, чтобы при их происхождении не падал весь процесс. Можно конкретно для express и mongodb.

В примерах я делал 'throw', обычно я так не делаю.
Важно понимать что ошибки - это важно, и их не должно быть.
Если в тестировании ошибок в запросах к бд небыло, а потом есть, значит твой query слишком динамичен и нужно его немного нормализировать (это 90% случаев).
Я у себя в коде так делаю:

Ранее при настройке express'а:
PHP код:

app.configure(function() {
  
app.use(express.errorHandler());
  ...
}); 

PHP код:

app.get('/users', ..., function(reqresnext) {
  
users.find({ }, { skipreq.paginator.skiplimitreq.paginator.limit }).toArray(function(errdata) {
     if (!
err) {
       
res.json(data);
     } else {
       
next(err);
     }
  });
}); 

Далее где-то в коде самый последний middleware будет для ловли ошибок:

PHP код:

app.use(function(errreqresnext) {
  var 
obj = {
    
errortrue,
    
messageerr.message
  
};
  if (
development) {
    
obj.stackerr.stack;
  }
  
res.json(obj);
}); 



Цитата:

Сообщение от pax (Сообщение 266375)
2. Я так понимаю что кластер можно использовать как защиту от падения процесса, т.е. кластер при падении одного воркера может запустить нового воркера? И если надо будет перезапустить сервер, то просто необходимо будет перезапустить кластер да?

Да. Но нужно следить точно когда что-то падает или нет.
Вообще есть "защито" от падения процесса. Есть event который ловит не словленные exception'ы. Но естественно если что-то жесть как пошло не так, то нужно и реагировать соответственно, а не тупо игнорировать.
Но всё равно:

PHP код:

process.on('uncaughtException', function(err) {
  
console.log('Caught exception: ' errerr.stack);
}); 



Цитата:

Сообщение от pax (Сообщение 266375)
3. Какой модуль лучше использовать для логирования? Особенно если используется кластер и несколько воркеров.

Я пока настраивал node.js как сервис в linux, тупо выводил консоль в файл, естественно это не супер решение. И исследовал этот вопрос. Лидер тут конкретный: winston https://github.com/flatiron/winston , отлично поддерживается.
Суть у него в том что он имеет основу для логгирования, но логит в транспорты, которые могут быть что угодно: консоль, файл, бд, MQ, redis, socket, amazon alerts, email, и т.п.
Есть стандартный набор транспортов которых обычно хватает с головой.

Я лично юзать буду файл + бд (с индексом просрочивания неделю), и ZeroMQ в мелкий процесс который через socket.io будет слать логи от разных процессов мне на мелкую страничку мониторинга. Таким образом если что-то случиться, я сразу могу вычислить с кем, где и т.п.

pax 03.09.2013 15:10

Ответ: Node.JS + MongoDB
 
Хочу часть логики сделать на хранимом js в MongoDB, это лучше чем делать логику в NodeJS?

Upd: кстати как правильно вызывать хранимые функции?

Upd2: Получилось примерно так:
PHP код:

db.getUser = function (user_idcallback) {
        
db.eval(new Code('return user_get(user_id);', {'user_id'user_id}), {nolocktrue}, callback);
    } 

сама функция


Это нормально или я что-то не так делаю?

moka 03.09.2013 16:58

Ответ: Node.JS + MongoDB
 
Я предпочитаю ничего не хранить в DB. Можно лишь мелкие туулзы или функции утилит, но не относящиеся к логике твоего приложения - это ответственность твоего API.
Да и сразу по твоей функции - ты доверяешь назначение user_id кому? БД, или логике приложения?
Я пытался сделать подобное - вставлять если нету, но потом пришёл к выводу - нефиг! Есть куча сценариев где тебе нужно получить юзверя - но он может и не быть там, следственно и вставлять его не нужно.
Так что лучше разделить эту логику.

Да и если ты 100% вставляешь, то не используй 'save', а используй 'insert'.
save - весьма опасен, тем что если будешь обновлять что-то (ожидая update), а документа такого не будет, а обновление будет partial (только пару полей), то вставиться "не полный" документ, что твоя логика приложения не будет ожидать.

И мелкая заметка, в node, для даты проще писать так:
Date.now() - получает timestamp.

pax 03.09.2013 18:21

Ответ: Node.JS + MongoDB
 
У меня это авторегистрация такая. id это id пользователя в соц сети. На счет insert спасибо, буду иметь ввиду. И я уже решил делать логику в ноде, а не в БД, а то получается какая-то фигня (не нравится мне такой код) с вызовами.

moka 03.09.2013 19:27

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

Пример player'а:
PHP код:

{
  
_id42,
  
prizes: [
    { 
created123type'foo' },
    { 
created234type'bar' }
  ]


И вот такой query:
PHP код:

players.count({ $where'this.prizes.length >= 20' }, function(errcount) {
  if(!
err) {
    
// count - сколько игроков имеет призов больше чем 20
  
} else {
    ...
  }
}); 

Если в бд вбить:
PHP код:

db.players.find({ $where'this.prizes.length >= 20' }).explain() 

То выдаст факт того что тут ничего естественно не индексируется, индекс тоже игнорируется.
Следственно например для collection'а с 2800 записями, у меня затрачивается 72мс (на простом Mac'е), что ужасно долго для таких запросов.
Следственно в реальном мире лучше кешировать длину массива рядом с массивом.

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


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

PHP код:

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 записями такого вида:
PHP код:

{
  
_id 685,
  
total 813


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


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

PHP код:

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.
Снова, для тысячей записей - это не шустро, но для статистики - самое то.

pax 04.09.2013 12:21

Ответ: Node.JS + MongoDB
 
Заметил что таймштамп в ноде в миллисекундах, а не в секундах как это в php. Поделил все на 1000)

moka 04.09.2013 16:34

Ответ: Node.JS + MongoDB
 
Цитата:

Сообщение от pax (Сообщение 266439)
Заметил что таймштамп в ноде в миллисекундах, а не в секундах как это в php. Поделил все на 1000)

Дык держи в миллисекундах, их и так достаточно, и не вижу бенефита в секундах :)
Тем более что если захочешь перевезти timestamp в дату то:
PHP код:

var date = new Date(timestamp); 

Иначе прийдётся ещё и на 1000 умножать..

pax 04.09.2013 18:08

Ответ: Node.JS + MongoDB
 
Бенефит: в базе либо Int32 если секунды, либо double если миллисекунды.

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

moka 04.09.2013 18:32

Ответ: Node.JS + MongoDB
 
Цитата:

Сообщение от pax (Сообщение 266462)
Бенефит: в базе либо Int32 если секунды, либо double если миллисекунды.

Исходя из того как бд хранит данные и создаёт файлы с зазорами - это не даст практически никакой разницы, если конечно у тебя документы не просто _id и timestamp.
http://stackoverflow.com/questions/6...tes-in-mongodb

Цитата:

Сообщение от pax (Сообщение 266462)
PS: начинаю втягиваться, на первый взгляд сложнее чем на php, потому что надо писать асинхронный код, с другой стороны вроде как и интереснее получается.

На самом деле как привыкнешь, потом будет даже не сложнее.

Правда появяться некоторые "трудности", например запустить сразу 3 асинхронных функции, и потом финальную функцию, или сделать очередь асинхронных функций. Есть promise и async.js, но я как обычно юзаю свой вариант решения простой задачи:

PHP код:

function jobsQueue(jobs) {
  if (
jobs && jobs instanceof Array && jobs.length 0) {
    var 
count jobs.length;
    var 
= -1;
    var 
next = function() {
      if (++
count) {
        
jobs[i](next);
      }
    }
    
next();
  }
}

function 
jobsQueueAsync(jobscomplete) {
  if (
jobs && jobs instanceof Array && jobs.length 0) {
    var 
count jobs.length;
    var 
done = function() {
      if (--
count == && complete) {
        
complete();
      }
    }
    for(var 
0len jobs.lengthlen; ++i) {
      
jobs[i](done)
    }
  }


Первая запускает поочерёдно функции.
Вторая запускает все асинхронно и финальную когда все закончили.

Пользоваться так:
PHP код:

jobsQueue([
  function(
next) {
    ...
    
next();
  }, function() {
    ...
  }
]);

jobsQueueAsync([
  function(
next) {
    ...
    
next();
  },
  function(
next) {
    ...
    
next();
  }
], function() {
  ... 
done
}); 

Ну и тесты:
PHP код:

jobsQueue([
  function(
next) {
    
console.log('1');
    
next();
  },
  function(
next) {
    
console.log('2a')
    
setTimeout(function() {
      
console.log('2b');
      
next();
    }, 
400);
  },
  function(
next) {
    
console.log('3a');
    
setTimeout(function() {
      
console.log('3b');
      
next();
    }, 
100);
  },
  function() {
    
console.log('4');
  }
]); 

Выдаст: 1 2a 2b 3a 3b 4
Как видишь он даже не начинает выполнение следующей функции до выполнения прошлой.

PHP код:

jobsQueueAsync([
  function(
next) {
    
console.log('1');
    
next();
  },
  function(
next) {
    
console.log('2a')
    
setTimeout(function() {
      
console.log('2b');
      
next();
    }, 
400);
  },
  function(
next) {
    
console.log('3a');
    
setTimeout(function() {
      
console.log('3b');
      
next();
    }, 
100);
  } ],
  function() {
    
console.log('4');
  }
); 

Выдаст: 1 2a 3a 3b 2b 4
И тут видно что он запускает все сразу, но финальную только когда каждая функция запустит next() по завершению (асинхронно или нет).

moka 04.09.2013 18:36

Ответ: Node.JS + MongoDB
 
В тему асинхронного кода, юмор:

var result = fs.readFile(‘data.json’, function () {})


Часовой пояс GMT +4, время: 02:32.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot