2020-05-06 06:09:15 -04:00
|
|
|
/* eslint-disable
|
|
|
|
no-unused-vars,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-05-06 06:08:21 -04:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS101: Remove unnecessary use of Array.from
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS205: Consider reworking code to avoid use of IIFEs
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
2020-05-06 06:09:33 -04:00
|
|
|
const request = require('request')
|
2021-07-12 12:47:15 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2020-05-06 06:09:33 -04:00
|
|
|
const RedisManager = require('./RedisManager')
|
|
|
|
const { rclient } = RedisManager
|
|
|
|
const docUpdaterKeys = Settings.redis.documentupdater.key_schema
|
|
|
|
const async = require('async')
|
|
|
|
const ProjectManager = require('./ProjectManager')
|
|
|
|
const _ = require('lodash')
|
2021-10-06 05:10:28 -04:00
|
|
|
const logger = require('@overleaf/logger')
|
2024-01-23 05:45:06 -05:00
|
|
|
const { promisifyAll } = require('@overleaf/promise-utils')
|
2019-05-02 11:30:36 -04:00
|
|
|
|
2021-10-26 04:08:56 -04:00
|
|
|
const ProjectFlusher = {
|
2020-05-06 06:09:33 -04:00
|
|
|
// iterate over keys asynchronously using redis scan (non-blocking)
|
|
|
|
// handle all the cluster nodes or single redis server
|
|
|
|
_getKeys(pattern, limit, callback) {
|
|
|
|
const nodes = (typeof rclient.nodes === 'function'
|
|
|
|
? rclient.nodes('master')
|
|
|
|
: undefined) || [rclient]
|
|
|
|
const doKeyLookupForNode = (node, cb) =>
|
|
|
|
ProjectFlusher._getKeysFromNode(node, pattern, limit, cb)
|
|
|
|
return async.concatSeries(nodes, doKeyLookupForNode, callback)
|
|
|
|
},
|
2019-05-02 11:30:36 -04:00
|
|
|
|
2020-05-06 06:09:33 -04:00
|
|
|
_getKeysFromNode(node, pattern, limit, callback) {
|
|
|
|
if (limit == null) {
|
|
|
|
limit = 1000
|
|
|
|
}
|
|
|
|
let cursor = 0 // redis iterator
|
|
|
|
const keySet = {} // use hash to avoid duplicate results
|
|
|
|
const batchSize = limit != null ? Math.min(limit, 1000) : 1000
|
|
|
|
// scan over all keys looking for pattern
|
2021-10-26 04:08:56 -04:00
|
|
|
const doIteration = (
|
2020-05-06 06:09:33 -04:00
|
|
|
cb // avoid hitting redis too hard
|
|
|
|
) =>
|
2021-07-13 07:04:42 -04:00
|
|
|
node.scan(
|
|
|
|
cursor,
|
|
|
|
'MATCH',
|
|
|
|
pattern,
|
|
|
|
'COUNT',
|
|
|
|
batchSize,
|
|
|
|
function (error, reply) {
|
|
|
|
let keys
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
;[cursor, keys] = Array.from(reply)
|
|
|
|
for (const key of Array.from(keys)) {
|
|
|
|
keySet[key] = true
|
|
|
|
}
|
|
|
|
keys = Object.keys(keySet)
|
|
|
|
const noResults = cursor === '0' // redis returns string results not numeric
|
|
|
|
const limitReached = limit != null && keys.length >= limit
|
|
|
|
if (noResults || limitReached) {
|
|
|
|
return callback(null, keys)
|
|
|
|
} else {
|
|
|
|
return setTimeout(doIteration, 10)
|
|
|
|
}
|
2020-05-06 06:09:33 -04:00
|
|
|
}
|
2021-07-13 07:04:42 -04:00
|
|
|
)
|
2020-05-06 06:09:33 -04:00
|
|
|
return doIteration()
|
|
|
|
},
|
2019-05-02 11:30:36 -04:00
|
|
|
|
2020-05-06 06:09:33 -04:00
|
|
|
// extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
|
|
|
|
// or docsInProject:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
|
|
|
|
_extractIds(keyList) {
|
|
|
|
const ids = (() => {
|
|
|
|
const result = []
|
|
|
|
for (const key of Array.from(keyList)) {
|
|
|
|
const m = key.match(/:\{?([0-9a-f]{24})\}?/) // extract object id
|
|
|
|
result.push(m[1])
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
})()
|
|
|
|
return ids
|
|
|
|
},
|
2019-05-02 11:30:36 -04:00
|
|
|
|
2020-05-06 06:09:33 -04:00
|
|
|
flushAllProjects(options, callback) {
|
2021-09-30 04:28:32 -04:00
|
|
|
logger.info({ options }, 'flushing all projects')
|
2020-05-06 06:09:33 -04:00
|
|
|
return ProjectFlusher._getKeys(
|
|
|
|
docUpdaterKeys.docsInProject({ project_id: '*' }),
|
|
|
|
options.limit,
|
2023-03-21 08:06:13 -04:00
|
|
|
function (error, projectKeys) {
|
2020-05-06 06:09:33 -04:00
|
|
|
if (error != null) {
|
|
|
|
logger.err({ err: error }, 'error getting keys for flushing')
|
|
|
|
return callback(error)
|
|
|
|
}
|
2023-03-21 08:06:13 -04:00
|
|
|
const projectIds = ProjectFlusher._extractIds(projectKeys)
|
2020-05-06 06:09:33 -04:00
|
|
|
if (options.dryRun) {
|
2023-03-21 08:06:13 -04:00
|
|
|
return callback(null, projectIds)
|
2020-05-06 06:09:33 -04:00
|
|
|
}
|
2021-07-13 07:04:42 -04:00
|
|
|
const jobs = _.map(
|
2023-03-21 08:06:13 -04:00
|
|
|
projectIds,
|
|
|
|
projectId => cb =>
|
2021-07-13 07:04:42 -04:00
|
|
|
ProjectManager.flushAndDeleteProjectWithLocks(
|
2023-03-21 08:06:13 -04:00
|
|
|
projectId,
|
2021-07-13 07:04:42 -04:00
|
|
|
{ background: true },
|
|
|
|
cb
|
|
|
|
)
|
2020-05-06 06:09:33 -04:00
|
|
|
)
|
|
|
|
return async.parallelLimit(
|
|
|
|
async.reflectAll(jobs),
|
|
|
|
options.concurrency,
|
|
|
|
function (error, results) {
|
|
|
|
const success = []
|
|
|
|
const failure = []
|
|
|
|
_.each(results, function (result, i) {
|
|
|
|
if (result.error != null) {
|
2023-03-21 08:06:13 -04:00
|
|
|
return failure.push(projectIds[i])
|
2020-05-06 06:09:33 -04:00
|
|
|
} else {
|
2023-03-21 08:06:13 -04:00
|
|
|
return success.push(projectIds[i])
|
2020-05-06 06:09:33 -04:00
|
|
|
}
|
|
|
|
})
|
2021-09-30 04:28:32 -04:00
|
|
|
logger.info(
|
|
|
|
{ successCount: success.length, failureCount: failure.length },
|
|
|
|
'finished flushing all projects'
|
|
|
|
)
|
2020-05-06 06:09:33 -04:00
|
|
|
return callback(error, { success, failure })
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2021-07-13 07:04:42 -04:00
|
|
|
},
|
2020-05-06 06:09:33 -04:00
|
|
|
}
|
2019-05-02 11:30:36 -04:00
|
|
|
|
2020-05-06 06:09:33 -04:00
|
|
|
module.exports = ProjectFlusher
|
2024-01-23 05:45:06 -05:00
|
|
|
module.exports.promises = promisifyAll(ProjectFlusher)
|