batchedUpdate.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. const { ReadPreference, ObjectId } = require('mongodb')
  2. const { db, waitForDb } = require('../../app/src/infrastructure/mongodb')
  3. const BATCH_DESCENDING = process.env.BATCH_DESCENDING === 'true'
  4. const BATCH_SIZE = parseInt(process.env.BATCH_SIZE, 10) || 1000
  5. const VERBOSE_LOGGING = process.env.VERBOSE_LOGGING === 'true'
  6. let BATCH_LAST_ID
  7. if (process.env.BATCH_LAST_ID) {
  8. BATCH_LAST_ID = ObjectId(process.env.BATCH_LAST_ID)
  9. } else if (process.env.BATCH_RANGE_START) {
  10. BATCH_LAST_ID = ObjectId(process.env.BATCH_RANGE_START)
  11. }
  12. let BATCH_RANGE_END
  13. if (process.env.BATCH_RANGE_END) {
  14. BATCH_RANGE_END = ObjectId(process.env.BATCH_RANGE_END)
  15. }
  16. async function getNextBatch(collection, query, maxId, projection, options) {
  17. const queryIdField = {}
  18. maxId = maxId || BATCH_LAST_ID
  19. if (maxId) {
  20. if (BATCH_DESCENDING) {
  21. queryIdField.$lt = maxId
  22. } else {
  23. queryIdField.$gt = maxId
  24. }
  25. }
  26. if (BATCH_RANGE_END) {
  27. if (BATCH_DESCENDING) {
  28. queryIdField.$gt = BATCH_RANGE_END
  29. } else {
  30. queryIdField.$lt = BATCH_RANGE_END
  31. }
  32. }
  33. if (queryIdField.$gt || queryIdField.$lt) {
  34. query._id = queryIdField
  35. }
  36. const entries = await collection
  37. .find(query, options)
  38. .project(projection)
  39. .sort({ _id: BATCH_DESCENDING ? -1 : 1 })
  40. .limit(BATCH_SIZE)
  41. .toArray()
  42. return entries
  43. }
  44. async function performUpdate(collection, nextBatch, update) {
  45. return collection.updateMany(
  46. { _id: { $in: nextBatch.map(entry => entry._id) } },
  47. update
  48. )
  49. }
  50. async function batchedUpdate(
  51. collectionName,
  52. query,
  53. update,
  54. projection,
  55. options
  56. ) {
  57. await waitForDb()
  58. const collection = db[collectionName]
  59. options = options || {}
  60. options.readPreference = ReadPreference.SECONDARY
  61. projection = projection || { _id: 1 }
  62. let nextBatch
  63. let updated = 0
  64. let maxId
  65. while (
  66. (nextBatch = await getNextBatch(
  67. collection,
  68. query,
  69. maxId,
  70. projection,
  71. options
  72. )).length
  73. ) {
  74. maxId = nextBatch[nextBatch.length - 1]._id
  75. updated += nextBatch.length
  76. if (VERBOSE_LOGGING) {
  77. console.log(
  78. `Running update on batch with ids ${JSON.stringify(
  79. nextBatch.map(entry => entry._id)
  80. )}`
  81. )
  82. } else {
  83. console.error(`Running update on batch ending ${maxId}`)
  84. }
  85. if (typeof update === 'function') {
  86. await update(collection, nextBatch)
  87. } else {
  88. await performUpdate(collection, nextBatch, update)
  89. }
  90. console.error(`Completed batch ending ${maxId}`)
  91. }
  92. return updated
  93. }
  94. function batchedUpdateWithResultHandling(
  95. collection,
  96. query,
  97. update,
  98. projection,
  99. options
  100. ) {
  101. batchedUpdate(collection, query, update, projection, options)
  102. .then(updated => {
  103. console.error({ updated })
  104. process.exit(0)
  105. })
  106. .catch(error => {
  107. console.error({ error })
  108. process.exit(1)
  109. })
  110. }
  111. module.exports = {
  112. getNextBatch,
  113. batchedUpdate,
  114. batchedUpdateWithResultHandling,
  115. }