remove_deleted_docs.js 4.2 KB

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