remove_deleted_docs.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 util = require('util')
  11. const getDoc = util.promisify((projectId, docId, cb) =>
  12. RedisManager.getDoc(projectId, docId, (err, ...args) => cb(err, args))
  13. )
  14. const removeDocFromMemory = util.promisify(RedisManager.removeDocFromMemory)
  15. const summary = { totalDocs: 0, deletedDocs: 0, skippedDocs: 0 }
  16. async function removeDeletedDocs(dockeys, options) {
  17. const docIds = ProjectFlusher._extractIds(dockeys)
  18. for (const docId of docIds) {
  19. summary.totalDocs++
  20. const docCount = await db.docs.find({ _id: ObjectId(docId) }).count()
  21. if (!docCount) {
  22. try {
  23. await removeDeletedDoc(docId, options)
  24. } catch (err) {
  25. logger.error({ docId, err }, 'error removing deleted doc')
  26. }
  27. }
  28. }
  29. }
  30. async function removeDeletedDoc(docId, options) {
  31. const projectId = await rclient.get(keys.projectKey({ doc_id: docId }))
  32. const [
  33. docLines,
  34. version,
  35. ranges,
  36. pathname,
  37. projectHistoryId,
  38. unflushedTime,
  39. lastUpdatedAt,
  40. lastUpdatedBy,
  41. ] = await getDoc(projectId, docId)
  42. const project = await db.projects.findOne({ _id: ObjectId(projectId) })
  43. let status
  44. if (project) {
  45. const projectJSON = JSON.stringify(project.rootFolder)
  46. const containsDoc = projectJSON.indexOf(docId) !== -1
  47. if (containsDoc) {
  48. logger.warn(
  49. {
  50. projectId,
  51. docId,
  52. docLinesBytes: docLines && docLines.length,
  53. version,
  54. rangesBytes: ranges && ranges.length,
  55. pathname,
  56. projectHistoryId,
  57. unflushedTime,
  58. lastUpdatedAt,
  59. lastUpdatedBy,
  60. },
  61. 'refusing to delete doc, project contains docId'
  62. )
  63. summary.skippedDocs++
  64. return
  65. } else {
  66. logger.warn(
  67. {
  68. projectId,
  69. docId,
  70. docLinesBytes: docLines && docLines.length,
  71. version,
  72. rangesBytes: ranges && ranges.length,
  73. pathname,
  74. projectHistoryId,
  75. unflushedTime,
  76. lastUpdatedAt,
  77. lastUpdatedBy,
  78. },
  79. 'refusing to delete doc, project still exists'
  80. )
  81. summary.skippedDocs++
  82. return
  83. }
  84. } else {
  85. status = 'projectDeleted'
  86. }
  87. summary.deletedDocs++
  88. if (options.dryRun) {
  89. logger.info(
  90. {
  91. projectId,
  92. docId,
  93. docLinesBytes: docLines && docLines.length,
  94. version,
  95. rangesBytes: ranges && ranges.length,
  96. pathname,
  97. projectHistoryId,
  98. unflushedTime,
  99. lastUpdatedAt,
  100. lastUpdatedBy,
  101. status,
  102. summary,
  103. },
  104. 'dry run mode - would remove doc from redis'
  105. )
  106. return
  107. }
  108. removeDocFromMemory(projectId, docId)
  109. logger.info(
  110. {
  111. projectId,
  112. docId,
  113. docLinesBytes: docLines && docLines.length,
  114. version,
  115. rangesBytes: ranges && ranges.length,
  116. pathname,
  117. projectHistoryId,
  118. unflushedTime,
  119. lastUpdatedAt,
  120. lastUpdatedBy,
  121. status,
  122. summary,
  123. },
  124. 'removed doc from redis'
  125. )
  126. }
  127. async function findAndProcessDocs(options) {
  128. logger.info({ options }, 'removing deleted docs')
  129. let cursor = 0
  130. do {
  131. const [newCursor, doclinesKeys] = await rclient.scan(
  132. cursor,
  133. 'MATCH',
  134. keys.docLines({ doc_id: '*' }),
  135. 'COUNT',
  136. options.limit
  137. )
  138. await removeDeletedDocs(doclinesKeys, options)
  139. cursor = newCursor
  140. } while (cursor !== '0')
  141. }
  142. findAndProcessDocs({ limit: 1000, dryRun: process.env.DRY_RUN !== 'false' })
  143. .then(result => {
  144. rclient.quit()
  145. mongoClient.close()
  146. console.log('DONE')
  147. })
  148. .catch(function (error) {
  149. console.error(error)
  150. process.exit(1)
  151. })