clear_deleted.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. #!/usr/bin/env node
  2. import async from 'async'
  3. import logger from '@overleaf/logger'
  4. import Settings from '@overleaf/settings'
  5. import redis from '@overleaf/redis-wrapper'
  6. import { db, ObjectId } from '../app/js/mongodb.js'
  7. logger.logger.level('fatal')
  8. const rclient = redis.createClient(Settings.redis.project_history)
  9. const Keys = Settings.redis.project_history.key_schema
  10. const argv = process.argv.slice(2)
  11. const limit = parseInt(argv[0], 10) || null
  12. const force = argv[1] === 'force' || false
  13. let delay = 0
  14. function checkAndClear(project, callback) {
  15. const projectId = project.project_id
  16. function checkDeleted(cb) {
  17. db.projects.findOne(
  18. { _id: ObjectId(projectId) },
  19. { projection: { _id: 1 } },
  20. (err, result) => {
  21. if (err) {
  22. cb(err)
  23. } else if (!result) {
  24. // project not found, but we still need to look at deletedProjects
  25. cb()
  26. } else {
  27. console.log(`Project ${projectId} found in projects`)
  28. cb(new Error('error: project still exists'))
  29. }
  30. }
  31. )
  32. }
  33. function checkRecoverable(cb) {
  34. db.deletedProjects.findOne(
  35. {
  36. // this condition makes use of the index
  37. 'deleterData.deletedProjectId': ObjectId(projectId),
  38. // this condition checks if the deleted project has expired
  39. 'project._id': ObjectId(projectId),
  40. },
  41. { projection: { _id: 1 } },
  42. (err, result) => {
  43. if (err) {
  44. cb(err)
  45. } else if (!result) {
  46. console.log(
  47. `project ${projectId} has been deleted - safe to clear queue`
  48. )
  49. cb()
  50. } else {
  51. console.log(`Project ${projectId} found in deletedProjects`)
  52. cb(new Error('error: project still exists'))
  53. }
  54. }
  55. )
  56. }
  57. function clearRedisQueue(cb) {
  58. const key = Keys.projectHistoryOps({ project_id: projectId })
  59. delay++
  60. if (force) {
  61. console.log('setting redis key', key, 'to expire in', delay, 'seconds')
  62. // use expire to allow redis to delete the key in the background
  63. rclient.expire(key, delay, err => {
  64. cb(err)
  65. })
  66. } else {
  67. console.log(
  68. 'dry run, would set key',
  69. key,
  70. 'to expire in',
  71. delay,
  72. 'seconds'
  73. )
  74. cb()
  75. }
  76. }
  77. function clearMongoEntry(cb) {
  78. if (force) {
  79. console.log('deleting key in mongo projectHistoryFailures', projectId)
  80. db.projectHistoryFailures.deleteOne({ project_id: projectId }, cb)
  81. } else {
  82. console.log('would delete failure record for', projectId, 'from mongo')
  83. cb()
  84. }
  85. }
  86. // do the checks and deletions
  87. async.waterfall(
  88. [checkDeleted, checkRecoverable, clearRedisQueue, clearMongoEntry],
  89. err => {
  90. if (!err || err.message === 'error: project still exists') {
  91. callback()
  92. } else {
  93. console.log('error:', err)
  94. callback(err)
  95. }
  96. }
  97. )
  98. }
  99. // find all the broken projects from the failure records
  100. async function main() {
  101. const results = await db.projectHistoryFailures.find({}).toArray()
  102. processFailures(results)
  103. }
  104. main().catch(error => {
  105. console.error(error)
  106. process.exit(1)
  107. })
  108. function processFailures(results) {
  109. if (argv.length === 0) {
  110. console.log(`
  111. Usage: node clear_deleted.js [QUEUES] [FORCE]
  112. where
  113. QUEUES is the number of queues to process
  114. FORCE is the string "force" when we're ready to delete the queues. Without it, this script does a dry-run
  115. `)
  116. }
  117. console.log('number of stuck projects', results.length)
  118. // now check if the project is truly deleted in mongo
  119. async.eachSeries(results.slice(0, limit), checkAndClear, err => {
  120. console.log('DONE', err)
  121. process.exit()
  122. })
  123. }