delete_orphaned_project_archives.js 3.8 KB

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