MongoDB使用未知的findOne方法导致大延迟 - New Relic

时间:2016-10-12 07:34:52

标签: javascript node.js mongodb express newrelic

我设置newrelic以更好地了解我的应用程序存在哪些瓶颈,并且我发现了一个我似乎无法弄清楚的问题。

我的大部分延迟都是由mongoDB user.fineOne引起的,但主要问题是我似乎无法找到代码中的位置。

在下图中,您可以看到调用API get/all/proposal端点的跟踪详细信息。它首先是14个方法调用,它是我的server.js中的中间件,之后是中间件:验证,其中有 MongoDB用户findOne ,那就是哪里延迟是。

enter image description here

获取/所有/提案的代码:

app.get('/all/proposals',isLoggedIn,function(req, res) {
   Proposal.find().sort({proposalNo: -1}).limit(5).exec(function(err,proposal){
       if(err){
           console.log(err);
       }else{
           console.log("All Proposals " + proposal);
           res.json(proposal);
       }
   });
});

现在我无法看到我在get/all/proposals 的MongoDB上运行User.findOne调用。最初我认为是isLoggedIn中间件,我检查用户是否在会话中(Passport.js)但是你可以看到isLoggedIn Middleware只需要0.0222(ms)。

同样的问题出现在多个API端点上,即get/caseStudy,它始终是user.findOne下面的另一个示例:

enter image description here

任何人都可以帮我解决这个问题。如果您需要更多细节,请告诉我,我猜你会这样。

更新 Server.js代码

 // set up ======================================================================

require('newrelic');
var express  = require('express');
var app      = express();                               // create our app w/ express
var server = require('http').createServer(app);
var mongoose = require('mongoose');                     // mongoose for mongodb
var port     = process.env.PORT || 8080;                // set the port
var database = require('./config/db');                  // load the database config
var morgan = require('morgan');                         // log requests to the console (express4)
var bodyParser = require('body-parser');                // pull information from HTML POST (express4)
var methodOverride = require('method-override');        // simulate DELETE and PUT (express4)
var passport = require('passport');
var flash    = require('connect-flash');
var session      = require('express-session');
var cookieParser = require('cookie-parser');
var compression = require('compression');
var nodemailer = require('nodemailer');
var busboy = require("connect-busboy");

// configuration ===============================================================


mongoose.connect(database.url);                                 // connect to mongoDB database on modulus.io
require('./config/passport')(passport);
app.use(express.static(__dirname + '/public'));  
app.use(express.static(__dirname + '/views'));  // set the static files location /public/img will be /img for users
app.use(busboy());
app.use(compression()); //use compression
app.use(morgan('dev'));                                         // log every request to the console
app.use(bodyParser.urlencoded({'extended': true}));             // parse application/x-www-form-urlencoded
app.use(bodyParser.json());                                     // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
app.use(cookieParser());                                        // read cookies (needed for auth)
app.set('view engine', 'ejs');                                  // set up ejs for templating

// required for passport
app.use(session({ secret: '',  resave: false, saveUninitialized: false })); // session secret
app.use(passport.initialize());
app.use(passport.session());                                    // persistent login sessions
app.use(flash());                                               // use connect-flash for flash messages stored in session

 // routes ======================================================================

require('./routes/index.js')(app, passport); // load our routes and pass in our app and fully configured passport
//require('./routes/knowledgeBase/index.js')(app, passport);
require('./routes/bios/index.js')(app, passport);

 // listen (start app with node server.js) ======================================

app.listen(port);
console.log("App listening on port " + port);

更新2: Passport.js

// config/passport.js

// load all the things we need
var LocalStrategy   = require('passport-local').Strategy;
var crypto = require("crypto");
var api_key = '';
var domain = '';
var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
// load up the user model
var User            = require('../app/models/user');

 // expose this function to our app using module.exports
 module.exports = function(passport) {

// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session

// used to serialize the user for the session
passport.serializeUser(function(user, done) {
    done(null, user.id);
});

// used to deserialize the user
passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
        done(err, user);
    });
});

// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'

passport.use('local-signup', new LocalStrategy({
        // by default, local strategy uses username and password, we will override with email
        firstNameField: 'firstName',
        lastNameField: 'lastName',
        usernameField: 'email',
        passwordField: 'password',
        jobTitleField: 'jobTitle',
        startDateField: 'startDate',
        passReqToCallback: true // allows us to pass back the entire request to the callback
    },

    function(req, email, password, done) {

        // find a user whose email is the same as the forms email
        // we are checking to see if the user trying to login already exists
        User.findOne({
            'email': email
        }, function(err, user) {
            // if there are any errors, return the error
            if (err)
                return done(err);

            // check to see if theres already a user with that email
            if (user) {
                return done(null, false, {
                    message: 'That email is already taken.'
                });
            }
            else {

                var token = crypto.randomBytes().toString();
                // if there is no user with that email
                // create the user
                var newUser = new User();

                // set the user's local credentials
                newUser.firstName = req.body.firstName;
                newUser.lastName = req.body.lastName;
                newUser.email = email;
                newUser.password = newUser.generateHash(password); // use the generateHash function in our user model
                newUser.jobTitle = req.body.jobTitle;
                newUser.startDate = req.body.startDate;
                newUser.birthday = req.body.birthday;
                newUser.region = req.body.region;
                newUser.sector = req.body.sector;
                newUser.accountConfirmationToken = token;
                newUser.accountConfirmationTokenExpires = Date.now() + 3600000;
                newUser.accountVerified = 'false';



                // save the user
                newUser.save(function(err) {
                    if (err)
                        throw err;
                    else {
                        return done(null, newUser);
                    }
                });
            }

        });

    }));

// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'

passport.use('local-login', new LocalStrategy({
    // by default, local strategy uses username and password, we will override with email
    usernameField : 'email',
    passwordField : 'password',
    passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form

    // find a user whose email is the same as the forms email
    // we are checking to see if the user trying to login already exists
    User.findOne({ 'email' :  email }, function(err, user) {
        // if there are any errors, return the error before anything else
        if (err)
            return done(err);

        // if no user is found, return the message
        if (!user)
            return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash

        // if the user is found but the password is wrong
        if (!user.validPassword(password))
            return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata

        if(user.accountVerified == 'false')    
            return done(null, false, req.flash('loginMessage', 'Looks like you have not verified your account after registeration.'));
        else
            user.lastLogin = Date.now();
            user.save(function(err) {
                if (err)
                    throw err;
                else {
        // all is well, return successful user
                    return done(null, user);
                }
            });
    });

}));

};

更新3: isLoggedIn功能

 // route middleware to make sure a user is logged in
  function isLoggedIn(req, res, next) {

// if user is authenticated in the session, carry on 
if (req.isAuthenticated())
    return next();

    // if they aren't redirect them to the home page
    res.redirect('/');
 }

更新4: 获取提案的步骤

第1步: 首先加载提案页面

 app.get('/proposals',isLoggedIn,function(req, res) {
    res.render('proposals.ejs', {
        user : req.user // get the user out of session and pass to template
    });
});

第2步: proposal页面有一个angular.js控制器/工厂,它在页面加载时调用以下函数来获取数据。

// =========================================================================
// FUNCTIONS TO BE RUN WHEN THE PAGE FIRST LOADS TO POPULATE FRONT-END =====
// =========================================================================
$scope.intialize = function() {
    $scope.getAllSectors();
    $scope.getLatestProposals();
}

// ===============================
// GET LATEST *5* PROPOSALS  =====
// ===============================
factory.getLatestProposals = function() {

    return $http.get('/all/proposals')
        .then(function(response) {
            //promise is fulfilled
            deferred.resolve(response.data);

            console.log("readched the filtered project service!");

            //promise is returned
            // return deferred.promise;
            return response.data;

        }, function(response) {
            deferred.reject(response);

            //promise is returned
            return deferred.promise;
        });
};

第3步: /all/proposals路由称为

 // =======================
//  GET All Proposals =====
//  =======================
app.get('/all/proposals',isLoggedIn,function(req, res) {
   Proposal.find().sort({proposalNo: -1}).limit(5).exec(function(err,proposal){
       if(err){
           console.log(err);
       }else{
           console.log("All Proposals " + proposal);
           res.json(proposal);
       }
   });
});

1 个答案:

答案 0 :(得分:6)

查看您提供的代码后,性能日志中显示的.findOne()似乎是在搜索用户并对其进行身份验证时执行的。{/ p>

因此,似乎性能瓶颈发生在以下两个查询之一中:

/*
 * LOCAL LOGIN
 */
// find a user whose email is the same as the forms email
    // we are checking to see if the user trying to login already exists
    User.findOne({ 'email' :  email }, function(err, user) {
...


/*
 * LOCAL SIGNUP
 */
    // find a user whose email is the same as the forms email
    // we are checking to see if the user trying to login already exists
    User.findOne({
        'email': email
...

我看到您在两个护照本地策略中搜索email字段,因此您可以通过在该字段上创建索引来提高性能。

要尝试更优化LOCAL LOGIN findOne查询,您可以尝试在users字段上添加email集合的索引,如果您还没有:< / p>

// This index will optimize queries that search against the {email} field
db.users.createIndex({ email: 1});

更新#1

我发现可能是您的效果问题的相关Stack Overflow topic - 您应该更新express.js配置中的以下行:

app.use(session({ secret: '',  resave: false, saveUninitialized: false }));

app.use(session({ secret: '',  resave: true, saveUninitialized: true }));

我还设法在Express JS文档中找到了关于resavesaveUninitalized属性的这些注释:

<强> saveUninitialized

  

强制进行&#34;未初始化的会话&#34;被保存到商店。一个   会话在新的但未修改时未初始化。选择   false对于实现登录会话,减少服务器非常有用   存储使用,或遵守之前需要许可的法律   设置一个cookie。选择false也有助于竞争条件   客户端在没有会话的情况下发出多个并行请求。

     

默认值为true,但不推荐使用默认值,   因为默认将来会改变。请研究一下   设置并选择适合您的用例的内容。

     

请注意,如果您将Session与PassportJS结合使用,Passport会将一个空的Passport对象添加到会话中以供使用   用户通过身份验证后,将被视为修改   到会话,导致它被保存。这已经修复了   PassportJS 0.3.0

<强> resave

  

强制将会话保存回会话存储区,即使是   在请求期间从未修改过会话。取决于您的商店   这可能是必要的,但它也可以创造竞争条件   客户端向您的服务器发出两个并行请求并进行更改   一个请求中的会话可能会被另一个请求覆盖   请求结束,即使它没有变化(这种行为也取决于   你正在使用什么商店。

     

默认值为true,但不推荐使用默认值,   因为默认将来会改变。请研究一下   设置并选择适合您的用例的内容。通常情况下,   你会想要假的。

     

我怎么知道这对我的商店是否有必要?了解最好的方法   如果它实现了触摸方法,请检查您的商店。如果它   那么你可以安全地设置resave:false。如果它没有实现   触摸方法和您的商店设置存储的到期日期   会话,然后你可能需要resave:true。