DeleteQueueManager.coffee 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. Settings = require('settings-sharelatex')
  2. RedisManager = require "./RedisManager"
  3. ProjectManager = require "./ProjectManager"
  4. logger = require "logger-sharelatex"
  5. metrics = require "./Metrics"
  6. async = require "async"
  7. # Maintain a sorted set of project flushAndDelete requests, ordered by timestamp
  8. # (ZADD), and process them from oldest to newest. A flushAndDelete request comes
  9. # from real-time and is triggered when a user leaves a project.
  10. #
  11. # The aim is to remove the project from redis 5 minutes after the last request
  12. # if there has been no activity (document updates) in that time. If there is
  13. # activity we can expect a further flushAndDelete request when the editing user
  14. # leaves the project.
  15. #
  16. # If a new flushAndDelete request comes in while an existing request is already
  17. # in the queue we update the timestamp as we can postpone flushing further.
  18. #
  19. # Documents are processed by checking the queue, seeing if the first entry is
  20. # older than 5 minutes, and popping it from the queue in that case.
  21. module.exports = DeleteQueueManager =
  22. flushAndDeleteOldProjects: (options, callback) ->
  23. startTime = Date.now()
  24. cutoffTime = startTime - options.min_delete_age + 100 * (Math.random() - 0.5)
  25. count = 0
  26. flushProjectIfNotModified = (project_id, flushTimestamp, cb) ->
  27. ProjectManager.getProjectDocsTimestamps project_id, (err, timestamps) ->
  28. return callback(err) if err?
  29. if timestamps.length == 0
  30. logger.log {project_id}, "skipping flush of queued project - no timestamps"
  31. return cb()
  32. # are any of the timestamps newer than the time the project was flushed?
  33. for timestamp in timestamps when timestamp > flushTimestamp
  34. metrics.inc "queued-delete-skipped"
  35. logger.debug {project_id, timestamps, flushTimestamp}, "found newer timestamp, will skip delete"
  36. return cb()
  37. logger.log {project_id, flushTimestamp}, "flushing queued project"
  38. ProjectManager.flushAndDeleteProjectWithLocks project_id, {skip_history_flush: false}, (err) ->
  39. if err?
  40. logger.err {project_id, err}, "error flushing queued project"
  41. metrics.inc "queued-delete-completed"
  42. return cb(null, true)
  43. flushNextProject = () ->
  44. now = Date.now()
  45. if now - startTime > options.timeout
  46. logger.log "hit time limit on flushing old projects"
  47. return callback(null, count)
  48. if count > options.limit
  49. logger.log "hit count limit on flushing old projects"
  50. return callback(null, count)
  51. RedisManager.getNextProjectToFlushAndDelete cutoffTime, (err, project_id, flushTimestamp, queueLength) ->
  52. return callback(err) if err?
  53. return callback(null, count) if !project_id?
  54. logger.log {project_id, queueLength: queueLength}, "flushing queued project"
  55. metrics.globalGauge "queued-flush-backlog", queueLength
  56. flushProjectIfNotModified project_id, flushTimestamp, (err, flushed) ->
  57. count++ if flushed
  58. flushNextProject()
  59. flushNextProject()
  60. startBackgroundFlush: () ->
  61. SHORT_DELAY = 10
  62. LONG_DELAY = 1000
  63. doFlush = () ->
  64. if Settings.shuttingDown
  65. logger.warn "discontinuing background flush due to shutdown"
  66. return
  67. DeleteQueueManager.flushAndDeleteOldProjects {
  68. timeout:1000,
  69. min_delete_age:3*60*1000,
  70. limit:1000 # high value, to ensure we always flush enough projects
  71. }, (err, flushed) ->
  72. setTimeout doFlush, (if flushed > 10 then SHORT_DELAY else LONG_DELAY)
  73. doFlush()