|
|
@@ -26,6 +26,7 @@ const {
|
|
|
* @param {boolean} [opts.force]
|
|
|
* @param {boolean} [opts.stopOnError]
|
|
|
* @param {boolean} [opts.quickOnly]
|
|
|
+ * @param {number} [opts.concurrency]
|
|
|
*/
|
|
|
async function migrateProjects(opts = {}) {
|
|
|
const {
|
|
|
@@ -38,6 +39,7 @@ async function migrateProjects(opts = {}) {
|
|
|
force = false,
|
|
|
stopOnError = false,
|
|
|
quickOnly = false,
|
|
|
+ concurrency = 1,
|
|
|
} = opts
|
|
|
|
|
|
const clauses = []
|
|
|
@@ -70,11 +72,31 @@ async function migrateProjects(opts = {}) {
|
|
|
})
|
|
|
.sort({ _id: -1 })
|
|
|
|
|
|
+ let terminating = false
|
|
|
+ const handleSignal = signal => {
|
|
|
+ logger.info({ signal }, 'History ranges support migration received signal')
|
|
|
+ terminating = true
|
|
|
+ }
|
|
|
+ process.on('SIGINT', handleSignal)
|
|
|
+ process.on('SIGTERM', handleSignal)
|
|
|
+
|
|
|
let projectsProcessed = 0
|
|
|
+ const jobsByProjectId = new Map()
|
|
|
+ let errors = 0
|
|
|
+
|
|
|
for await (const project of projects) {
|
|
|
if (projectsProcessed >= maxCount) {
|
|
|
break
|
|
|
}
|
|
|
+
|
|
|
+ if (errors > 0 && stopOnError) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ if (terminating) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
const projectId = project._id.toString()
|
|
|
|
|
|
if (!force) {
|
|
|
@@ -89,45 +111,63 @@ async function migrateProjects(opts = {}) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const startTimeMs = Date.now()
|
|
|
- let quickMigrationSuccess
|
|
|
- try {
|
|
|
- quickMigrationSuccess = await quickMigration(projectId, direction)
|
|
|
- if (!quickMigrationSuccess) {
|
|
|
- if (quickOnly) {
|
|
|
- logger.info(
|
|
|
- { projectId, direction },
|
|
|
- 'Quick migration failed, skipping project'
|
|
|
- )
|
|
|
- } else {
|
|
|
- await migrateProject(projectId, direction)
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (err) {
|
|
|
- logger.error(
|
|
|
- { err, projectId, direction, projectsProcessed },
|
|
|
- 'Failed to migrate history ranges support'
|
|
|
- )
|
|
|
- projectsProcessed += 1
|
|
|
- if (stopOnError) {
|
|
|
- break
|
|
|
- } else {
|
|
|
- continue
|
|
|
- }
|
|
|
+ if (jobsByProjectId.size >= concurrency) {
|
|
|
+ // Wait until the next job finishes
|
|
|
+ await Promise.race(jobsByProjectId.values())
|
|
|
}
|
|
|
- const elapsedMs = Date.now() - startTimeMs
|
|
|
- projectsProcessed += 1
|
|
|
- logger.info(
|
|
|
- {
|
|
|
- projectId,
|
|
|
- direction,
|
|
|
- projectsProcessed,
|
|
|
- elapsedMs,
|
|
|
- quick: quickMigrationSuccess,
|
|
|
- },
|
|
|
- 'Migrated history ranges support'
|
|
|
- )
|
|
|
+
|
|
|
+ const job = processProject(projectId, direction, quickOnly)
|
|
|
+ .then(info => {
|
|
|
+ jobsByProjectId.delete(projectId)
|
|
|
+ projectsProcessed += 1
|
|
|
+ logger.info(
|
|
|
+ {
|
|
|
+ projectId,
|
|
|
+ direction,
|
|
|
+ projectsProcessed,
|
|
|
+ errors,
|
|
|
+ ...info,
|
|
|
+ },
|
|
|
+ 'History ranges support migration'
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ jobsByProjectId.delete(projectId)
|
|
|
+ errors += 1
|
|
|
+ logger.error(
|
|
|
+ { err, projectId, direction, projectsProcessed, errors },
|
|
|
+ 'Failed to migrate history ranges support'
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ jobsByProjectId.set(projectId, job)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Let the last jobs finish
|
|
|
+ await Promise.all(jobsByProjectId.values())
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Migrate a single project
|
|
|
+ *
|
|
|
+ * @param {string} projectId
|
|
|
+ * @param {"forwards" | "backwards"} direction
|
|
|
+ * @param {boolean} quickOnly
|
|
|
+ */
|
|
|
+async function processProject(projectId, direction, quickOnly) {
|
|
|
+ const startTimeMs = Date.now()
|
|
|
+ const quickMigrationSuccess = await quickMigration(projectId, direction)
|
|
|
+ let migrationType
|
|
|
+ if (quickMigrationSuccess) {
|
|
|
+ migrationType = 'quick'
|
|
|
+ } else if (quickOnly) {
|
|
|
+ migrationType = 'skipped'
|
|
|
+ } else {
|
|
|
+ await migrateProject(projectId, direction)
|
|
|
+ migrationType = 'resync'
|
|
|
}
|
|
|
+ const elapsedMs = Date.now() - startTimeMs
|
|
|
+ return { migrationType, elapsedMs }
|
|
|
}
|
|
|
|
|
|
/**
|