DeleteQueueManager.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let DeleteQueueManager
  16. const Settings = require('@overleaf/settings')
  17. const RedisManager = require('./RedisManager')
  18. const ProjectManager = require('./ProjectManager')
  19. const logger = require('logger-sharelatex')
  20. const metrics = require('./Metrics')
  21. const async = require('async')
  22. // Maintain a sorted set of project flushAndDelete requests, ordered by timestamp
  23. // (ZADD), and process them from oldest to newest. A flushAndDelete request comes
  24. // from real-time and is triggered when a user leaves a project.
  25. //
  26. // The aim is to remove the project from redis 5 minutes after the last request
  27. // if there has been no activity (document updates) in that time. If there is
  28. // activity we can expect a further flushAndDelete request when the editing user
  29. // leaves the project.
  30. //
  31. // If a new flushAndDelete request comes in while an existing request is already
  32. // in the queue we update the timestamp as we can postpone flushing further.
  33. //
  34. // Documents are processed by checking the queue, seeing if the first entry is
  35. // older than 5 minutes, and popping it from the queue in that case.
  36. module.exports = DeleteQueueManager = {
  37. flushAndDeleteOldProjects(options, callback) {
  38. const startTime = Date.now()
  39. const cutoffTime =
  40. startTime - options.min_delete_age + 100 * (Math.random() - 0.5)
  41. let count = 0
  42. const flushProjectIfNotModified = (project_id, flushTimestamp, cb) =>
  43. ProjectManager.getProjectDocsTimestamps(project_id, function (
  44. err,
  45. timestamps
  46. ) {
  47. if (err != null) {
  48. return callback(err)
  49. }
  50. if (timestamps.length === 0) {
  51. logger.log(
  52. { project_id },
  53. 'skipping flush of queued project - no timestamps'
  54. )
  55. return cb()
  56. }
  57. // are any of the timestamps newer than the time the project was flushed?
  58. for (const timestamp of Array.from(timestamps)) {
  59. if (timestamp > flushTimestamp) {
  60. metrics.inc('queued-delete-skipped')
  61. logger.debug(
  62. { project_id, timestamps, flushTimestamp },
  63. 'found newer timestamp, will skip delete'
  64. )
  65. return cb()
  66. }
  67. }
  68. logger.log({ project_id, flushTimestamp }, 'flushing queued project')
  69. return ProjectManager.flushAndDeleteProjectWithLocks(
  70. project_id,
  71. { skip_history_flush: false },
  72. function (err) {
  73. if (err != null) {
  74. logger.err({ project_id, err }, 'error flushing queued project')
  75. }
  76. metrics.inc('queued-delete-completed')
  77. return cb(null, true)
  78. }
  79. )
  80. })
  81. var flushNextProject = function () {
  82. const now = Date.now()
  83. if (now - startTime > options.timeout) {
  84. logger.log('hit time limit on flushing old projects')
  85. return callback(null, count)
  86. }
  87. if (count > options.limit) {
  88. logger.log('hit count limit on flushing old projects')
  89. return callback(null, count)
  90. }
  91. return RedisManager.getNextProjectToFlushAndDelete(cutoffTime, function (
  92. err,
  93. project_id,
  94. flushTimestamp,
  95. queueLength
  96. ) {
  97. if (err != null) {
  98. return callback(err)
  99. }
  100. if (project_id == null) {
  101. return callback(null, count)
  102. }
  103. logger.log({ project_id, queueLength }, 'flushing queued project')
  104. metrics.globalGauge('queued-flush-backlog', queueLength)
  105. return flushProjectIfNotModified(project_id, flushTimestamp, function (
  106. err,
  107. flushed
  108. ) {
  109. if (flushed) {
  110. count++
  111. }
  112. return flushNextProject()
  113. })
  114. })
  115. }
  116. return flushNextProject()
  117. },
  118. startBackgroundFlush() {
  119. const SHORT_DELAY = 10
  120. const LONG_DELAY = 1000
  121. var doFlush = function () {
  122. if (Settings.shuttingDown) {
  123. logger.warn('discontinuing background flush due to shutdown')
  124. return
  125. }
  126. return DeleteQueueManager.flushAndDeleteOldProjects(
  127. {
  128. timeout: 1000,
  129. min_delete_age: 3 * 60 * 1000,
  130. limit: 1000 // high value, to ensure we always flush enough projects
  131. },
  132. (err, flushed) =>
  133. setTimeout(doFlush, flushed > 10 ? SHORT_DELAY : LONG_DELAY)
  134. )
  135. }
  136. return doFlush()
  137. }
  138. }