Added one-minute in-memory server caching for a bunch of common queries.

This commit is contained in:
Nick Winter 2015-02-26 17:20:27 -08:00
parent 9b34d4e166
commit b4e9ee67f0
8 changed files with 51 additions and 39 deletions

View file

@ -64,7 +64,7 @@ module.exports = class LadderView extends RootView
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions)) @insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions)) @insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
@insertSubView(@simulateTab = new SimulateTabView()) @insertSubView(@simulateTab = new SimulateTabView())
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 20 * 1000) @refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 60 * 1000)
hash = document.location.hash[1..] if document.location.hash hash = document.location.hash[1..] if document.location.hash
if hash and not (hash in ['my-matches', 'simulate', 'ladder', 'prizes', 'rules', 'winners']) if hash and not (hash in ['my-matches', 'simulate', 'ladder', 'prizes', 'rules', 'winners'])
@showPlayModal(hash) if @sessions.loaded @showPlayModal(hash) if @sessions.loaded

View file

@ -38,36 +38,37 @@
"multiplayer" "multiplayer"
], ],
"dependencies": { "dependencies": {
"express": "~3.0.6",
"winston": "0.6.x",
"passport": "0.1.x",
"passport-local": "0.1.x",
"moment": "~2.5.0",
"mongoose": "3.8.x",
"request": "2.12.x",
"tv4": "~1.0.16",
"lodash": "~2.4.1",
"underscore.string": "2.3.x",
"async": "0.2.x",
"connect": "2.7.x",
"coffee-script": "1.9.x",
"graceful-fs": "~2.0.1",
"node-force-domain": "~0.1.0",
"mailchimp-api": "2.0.x",
"express-useragent": "~0.0.9",
"gridfs-stream": "0.4.x",
"stream-buffers": "0.2.x",
"sendwithus": "2.1.x",
"aws-sdk": "~2.0.0",
"bayesian-battle": "0.0.x",
"redis": "",
"webworker-threads": "~0.5.5",
"node-gyp": "~0.13.0",
"aether": "~0.3.0",
"JASON": "~0.1.3", "JASON": "~0.1.3",
"JQDeferred": "~2.1.0", "JQDeferred": "~2.1.0",
"aether": "~0.3.0",
"async": "0.2.x",
"aws-sdk": "~2.0.0",
"bayesian-battle": "0.0.x",
"coffee-script": "1.9.x",
"connect": "2.7.x",
"express": "~3.0.6",
"express-useragent": "~0.0.9",
"graceful-fs": "~2.0.1",
"gridfs-stream": "0.4.x",
"jsondiffpatch": "0.1.17", "jsondiffpatch": "0.1.17",
"stripe": "~2.9.0" "lodash": "~2.4.1",
"mailchimp-api": "2.0.x",
"moment": "~2.5.0",
"mongoose": "3.8.x",
"mongoose-cache": "https://github.com/nwinter/mongoose-cache/tarball/master",
"node-force-domain": "~0.1.0",
"node-gyp": "~0.13.0",
"passport": "0.1.x",
"passport-local": "0.1.x",
"redis": "",
"request": "2.12.x",
"sendwithus": "2.1.x",
"stream-buffers": "0.2.x",
"stripe": "~2.9.0",
"tv4": "~1.0.16",
"underscore.string": "2.3.x",
"webworker-threads": "~0.5.5",
"winston": "0.6.x"
}, },
"devDependencies": { "devDependencies": {
"auto-reload-brunch": "> 1.0 < 1.8", "auto-reload-brunch": "> 1.0 < 1.8",

View file

@ -2,6 +2,7 @@ config = require '../../server_config'
winston = require 'winston' winston = require 'winston'
mongoose = require 'mongoose' mongoose = require 'mongoose'
Grid = require 'gridfs-stream' Grid = require 'gridfs-stream'
mongooseCache = require 'mongoose-cache'
global.testing = testing = '--unittest' in process.argv global.testing = testing = '--unittest' in process.argv
@ -13,6 +14,7 @@ module.exports.connect = () ->
mongoose.connect address mongoose.connect address
mongoose.connection.once 'open', -> Grid.gfs = Grid(mongoose.connection.db, mongoose.mongo) mongoose.connection.once 'open', -> Grid.gfs = Grid(mongoose.connection.db, mongoose.mongo)
mongooseCache.install(mongoose, {max: 200, maxAge: 1 * 60 * 1000, debug: false})
module.exports.generateMongoConnectionString = -> module.exports.generateMongoConnectionString = ->
if not testing and config.mongo.mongoose_replica_string if not testing and config.mongo.mongoose_replica_string

View file

@ -164,6 +164,7 @@ LevelHandler = class LevelHandler extends Handler
{$match: {'levelID': slug, 'submitted': true, 'team': req.query.team}} {$match: {'levelID': slug, 'submitted': true, 'team': req.query.team}}
{$project: {totalScore: 1, _id: 0}} {$project: {totalScore: 1, _id: 0}}
] ]
#query.cache() # TODO: implement caching for aggregates
query.exec (err, data) => query.exec (err, data) =>
if err? then return @sendDatabaseError res, err if err? then return @sendDatabaseError res, err
@ -199,6 +200,7 @@ LevelHandler = class LevelHandler extends Handler
.limit(req.query.limit) .limit(req.query.limit)
.sort(sortParameters) .sort(sortParameters)
.select(selectProperties.join ' ') .select(selectProperties.join ' ')
query.cache() if sessionsQueryParameters.totalScore.$lt is 1000000
query.exec (err, resultSessions) => query.exec (err, resultSessions) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@ -269,6 +271,7 @@ LevelHandler = class LevelHandler extends Handler
query = Level.findOne(findParameters) query = Level.findOne(findParameters)
.select(selectString) .select(selectString)
.lean() .lean()
.cache()
query.exec (err, level) => query.exec (err, level) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@ -280,17 +283,19 @@ LevelHandler = class LevelHandler extends Handler
majorVersion: level.version.major majorVersion: level.version.major
submitted: true submitted: true
query = Session.find(sessionsQueryParameters).distinct('team') query = Session.find(sessionsQueryParameters).distinct('team').cache()
query.exec (err, teams) => query.exec (err, teams) =>
return @sendDatabaseError res, err if err? or not teams return @sendDatabaseError res, err if err? or not teams
findTop20Players = (sessionQueryParams, team, cb) -> findTop20Players = (sessionQueryParams, team, cb) ->
sessionQueryParams['team'] = team sessionQueryParams['team'] = team
Session.aggregate [ aggregate = Session.aggregate [
{$match: sessionQueryParams} {$match: sessionQueryParams}
{$project: {'totalScore': 1}} {$project: {'totalScore': 1}}
{$sort: {'totalScore': -1}} {$sort: {'totalScore': -1}}
{$limit: 20} {$limit: 20}
], cb ]
#aggregate.cache() # TODO: implement caching for aggregates
aggregate.exec cb
async.map teams, findTop20Players.bind(@, sessionsQueryParameters), (err, map) => async.map teams, findTop20Players.bind(@, sessionsQueryParameters), (err, map) =>
if err? then return @sendDatabaseError(res, err) if err? then return @sendDatabaseError(res, err)

