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

前端之家收集整理的这篇文章主要介绍了javascript – MongoDB使用未知的findOne方法导致大延迟 – New Relic前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我设置newrelic以更好地了解我的应用程序的瓶颈,我发现了一个我似乎无法弄清楚的问题.

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

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

获取/所有/提案的代码

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 / proposal上运行MongoDB上的User.findOne调用.最初我认为是isLoggedIn中间件,我检查用户是否在会话中(Passport.js)但是你可以看到isLoggedIn Middleware只需要0.0222(ms).

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

谁能帮我解决这个问题.如果您需要更多详细信息,请告诉我.

更新:
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
    },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
        },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,it would just be called 'local'

passport.use('local-login',new LocalStrategy({
    // by default,we will override with email
    usernameField : 'email',passwordField : 'password',passReqToCallback : true // allows us to pass back the entire request to the callback
},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 },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,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,'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata

        if(user.accountVerified == 'false')    
            return done(null,'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',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 / proposal路由

// =======================
//  GET All Proposals =====
//  =======================
app.get('/all/proposals',proposal){
       if(err){
           console.log(err);
       }else{
           console.log("All Proposals " + proposal);
           res.json(proposal);
       }
   });
});

解决方法

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

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

/*
 * 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 },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
...

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

要尝试优化LOCAL LOGIN findOne查询,您可以尝试在电子邮件字段中为users集合添加索引(如果您还没有):

// 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: '',saveUninitialized: false }));

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

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

saveUninitialized

Forces a session that is “uninitialized” to be saved to the store. A
session is uninitialized when it is new but not modified. Choosing
false is useful for implementing login sessions,reducing server
storage usage,or complying with laws that require permission before
setting a cookie. Choosing false will also help with race conditions
where a client makes multiple parallel requests without a session.

The default value is true,but using the default has been deprecated,
as the default will change in the future. Please research into this
setting and choose what is appropriate to your use-case.

Note if you are using Session in conjunction with PassportJS,Passport will add an empty Passport object to the session for use
after a user is authenticated,which will be treated as a modification
to the session,causing it to be saved. This has been fixed in
PassportJS 0.3.0

resave

Forces the session to be saved back to the session store,even if the
session was never modified during the request. Depending on your store
this may be necessary,but it can also create race conditions where
a client makes two parallel requests to your server and changes made
to the session in one request may get overwritten when the other
request ends,even if it made no changes
(this behavior also depends
on what store you’re using).

The default value is true,
as the default will change in the future. Please research into this
setting and choose what is appropriate to your use-case. Typically,
you’ll want false.

How do I know if this is necessary for my store? The best way to know is to check with your store if it implements the touch method. If it does,then you can safely set resave: false. If it does not implement the touch method and your store sets an expiration date on stored sessions,then you likely need resave: true.

猜你在找的JavaScript相关文章