我设置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/proposals
的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
},
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);
}
});
});
答案 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文档中找到了关于resave
和saveUninitalized
属性的这些注释:
<强> saveUninitialized 强>
强制进行&#34;未初始化的会话&#34;被保存到商店。一个 会话在新的但未修改时未初始化。选择 false对于实现登录会话,减少服务器非常有用 存储使用,或遵守之前需要许可的法律 设置一个cookie。选择false也有助于竞争条件 客户端在没有会话的情况下发出多个并行请求。
默认值为true,但不推荐使用默认值, 因为默认将来会改变。请研究一下 设置并选择适合您的用例的内容。
请注意,如果您将Session与PassportJS结合使用,Passport会将一个空的Passport对象添加到会话中以供使用 用户通过身份验证后,将被视为修改 到会话,导致它被保存。这已经修复了 PassportJS 0.3.0
<强> resave 强>
强制将会话保存回会话存储区,即使是 在请求期间从未修改过会话。取决于您的商店 这可能是必要的,但它也可以创造竞争条件 客户端向您的服务器发出两个并行请求并进行更改 一个请求中的会话可能会被另一个请求覆盖 请求结束,即使它没有变化(这种行为也取决于 你正在使用什么商店。
默认值为true,但不推荐使用默认值, 因为默认将来会改变。请研究一下 设置并选择适合您的用例的内容。通常情况下, 你会想要假的。
我怎么知道这对我的商店是否有必要?了解最好的方法 如果它实现了触摸方法,请检查您的商店。如果它 那么你可以安全地设置resave:false。如果它没有实现 触摸方法和您的商店设置存储的到期日期 会话,然后你可能需要resave:true。