delete_orphaned_project_archives.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. const Settings = require('@overleaf/settings')
  2. const { waitForDb } = require('../app/src/infrastructure/mongodb')
  3. const { promiseMapWithLimit } = require('../app/src/util/promises')
  4. const { getHardDeletedProjectIds } = require('./delete_orphaned_data_helper')
  5. const TpdsUpdateSender = require('../app/src/Features/ThirdPartyDataStore/TpdsUpdateSender')
  6. const { promisify } = require('util')
  7. const { ObjectId } = require('mongodb')
  8. const request = require('request-promise-native')
  9. const sleep = promisify(setTimeout)
  10. const START_OFFSET = process.env.START_OFFSET
  11. const BATCH_SIZE = parseInt(process.env.BATCH_SIZE, 10) || 1000
  12. const DRY_RUN = process.env.DRY_RUN !== 'false'
  13. const READ_CONCURRENCY_SECONDARY =
  14. parseInt(process.env.READ_CONCURRENCY_SECONDARY, 10) || 1000
  15. const READ_CONCURRENCY_PRIMARY =
  16. parseInt(process.env.READ_CONCURRENCY_PRIMARY, 10) || 500
  17. const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY, 10) || 10
  18. const LET_USER_DOUBLE_CHECK_INPUTS_FOR =
  19. parseInt(process.env.LET_USER_DOUBLE_CHECK_INPUTS_FOR, 10) || 10 * 1000
  20. async function main() {
  21. await letUserDoubleCheckInputs()
  22. await waitForDb()
  23. let processed = 0
  24. let hardDeleted = 0
  25. let pageToken = ''
  26. let startOffset = START_OFFSET
  27. while (pageToken !== undefined) {
  28. const { nextPageToken, entries } = await request({
  29. url: `${Settings.apis.project_archiver.url}/project/list`,
  30. json: true,
  31. qs: {
  32. pageToken,
  33. startOffset,
  34. },
  35. })
  36. pageToken = nextPageToken
  37. startOffset = undefined
  38. hardDeleted += await processBatch(entries)
  39. processed += entries.length
  40. console.log(
  41. 'processed:',
  42. processed.toString().padStart(10, '0'),
  43. 'hard deleted:',
  44. hardDeleted.toString().padStart(10, '0'),
  45. 'nextPageToken:',
  46. nextPageToken,
  47. 'START_OFFSET:',
  48. entries.pop()?.prefix
  49. )
  50. }
  51. }
  52. async function processBatch(entries) {
  53. const projectIdToPrefix = new Map()
  54. for (const { prefix, projectId } of entries) {
  55. const prefixes = projectIdToPrefix.get(projectId) || []
  56. prefixes.push(prefix)
  57. projectIdToPrefix.set(projectId, prefixes)
  58. }
  59. const projectIds = Array.from(projectIdToPrefix.keys()).map(id =>
  60. ObjectId(id)
  61. )
  62. const projectsWithOrphanedArchive = await getHardDeletedProjectIds({
  63. projectIds,
  64. READ_CONCURRENCY_PRIMARY,
  65. READ_CONCURRENCY_SECONDARY,
  66. })
  67. await promiseMapWithLimit(
  68. WRITE_CONCURRENCY,
  69. projectsWithOrphanedArchive.flatMap(id =>
  70. projectIdToPrefix.get(id.toString())
  71. ),
  72. hardDeleteProjectArchiverData
  73. )
  74. return projectsWithOrphanedArchive.length
  75. }
  76. async function hardDeleteProjectArchiverData(prefix) {
  77. console.log(`Destroying hard deleted project archive at '${prefix}/'`)
  78. if (DRY_RUN) return
  79. for (let i = 0; i < 10; i++) {
  80. await sleep(1000 * i)
  81. try {
  82. const ok = await TpdsUpdateSender.promises.deleteProject({
  83. projectId: encodeURIComponent(prefix),
  84. })
  85. if (ok) {
  86. return
  87. }
  88. } catch (e) {
  89. console.error(`deletion failed for '${prefix}/'`, e)
  90. }
  91. }
  92. throw new Error(`deletion failed for '${prefix}/', check logs`)
  93. }
  94. async function letUserDoubleCheckInputs() {
  95. console.error(
  96. 'Options:',
  97. JSON.stringify(
  98. {
  99. BATCH_SIZE,
  100. DRY_RUN,
  101. LET_USER_DOUBLE_CHECK_INPUTS_FOR,
  102. READ_CONCURRENCY_SECONDARY,
  103. READ_CONCURRENCY_PRIMARY,
  104. START_OFFSET,
  105. WRITE_CONCURRENCY,
  106. },
  107. null,
  108. 2
  109. )
  110. )
  111. console.error(
  112. 'Waiting for you to double check inputs for',
  113. LET_USER_DOUBLE_CHECK_INPUTS_FOR,
  114. 'ms'
  115. )
  116. await sleep(LET_USER_DOUBLE_CHECK_INPUTS_FOR)
  117. }
  118. main()
  119. .then(() => {
  120. console.log('Done.')
  121. process.exit(0)
  122. })
  123. .catch(error => {
  124. console.error({ error })
  125. process.exit(1)
  126. })