ProjectFlusher.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /* eslint-disable
  2. no-unused-vars,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS205: Consider reworking code to avoid use of IIFEs
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const request = require('request')
  15. const Settings = require('@overleaf/settings')
  16. const RedisManager = require('./RedisManager')
  17. const { rclient } = RedisManager
  18. const docUpdaterKeys = Settings.redis.documentupdater.key_schema
  19. const async = require('async')
  20. const ProjectManager = require('./ProjectManager')
  21. const _ = require('lodash')
  22. const logger = require('@overleaf/logger')
  23. const ProjectFlusher = {
  24. // iterate over keys asynchronously using redis scan (non-blocking)
  25. // handle all the cluster nodes or single redis server
  26. _getKeys(pattern, limit, callback) {
  27. const nodes = (typeof rclient.nodes === 'function'
  28. ? rclient.nodes('master')
  29. : undefined) || [rclient]
  30. const doKeyLookupForNode = (node, cb) =>
  31. ProjectFlusher._getKeysFromNode(node, pattern, limit, cb)
  32. return async.concatSeries(nodes, doKeyLookupForNode, callback)
  33. },
  34. _getKeysFromNode(node, pattern, limit, callback) {
  35. if (limit == null) {
  36. limit = 1000
  37. }
  38. let cursor = 0 // redis iterator
  39. const keySet = {} // use hash to avoid duplicate results
  40. const batchSize = limit != null ? Math.min(limit, 1000) : 1000
  41. // scan over all keys looking for pattern
  42. const doIteration = (
  43. cb // avoid hitting redis too hard
  44. ) =>
  45. node.scan(
  46. cursor,
  47. 'MATCH',
  48. pattern,
  49. 'COUNT',
  50. batchSize,
  51. function (error, reply) {
  52. let keys
  53. if (error != null) {
  54. return callback(error)
  55. }
  56. ;[cursor, keys] = Array.from(reply)
  57. for (const key of Array.from(keys)) {
  58. keySet[key] = true
  59. }
  60. keys = Object.keys(keySet)
  61. const noResults = cursor === '0' // redis returns string results not numeric
  62. const limitReached = limit != null && keys.length >= limit
  63. if (noResults || limitReached) {
  64. return callback(null, keys)
  65. } else {
  66. return setTimeout(doIteration, 10)
  67. }
  68. }
  69. )
  70. return doIteration()
  71. },
  72. // extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
  73. // or docsInProject:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
  74. _extractIds(keyList) {
  75. const ids = (() => {
  76. const result = []
  77. for (const key of Array.from(keyList)) {
  78. const m = key.match(/:\{?([0-9a-f]{24})\}?/) // extract object id
  79. result.push(m[1])
  80. }
  81. return result
  82. })()
  83. return ids
  84. },
  85. flushAllProjects(options, callback) {
  86. logger.info({ options }, 'flushing all projects')
  87. return ProjectFlusher._getKeys(
  88. docUpdaterKeys.docsInProject({ project_id: '*' }),
  89. options.limit,
  90. function (error, projectKeys) {
  91. if (error != null) {
  92. logger.err({ err: error }, 'error getting keys for flushing')
  93. return callback(error)
  94. }
  95. const projectIds = ProjectFlusher._extractIds(projectKeys)
  96. if (options.dryRun) {
  97. return callback(null, projectIds)
  98. }
  99. const jobs = _.map(
  100. projectIds,
  101. projectId => cb =>
  102. ProjectManager.flushAndDeleteProjectWithLocks(
  103. projectId,
  104. { background: true },
  105. cb
  106. )
  107. )
  108. return async.parallelLimit(
  109. async.reflectAll(jobs),
  110. options.concurrency,
  111. function (error, results) {
  112. const success = []
  113. const failure = []
  114. _.each(results, function (result, i) {
  115. if (result.error != null) {
  116. return failure.push(projectIds[i])
  117. } else {
  118. return success.push(projectIds[i])
  119. }
  120. })
  121. logger.info(
  122. { successCount: success.length, failureCount: failure.length },
  123. 'finished flushing all projects'
  124. )
  125. return callback(error, { success, failure })
  126. }
  127. )
  128. }
  129. )
  130. },
  131. }
  132. module.exports = ProjectFlusher