agmission/Development/server/scripts/migrate_avg_spray_speed.js

118 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* Migration: backfill App.avgSpraySpeed for all existing applications.
*
* Uses a cursor over App documents to avoid loading everything into memory.
* Safe to run on production — read-only on AppDetail, bulkWrite on App.
* Re-entrant: apps that already have avgSpraySpeed are skipped.
*
* Usage:
* node scripts/migrate_avg_spray_speed.js
* node scripts/migrate_avg_spray_speed.js --dry-run
*/
'use strict';
const debug = require('debug')('agm:migrate-avg-spray-speed');
const { DBConnection } = require('../helpers/db/connect.js');
const { App, AppFile, AppDetail } = require('../model/index.js');
const utils = require('../helpers/utils.js');
const DRY_RUN = process.argv.includes('--dry-run');
const BATCH_SIZE = 50; // Apps per bulkWrite batch
const PROGRESS_EVERY = 100; // Log every N apps
async function computeAvgSpraySpeed(appId) {
const files = await AppFile.find({ appId, markedDelete: { $ne: true } }, '_id').lean();
if (!files.length) return null;
const fileIds = files.map(f => f._id);
let speedAcc = 0, count = 0;
const cursor = AppDetail.find(
{ fileId: { $in: fileIds }, sprayStat: { $in: [1, 2] } },
{ grSpeed: 1 },
{ lean: true }
).cursor();
for await (const record of cursor) {
if (utils.isNumber(record.grSpeed)) {
speedAcc += record.grSpeed;
count++;
}
}
return count > 0 ? speedAcc / count : null;
}
async function migrate() {
debug(`Starting${DRY_RUN ? ' (DRY RUN)' : ''}...`);
const total = await App.countDocuments({ avgSpraySpeed: { $exists: false }, status: 3 });
debug(`Apps to process: ${total}`);
if (!total) {
debug('Nothing to do.');
return;
}
let processed = 0, updated = 0, skipped = 0, errors = 0;
let bulk = [];
const appCursor = App.find(
{ avgSpraySpeed: { $exists: false }, status: 3 },
'_id'
).sort({ _id: 1 }).lean().cursor();
for await (const app of appCursor) {
try {
const avgSpeed = await computeAvgSpraySpeed(app._id);
if (avgSpeed !== null) {
bulk.push({
updateOne: {
filter: { _id: app._id },
update: { $set: { avgSpraySpeed: avgSpeed } }
}
});
updated++;
} else {
skipped++;
}
} catch (err) {
errors++;
debug(`Error on App ${app._id}: ${err.message}`);
}
processed++;
if (processed % PROGRESS_EVERY === 0) {
debug(`Progress: ${processed}/${total} (updated=${updated}, skipped=${skipped}, errors=${errors})`);
}
if (bulk.length >= BATCH_SIZE) {
if (!DRY_RUN) await App.bulkWrite(bulk, { ordered: false });
bulk = [];
}
}
if (bulk.length && !DRY_RUN) {
await App.bulkWrite(bulk, { ordered: false });
}
debug(`Done. processed=${processed}, updated=${updated}, skipped=${skipped}, errors=${errors}${DRY_RUN ? ' (DRY RUN — no writes)' : ''}`);
}
const workerDB = new DBConnection('Migrate avgSpraySpeed');
workerDB.initialize({
setupExitHandlers: false,
onReady: async () => {
try {
await migrate();
process.exit(0);
} catch (err) {
debug('Migration failed:', err);
process.exit(1);
}
}
});