remove_deleted_docs.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. const Settings = require('@overleaf/settings')
  2. const logger = require('@overleaf/logger')
  3. const rclient = require('@overleaf/redis-wrapper').createClient(
  4. Settings.redis.documentupdater
  5. )
  6. const keys = Settings.redis.documentupdater.key_schema
  7. const ProjectFlusher = require('../app/js/ProjectFlusher')
  8. const RedisManager = require('../app/js/RedisManager')
  9. const { mongoClient, db, ObjectId } = require('../app/js/mongodb')
  10. const summary = { totalDocs: 0, deletedDocs: 0, skippedDocs: 0 }
  11. async function removeDeletedDocs(dockeys, options) {
  12. const docIds = ProjectFlusher._extractIds(dockeys)
  13. for (const docId of docIds) {
  14. summary.totalDocs++
  15. const docCount = await db.docs.find({ _id: new ObjectId(docId) }).count()
  16. if (!docCount) {
  17. try {
  18. await removeDeletedDoc(docId, options)
  19. } catch (err) {
  20. logger.error({ docId, err }, 'error removing deleted doc')
  21. }
  22. }
  23. }
  24. }
  25. async function removeDeletedDoc(docId, options) {
  26. const projectId = await rclient.get(keys.projectKey({ doc_id: docId }))
  27. const {
  28. lines: docLines,
  29. version,
  30. ranges,
  31. pathname,
  32. projectHistoryId,
  33. unflushedTime,
  34. lastUpdatedAt,
  35. lastUpdatedBy,
  36. historyRangesSupport,
  37. resolvedCommentIds,
  38. } = await RedisManager.promises.getDoc(projectId, docId)
  39. const project = await db.projects.findOne({ _id: new ObjectId(projectId) })
  40. let status
  41. if (project) {
  42. const projectJSON = JSON.stringify(project.rootFolder)
  43. const containsDoc = projectJSON.indexOf(docId) !== -1
  44. if (containsDoc) {
  45. logger.warn(
  46. {
  47. projectId,
  48. docId,
  49. docLinesBytes: docLines && docLines.length,
  50. version,
  51. rangesBytes: ranges && ranges.length,
  52. pathname,
  53. projectHistoryId,
  54. unflushedTime,
  55. lastUpdatedAt,
  56. lastUpdatedBy,
  57. historyRangesSupport,
  58. resolvedCommentIds,
  59. },
  60. 'refusing to delete doc, project contains docId'
  61. )
  62. summary.skippedDocs++
  63. return
  64. } else {
  65. logger.warn(
  66. {
  67. projectId,
  68. docId,
  69. docLinesBytes: docLines && docLines.length,
  70. version,
  71. rangesBytes: ranges && ranges.length,
  72. pathname,
  73. projectHistoryId,
  74. unflushedTime,
  75. lastUpdatedAt,
  76. lastUpdatedBy,
  77. historyRangesSupport,
  78. resolvedCommentIds,
  79. },
  80. 'refusing to delete doc, project still exists'
  81. )
  82. summary.skippedDocs++
  83. return
  84. }
  85. } else {
  86. status = 'projectDeleted'
  87. }
  88. summary.deletedDocs++
  89. if (options.dryRun) {
  90. logger.info(
  91. {
  92. projectId,
  93. docId,
  94. docLinesBytes: docLines && docLines.length,
  95. version,
  96. rangesBytes: ranges && ranges.length,
  97. pathname,
  98. projectHistoryId,
  99. unflushedTime,
  100. lastUpdatedAt,
  101. lastUpdatedBy,
  102. historyRangesSupport,
  103. resolvedCommentIds,
  104. status,
  105. summary,
  106. },
  107. 'dry run mode - would remove doc from redis'
  108. )
  109. return
  110. }
  111. logger.info(
  112. {
  113. projectId,
  114. docId,
  115. docLinesBytes: docLines && docLines.length,
  116. version,
  117. rangesBytes: ranges && ranges.length,
  118. pathname,
  119. projectHistoryId,
  120. unflushedTime,
  121. lastUpdatedAt,
  122. lastUpdatedBy,
  123. historyRangesSupport,
  124. resolvedCommentIds,
  125. status,
  126. summary,
  127. },
  128. 'removing doc from redis'
  129. )
  130. await RedisManager.promises.removeDocFromMemory(projectId, docId)
  131. }
  132. async function findAndProcessDocs(options) {
  133. logger.info({ options }, 'removing deleted docs')
  134. let cursor = 0
  135. do {
  136. const [newCursor, doclinesKeys] = await rclient.scan(
  137. cursor,
  138. 'MATCH',
  139. keys.docLines({ doc_id: '*' }),
  140. 'COUNT',
  141. options.limit
  142. )
  143. await removeDeletedDocs(doclinesKeys, options)
  144. cursor = newCursor
  145. } while (cursor !== '0')
  146. }
  147. async function main() {
  148. const options = {
  149. limit: 1000,
  150. dryRun: process.env.DRY_RUN !== 'false',
  151. }
  152. if (process.argv.length > 2) {
  153. const docId = process.argv[process.argv.length - 1]
  154. if (!ObjectId.isValid(docId)) {
  155. throw new Error(
  156. 'bad docId: usage: $ node scripts/remove_deleted_docs.js [DOC_ID]'
  157. )
  158. }
  159. await removeDeletedDoc(docId, options)
  160. } else {
  161. await findAndProcessDocs(options)
  162. }
  163. }
  164. main()
  165. .then(() => {
  166. rclient.quit()
  167. mongoClient.close()
  168. console.log('DONE')
  169. process.exit(0)
  170. })
  171. .catch(function (error) {
  172. console.error(error)
  173. process.exit(1)
  174. })