delete_orphaned_project_archives.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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, projectIds } = 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(projectIds.map(id => ObjectId(id)))
  39. processed += projectIds.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. projectIds.pop()
  49. )
  50. }
  51. }
  52. async function processBatch(projectIds) {
  53. const projectsWithOrphanedArchive = await getHardDeletedProjectIds({
  54. projectIds,
  55. READ_CONCURRENCY_PRIMARY,
  56. READ_CONCURRENCY_SECONDARY,
  57. })
  58. await promiseMapWithLimit(
  59. WRITE_CONCURRENCY,
  60. projectsWithOrphanedArchive,
  61. hardDeleteProjectArchiverData
  62. )
  63. return projectsWithOrphanedArchive.length
  64. }
  65. async function hardDeleteProjectArchiverData(projectId) {
  66. console.log(`Destroying hard deleted project archive for ${projectId}`)
  67. if (DRY_RUN) return
  68. const ok = await TpdsUpdateSender.promises.deleteProject({
  69. project_id: projectId,
  70. })
  71. if (!ok) {
  72. throw new Error(`deletion failed for ${projectId}, check logs`)
  73. }
  74. }
  75. async function letUserDoubleCheckInputs() {
  76. console.error(
  77. 'Options:',
  78. JSON.stringify(
  79. {
  80. BATCH_SIZE,
  81. DRY_RUN,
  82. LET_USER_DOUBLE_CHECK_INPUTS_FOR,
  83. READ_CONCURRENCY_SECONDARY,
  84. READ_CONCURRENCY_PRIMARY,
  85. START_OFFSET,
  86. WRITE_CONCURRENCY,
  87. },
  88. null,
  89. 2
  90. )
  91. )
  92. console.error(
  93. 'Waiting for you to double check inputs for',
  94. LET_USER_DOUBLE_CHECK_INPUTS_FOR,
  95. 'ms'
  96. )
  97. await sleep(LET_USER_DOUBLE_CHECK_INPUTS_FOR)
  98. }
  99. main()
  100. .then(() => {
  101. console.log('Done.')
  102. process.exit(0)
  103. })
  104. .catch(error => {
  105. console.error({ error })
  106. process.exit(1)
  107. })