Skip to content

Commit 27f1db0

Browse files
authored
Server-Side - Safe Bulk Update Runner (#2383)
* Create README.md * Create safe_bulk_update_runner.js
1 parent c56ba8c commit 27f1db0

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Safe Bulk Update Runner (auto-throttled)
2+
3+
## Use case
4+
Run large backfills/hygiene tasks without timeouts or instance impact. Instead of one risky long transaction, process records in chunks and automatically schedule the next slice.
5+
6+
## Where to use it
7+
- Script Include invoked from Background Script, on-demand Scheduled Job, or Flow Action wrapper.
8+
9+
## How it works
10+
- Queries a time-boxed chunk (e.g., 40 seconds, 500 rows).
11+
- Executes a caller-supplied per-record function.
12+
- Saves a checkpoint (`sys_id`) in a system property.
13+
- Uses `ScheduleOnce` to queue the next slice (no `gs.sleep`).
14+
15+
## Configuration
16+
- Target table, encoded query, orderBy field (default `sys_id`)
17+
- Chunk size, max execution seconds
18+
- Property name for checkpoint
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
var SafeBulkUpdateRunner = Class.create();
3+
SafeBulkUpdateRunner.prototype = (function () {
4+
var DEFAULTS = {
5+
property_name: 'x_util.safe_bulk.last_id',
6+
table: 'incident',
7+
query: 'active=true',
8+
order_by: 'sys_id',
9+
chunk_size: 500,
10+
max_seconds: 40 // keep under typical script timeout
11+
};
12+
13+
function _propGet(name, fallback) { var v = gs.getProperty(name, ''); return v || fallback || ''; }
14+
function _propSet(name, value) { gs.setProperty(name, value || ''); }
15+
function _gt(field, sysId) { return sysId ? field + '>' + sysId : ''; }
16+
function _now() { return new Date().getTime(); }
17+
function _scheduleNext(scriptIncludeName, methodName, args) {
18+
var so = new ScheduleOnce();
19+
so.schedule(scriptIncludeName, methodName, JSON.stringify(args || {}));
20+
gs.info('[SafeBulk] Scheduled next slice for ' + scriptIncludeName + '.' + methodName);
21+
}
22+
23+
return {
24+
initialize: function () {},
25+
26+
/**
27+
* Run one slice then schedule the next.
28+
* @param {Object} cfg
29+
* @param {Function} perRecordFn function(gr) -> void
30+
*/
31+
runSlice: function (cfg, perRecordFn) {
32+
var c = Object.assign({}, DEFAULTS, cfg || {});
33+
if (typeof perRecordFn !== 'function') throw new Error('perRecordFn is required.');
34+
35+
var start = _now();
36+
var lastId = _propGet(c.property_name, '');
37+
38+
var gr = new GlideRecord(c.table);
39+
gr.addEncodedQuery(c.query);
40+
if (lastId) gr.addEncodedQuery(_gt(c.order_by, lastId));
41+
gr.orderBy(c.order_by);
42+
gr.setLimit(c.chunk_size);
43+
gr.query();
44+
45+
var processed = 0;
46+
while (gr.next()) {
47+
perRecordFn(gr);
48+
lastId = String(gr.getUniqueValue());
49+
processed++;
50+
51+
if ((_now() - start) / 1000 >= c.max_seconds) {
52+
gs.info('[SafeBulk] Timebox reached after ' + processed + ' records.');
53+
break;
54+
}
55+
}
56+
57+
if (lastId) _propSet(c.property_name, lastId);
58+
59+
if (processed === c.chunk_size || gr.hasNext()) {
60+
_scheduleNext('SafeBulkUpdateRunner', 'runSliceScheduled', { cfg: c });
61+
} else {
62+
gs.info('[SafeBulk] All done. Clearing checkpoint.');
63+
_propSet(c.property_name, '');
64+
}
65+
},
66+
67+
/**
68+
* Entry point for ScheduleOnce (stringified args).
69+
*/
70+
runSliceScheduled: function (jsonArgs) {
71+
var args = (typeof jsonArgs === 'string') ? JSON.parse(jsonArgs) : (jsonArgs || {});
72+
var cfg = args.cfg || {};
73+
// Example logic; replace with your own:
74+
this.runSlice(cfg, function (gr) {
75+
gr.setValue('u_backfilled', true);
76+
gr.update();
77+
});
78+
}
79+
};
80+
})();

0 commit comments

Comments
 (0)