mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #4650 from overleaf/jpa-in-memory-last-access-time
[perf] store the last access time of a project in memory GitOrigin-RevId: 16e2bee28f58eced18f4c3ec5571ea9d10805cbb
This commit is contained in:
parent
02918e7483
commit
44d0a8d162
2 changed files with 57 additions and 55 deletions
|
@ -5,7 +5,6 @@
|
||||||
* DS207: Consider shorter variations of null checks
|
* DS207: Consider shorter variations of null checks
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
const tenMinutes = 10 * 60 * 1000
|
|
||||||
const Metrics = require('@overleaf/metrics')
|
const Metrics = require('@overleaf/metrics')
|
||||||
Metrics.initialize('clsi')
|
Metrics.initialize('clsi')
|
||||||
|
|
||||||
|
@ -32,6 +31,7 @@ const OutputCacheManager = require('./app/js/OutputCacheManager')
|
||||||
const ContentCacheManager = require('./app/js/ContentCacheManager')
|
const ContentCacheManager = require('./app/js/ContentCacheManager')
|
||||||
|
|
||||||
require('./app/js/db').sync()
|
require('./app/js/db').sync()
|
||||||
|
ProjectPersistenceManager.init()
|
||||||
|
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const bodyParser = require('body-parser')
|
const bodyParser = require('body-parser')
|
||||||
|
@ -420,12 +420,6 @@ if (!module.parent) {
|
||||||
|
|
||||||
module.exports = app
|
module.exports = app
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
ProjectPersistenceManager.refreshExpiryTimeout(() => {
|
|
||||||
ProjectPersistenceManager.clearExpiredProjects()
|
|
||||||
})
|
|
||||||
}, tenMinutes)
|
|
||||||
|
|
||||||
function __guard__(value, transform) {
|
function __guard__(value, transform) {
|
||||||
return typeof value !== 'undefined' && value !== null
|
return typeof value !== 'undefined' && value !== null
|
||||||
? transform(value)
|
? transform(value)
|
||||||
|
|
|
@ -12,17 +12,19 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
let ProjectPersistenceManager
|
let ProjectPersistenceManager
|
||||||
const Metrics = require('./Metrics')
|
|
||||||
const UrlCache = require('./UrlCache')
|
const UrlCache = require('./UrlCache')
|
||||||
const CompileManager = require('./CompileManager')
|
const CompileManager = require('./CompileManager')
|
||||||
const db = require('./db')
|
|
||||||
const dbQueue = require('./DbQueue')
|
|
||||||
const async = require('async')
|
const async = require('async')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
const oneDay = 24 * 60 * 60 * 1000
|
const oneDay = 24 * 60 * 60 * 1000
|
||||||
const Settings = require('@overleaf/settings')
|
const Settings = require('@overleaf/settings')
|
||||||
const diskusage = require('diskusage')
|
const diskusage = require('diskusage')
|
||||||
const { callbackify } = require('util')
|
const { callbackify } = require('util')
|
||||||
|
const Path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
// projectId -> timestamp mapping.
|
||||||
|
const LAST_ACCESS = new Map()
|
||||||
|
|
||||||
async function refreshExpiryTimeout() {
|
async function refreshExpiryTimeout() {
|
||||||
const paths = [
|
const paths = [
|
||||||
|
@ -61,26 +63,50 @@ module.exports = ProjectPersistenceManager = {
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshExpiryTimeout: callbackify(refreshExpiryTimeout),
|
refreshExpiryTimeout: callbackify(refreshExpiryTimeout),
|
||||||
markProjectAsJustAccessed(project_id, callback) {
|
|
||||||
if (callback == null) {
|
init() {
|
||||||
callback = function (error) {}
|
fs.readdir(Settings.path.compilesDir, (err, dirs) => {
|
||||||
}
|
if (err) {
|
||||||
const timer = new Metrics.Timer('db-bump-last-accessed')
|
logger.warn({ err }, 'cannot get project listing')
|
||||||
const job = cb =>
|
dirs = []
|
||||||
db.Project.findOrCreate({ where: { project_id } })
|
}
|
||||||
.spread((project, created) =>
|
|
||||||
project
|
async.eachLimit(
|
||||||
.update({ lastAccessed: new Date() })
|
dirs,
|
||||||
.then(() => cb())
|
10,
|
||||||
.error(cb)
|
(projectAndUserId, cb) => {
|
||||||
)
|
const compileDir = Path.join(
|
||||||
.error(cb)
|
Settings.path.compilesDir,
|
||||||
dbQueue.queue.push(job, error => {
|
projectAndUserId
|
||||||
timer.done()
|
)
|
||||||
callback(error)
|
const projectId = projectAndUserId.slice(0, 24)
|
||||||
|
fs.stat(compileDir, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
// Schedule for immediate cleanup
|
||||||
|
LAST_ACCESS.set(projectId, 0)
|
||||||
|
} else {
|
||||||
|
// Cleanup eventually.
|
||||||
|
LAST_ACCESS.set(projectId, stats.mtime.getTime())
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
setInterval(() => {
|
||||||
|
ProjectPersistenceManager.refreshExpiryTimeout(() => {
|
||||||
|
ProjectPersistenceManager.clearExpiredProjects()
|
||||||
|
})
|
||||||
|
}, 10 * 60 * 1000)
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
markProjectAsJustAccessed(project_id, callback) {
|
||||||
|
LAST_ACCESS.set(project_id, Date.now())
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
|
||||||
clearExpiredProjects(callback) {
|
clearExpiredProjects(callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
callback = function (error) {}
|
callback = function (error) {}
|
||||||
|
@ -166,38 +192,20 @@ module.exports = ProjectPersistenceManager = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_clearProjectFromDatabase(project_id, callback) {
|
_clearProjectFromDatabase(project_id, callback) {
|
||||||
if (callback == null) {
|
LAST_ACCESS.delete(project_id)
|
||||||
callback = function (error) {}
|
callback()
|
||||||
}
|
|
||||||
logger.log({ project_id }, 'clearing project from database')
|
|
||||||
const job = cb =>
|
|
||||||
db.Project.destroy({ where: { project_id } })
|
|
||||||
.then(() => cb())
|
|
||||||
.error(cb)
|
|
||||||
return dbQueue.queue.push(job, callback)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_findExpiredProjectIds(callback) {
|
_findExpiredProjectIds(callback) {
|
||||||
if (callback == null) {
|
const expiredFrom = Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT
|
||||||
callback = function (error, project_ids) {}
|
const expiredProjectsIds = []
|
||||||
|
for (const [projectId, lastAccess] of LAST_ACCESS.entries()) {
|
||||||
|
if (lastAccess < expiredFrom) {
|
||||||
|
expiredProjectsIds.push(projectId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const job = function (cb) {
|
// ^ may be a fairly busy loop, continue detached.
|
||||||
const keepProjectsFrom = new Date(
|
setTimeout(() => callback(null, expiredProjectsIds), 0)
|
||||||
Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT
|
|
||||||
)
|
|
||||||
const q = {}
|
|
||||||
q[db.op.lt] = keepProjectsFrom
|
|
||||||
return db.Project.findAll({ where: { lastAccessed: q } })
|
|
||||||
.then(projects =>
|
|
||||||
cb(
|
|
||||||
null,
|
|
||||||
projects.map(project => project.project_id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.error(cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbQueue.queue.push(job, callback)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue