clear_deleted_history.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env node
  2. // To run in dev:
  3. //
  4. // docker compose run --rm project-history scripts/clear_deleted.js
  5. //
  6. // In production:
  7. //
  8. // docker run --rm $(docker ps -lq) scripts/clear_deleted.js
  9. import async from 'async'
  10. import logger from '@overleaf/logger'
  11. import Settings from '@overleaf/settings'
  12. import redis from '@overleaf/redis-wrapper'
  13. import { db, ObjectId } from '../app/js/mongodb.js'
  14. logger.logger.level('fatal')
  15. const rclient = redis.createClient(Settings.redis.project_history)
  16. const Keys = Settings.redis.project_history.key_schema
  17. const argv = process.argv.slice(2)
  18. const limit = parseInt(argv[0], 10) || null
  19. const force = argv[1] === 'force' || false
  20. let projectNotFoundErrors = 0
  21. let projectImportedFromV1Errors = 0
  22. const projectsNotFound = []
  23. const projectsImportedFromV1 = []
  24. let projectWithHistoryIdErrors = 0
  25. const projectsWithHistoryId = []
  26. function checkAndClear(project, callback) {
  27. const projectId = project.project_id
  28. console.log('checking project', projectId)
  29. function checkDeleted(cb) {
  30. db.projects.findOne(
  31. { _id: new ObjectId(projectId) },
  32. { projection: { overleaf: true } },
  33. (err, result) => {
  34. console.log(
  35. '1. looking in mongo projects collection: err',
  36. err,
  37. 'result',
  38. JSON.stringify(result)
  39. )
  40. if (err) {
  41. return cb(err)
  42. }
  43. if (!result) {
  44. return cb(new Error('project not found in mongo'))
  45. }
  46. if (
  47. result &&
  48. result.overleaf &&
  49. !result.overleaf.id &&
  50. result.overleaf.history &&
  51. !result.overleaf.history.id &&
  52. result.overleaf.history.deleted_id
  53. ) {
  54. console.log(
  55. ' - project is not imported from v1 and has a deleted_id - ok to clear'
  56. )
  57. return cb()
  58. } else if (result && result.overleaf && result.overleaf.id) {
  59. console.log(' - project is imported from v1')
  60. return cb(
  61. new Error('project is imported from v1 - will not clear it')
  62. )
  63. } else if (
  64. result &&
  65. result.overleaf &&
  66. result.overleaf.history &&
  67. result.overleaf.history.id
  68. ) {
  69. console.log(' - project has a history id')
  70. return cb(new Error('project has a history id - will not clear it'))
  71. } else {
  72. console.log(' - project state not recognised')
  73. return cb(new Error('project state not recognised'))
  74. }
  75. }
  76. )
  77. }
  78. function clearRedisQueue(cb) {
  79. const key = Keys.projectHistoryOps({ project_id: projectId })
  80. if (force) {
  81. console.log('deleting redis key', key)
  82. rclient.del(key, err => {
  83. cb(err)
  84. })
  85. } else {
  86. console.log('dry run, would deleted key', key)
  87. cb()
  88. }
  89. }
  90. function clearMongoEntry(cb) {
  91. if (force) {
  92. console.log('deleting key in mongo projectHistoryFailures', projectId)
  93. db.projectHistoryFailures.deleteOne(
  94. { project_id: projectId },
  95. (err, result) => {
  96. console.log('got result from remove', err, result)
  97. cb(err)
  98. }
  99. )
  100. } else {
  101. console.log('would delete failure record for', projectId, 'from mongo')
  102. cb()
  103. }
  104. }
  105. // do the checks and deletions
  106. async.waterfall([checkDeleted, clearRedisQueue, clearMongoEntry], err => {
  107. if (!err) {
  108. if (force) {
  109. return setTimeout(callback, 100)
  110. } // include a 1 second delay
  111. return callback()
  112. } else if (err.message === 'project not found in mongo') {
  113. projectNotFoundErrors++
  114. projectsNotFound.push(projectId)
  115. return callback()
  116. } else if (err.message === 'project has a history id - will not clear it') {
  117. projectWithHistoryIdErrors++
  118. projectsWithHistoryId.push(projectId)
  119. return callback()
  120. } else if (
  121. err.message === 'project is imported from v1 - will not clear it'
  122. ) {
  123. projectImportedFromV1Errors++
  124. projectsImportedFromV1.push(projectId)
  125. return callback()
  126. } else {
  127. console.log('error:', err)
  128. return callback(err)
  129. }
  130. })
  131. }
  132. // find all the broken projects from the failure records
  133. async function main() {
  134. const results = await db.projectHistoryFailures
  135. .find({ error: /history store a non-success status code: 422/ })
  136. .toArray()
  137. console.log('number of queues without history store 442 =', results.length)
  138. // now check if the project is truly deleted in mongo
  139. async.eachSeries(results.slice(0, limit), checkAndClear, err => {
  140. console.log('Final error status', err)
  141. console.log(
  142. 'Project not found errors',
  143. projectNotFoundErrors,
  144. projectsNotFound
  145. )
  146. console.log(
  147. 'Project with history id errors',
  148. projectWithHistoryIdErrors,
  149. projectsWithHistoryId
  150. )
  151. console.log(
  152. 'Project imported from V1 errors',
  153. projectImportedFromV1Errors,
  154. projectsImportedFromV1
  155. )
  156. process.exit()
  157. })
  158. }
  159. main().catch(error => {
  160. console.error(error)
  161. process.exit(1)
  162. })