118 lines
3.1 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
});
|