πΌ Business Requirement
Your NetSuite Map/Reduce script updates or creates records (e.g., Sales Orders, Invoices, Customers). However, it sometimes fails due to a server restart β like a system update or backend crash β which causes partial processing and duplicate updates when the script resumes.
You need to ensure:
- No record is updated twice
- Processing can resume safely after a restart
- Your script is idempotent β repeatable with no side effects
π¨ Problem: Unprotected Map/Reduce Leads to Duplicate Updates
A basic script without restart-safe logic may update or save records twice if interrupted mid-run. NetSuite does not rollback saved records during server restarts β only internal map/reduce state is cleared.
Hereβs a simple version that fails on restart:
map: function (context) {
var soEntry = JSON.parse(context.value);
var so = record.load({
type: soEntry.values.recordtype,
id: context.key
});
// Update fields blindly
so.save();
}
If this script is restarted midway, it will load and save the same Sales Order again β resulting in duplicate processing.
β Solution: Make Your Script Restart-Aware (Best Practice)
- Add a custom field like
custbody_processed_flagon your target record. - Check the field using
search.lookupFields()inmap()only ifcontext.isRestarted === true. - Only update the record if the flag is false.
- Always use
context.write()regardless of restart state β NetSuite clears stale writes automatically.
π§© Sample Restart-Safe Map/Reduce Script
/**
* @NApiVersion 2.0
* @NScriptType mapreducescript
*/
define(['N/search', 'N/record'], function(search, record) {
return {
getInputData: function (context) {
return search.create({
type: search.Type.SALES_ORDER,
filters: [
['mainline', 'is', 'T'],
'AND',
['custbody_processed_flag', 'is', 'F'] // Only unprocessed
],
columns: ['recordtype']
});
},
map: function (context) {
var soEntry = JSON.parse(context.value);
var alreadyProcessed = false;
if (context.isRestarted) {
var lookupResult = search.lookupFields({
type: soEntry.values.recordtype,
id: context.key,
columns: ['custbody_processed_flag']
});
alreadyProcessed = lookupResult.custbody_processed_flag === true;
}
if (!alreadyProcessed) {
var so = record.load({
type: soEntry.values.recordtype,
id: context.key
});
// Update the Sales Order
so.setValue({ fieldId: 'custbody_processed_flag', value: true });
so.save();
}
context.write(soEntry.values.recordtype, context.key);
},
reduce: function (context) {
context.write(context.key, context.values.length);
},
summarize: function (summary) {
if (summary.isRestarted) {
log.audit({ title: 'Restart Detected', details: 'Summarize stage was restarted.' });
}
var totalUpdated = 0;
summary.output.iterator().each(function (key, value) {
log.audit({ title: key + ' updated', details: value });
totalUpdated += parseInt(value);
return true;
});
log.audit({ title: 'Total Records Updated', details: totalUpdated });
}
};
});
π Best Practices for Restart Safety
| β Do | β Avoid |
|---|---|
Use custbody_processed_flag to track updates | Updating without a flag or marker |
Check context.isRestarted to skip processed records | Relying on getInputData alone to prevent duplicates |
Use search.lookupFields() instead of full record load for quick flag check | Loading entire records just to check one value |
Keep context.write() outside if-blocks | Skipping writes can break reduce stage |
Log restart awareness in summarize() | Ignoring summary.isRestarted may hide issues |
π Summary
If your Map/Reduce script updates NetSuite records, you must protect it from accidental duplication caused by restarts. This script pattern shows exactly how to:
- Use a
processedflag - Check for restarts
- Ensure idempotent behavior
- Maintain stability even during server interruptions
This is one of the most underutilized features in SuiteScript β and applying it will instantly harden your scripts for production use.
Leave a Reply