View file

@ -70,6 +70,8 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
if limit? and limit < 1000 if limit? and limit < 1000
q.limit(limit) q.limit(limit)
q.cache()
q.exec (err, documents) => q.exec (err, documents) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
documents = (@formatEntity(req, doc) for doc in documents) documents = (@formatEntity(req, doc) for doc in documents)

View file

@ -138,7 +138,7 @@ module.exports.getTwoGames = (req, res) ->
'submitted': true 'submitted': true
'team': 'humans' 'team': 'humans'
selection = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate' selection = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate'
LevelSession.count queryParams, (err, numberOfHumans) => LevelSession.count(queryParams).cache().exec (err, numberOfHumans) =>
if err? then return errors.serverError(res, 'Couldn\'t get the number of human games') if err? then return errors.serverError(res, 'Couldn\'t get the number of human games')
unless numberOfHumans unless numberOfHumans
res.send(204, 'No games to score.') res.send(204, 'No games to score.')
@ -149,7 +149,7 @@ module.exports.getTwoGames = (req, res) ->
'levelID': levelID 'levelID': levelID
'submitted': true 'submitted': true
'team': 'ogres' 'team': 'ogres'
LevelSession.count ogreCountParams, (err, numberOfOgres) => LevelSession.count(ogreCountParams).cache().exec (err, numberOfOgres) =>
if err? then return errors.serverError(res, 'Couldn\'t get the number of ogre games') if err? then return errors.serverError(res, 'Couldn\'t get the number of ogre games')
unless numberOfOgres unless numberOfOgres
res.send(204, 'No games to score.') res.send(204, 'No games to score.')

View file

@ -172,11 +172,12 @@ UserHandler = class UserHandler extends Handler
getSimulatorLeaderboard: (req, res) -> getSimulatorLeaderboard: (req, res) ->
queryParameters = @getSimulatorLeaderboardQueryParameters(req) queryParameters = @getSimulatorLeaderboardQueryParameters(req)
leaderboardQuery = User.find(queryParameters.query).select('name simulatedBy simulatedFor').sort({'simulatedBy': queryParameters.sortOrder}).limit(queryParameters.limit) leaderboardQuery = User.find(queryParameters.query).select('name simulatedBy simulatedFor').sort({'simulatedBy': queryParameters.sortOrder}).limit(queryParameters.limit)
leaderboardQuery.cache() if req.query.scoreOffset is -1
leaderboardQuery.exec (err, otherUsers) -> leaderboardQuery.exec (err, otherUsers) ->
otherUsers = _.reject otherUsers, _id: req.user._id if req.query.scoreOffset isnt -1 otherUsers = _.reject otherUsers, _id: req.user._id if req.query.scoreOffset isnt -1
otherUsers ?= [] otherUsers ?= []
res.send(otherUsers) res.send(otherUsers)
res.end() res.end()
getMySimulatorLeaderboardRank: (req, res) -> getMySimulatorLeaderboardRank: (req, res) ->
req.query.order = 1 req.query.order = 1
@ -229,12 +230,12 @@ UserHandler = class UserHandler extends Handler
for prop, val of obj for prop, val of obj
user.set(prop, undefined) unless prop is '_id' user.set(prop, undefined) unless prop is '_id'
user.set('deleted', true) user.set('deleted', true)
# Hack to get saving of Users to work. Probably should replace these props with strings # Hack to get saving of Users to work. Probably should replace these props with strings
# so that validation doesn't get hung up on Date objects in the documents. # so that validation doesn't get hung up on Date objects in the documents.
delete obj.dateCreated delete obj.dateCreated
user.save (err) => user.save (err) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendNoContent res @sendNoContent res

View file

@ -53,6 +53,7 @@ setupErrorMiddleware = (app) ->
hipchat.sendHipChatMessage(message, ['tower'], {papertrail: true}) hipchat.sendHipChatMessage(message, ['tower'], {papertrail: true})
else else
next(err) next(err)
setupExpressMiddleware = (app) -> setupExpressMiddleware = (app) ->
if config.isProduction if config.isProduction
express.logger.format('prod', productionLogging) express.logger.format('prod', productionLogging)