From 42271d95d3ff2868e643d68b2daea4f8ce2b5095 Mon Sep 17 00:00:00 2001 From: franks883 Date: Tue, 21 Oct 2025 18:33:11 +0100 Subject: [PATCH 1/2] Create README.md --- .../Safe Bulk Update Runner/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Server-Side Components/Script Includes/Safe Bulk Update Runner/README.md diff --git a/Server-Side Components/Script Includes/Safe Bulk Update Runner/README.md b/Server-Side Components/Script Includes/Safe Bulk Update Runner/README.md new file mode 100644 index 0000000000..7dc579f324 --- /dev/null +++ b/Server-Side Components/Script Includes/Safe Bulk Update Runner/README.md @@ -0,0 +1,18 @@ +# Safe Bulk Update Runner (auto-throttled) + +## Use case +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. + +## Where to use it +- Script Include invoked from Background Script, on-demand Scheduled Job, or Flow Action wrapper. + +## How it works +- Queries a time-boxed chunk (e.g., 40 seconds, 500 rows). +- Executes a caller-supplied per-record function. +- Saves a checkpoint (`sys_id`) in a system property. +- Uses `ScheduleOnce` to queue the next slice (no `gs.sleep`). + +## Configuration +- Target table, encoded query, orderBy field (default `sys_id`) +- Chunk size, max execution seconds +- Property name for checkpoint From 5b505f6114900bfba55ac9a690d02be529f883e1 Mon Sep 17 00:00:00 2001 From: franks883 Date: Tue, 21 Oct 2025 18:33:43 +0100 Subject: [PATCH 2/2] Create safe_bulk_update_runner.js --- .../safe_bulk_update_runner.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Server-Side Components/Script Includes/Safe Bulk Update Runner/safe_bulk_update_runner.js diff --git a/Server-Side Components/Script Includes/Safe Bulk Update Runner/safe_bulk_update_runner.js b/Server-Side Components/Script Includes/Safe Bulk Update Runner/safe_bulk_update_runner.js new file mode 100644 index 0000000000..44eeaa1a5a --- /dev/null +++ b/Server-Side Components/Script Includes/Safe Bulk Update Runner/safe_bulk_update_runner.js @@ -0,0 +1,80 @@ + +var SafeBulkUpdateRunner = Class.create(); +SafeBulkUpdateRunner.prototype = (function () { + var DEFAULTS = { + property_name: 'x_util.safe_bulk.last_id', + table: 'incident', + query: 'active=true', + order_by: 'sys_id', + chunk_size: 500, + max_seconds: 40 // keep under typical script timeout + }; + + function _propGet(name, fallback) { var v = gs.getProperty(name, ''); return v || fallback || ''; } + function _propSet(name, value) { gs.setProperty(name, value || ''); } + function _gt(field, sysId) { return sysId ? field + '>' + sysId : ''; } + function _now() { return new Date().getTime(); } + function _scheduleNext(scriptIncludeName, methodName, args) { + var so = new ScheduleOnce(); + so.schedule(scriptIncludeName, methodName, JSON.stringify(args || {})); + gs.info('[SafeBulk] Scheduled next slice for ' + scriptIncludeName + '.' + methodName); + } + + return { + initialize: function () {}, + + /** + * Run one slice then schedule the next. + * @param {Object} cfg + * @param {Function} perRecordFn function(gr) -> void + */ + runSlice: function (cfg, perRecordFn) { + var c = Object.assign({}, DEFAULTS, cfg || {}); + if (typeof perRecordFn !== 'function') throw new Error('perRecordFn is required.'); + + var start = _now(); + var lastId = _propGet(c.property_name, ''); + + var gr = new GlideRecord(c.table); + gr.addEncodedQuery(c.query); + if (lastId) gr.addEncodedQuery(_gt(c.order_by, lastId)); + gr.orderBy(c.order_by); + gr.setLimit(c.chunk_size); + gr.query(); + + var processed = 0; + while (gr.next()) { + perRecordFn(gr); + lastId = String(gr.getUniqueValue()); + processed++; + + if ((_now() - start) / 1000 >= c.max_seconds) { + gs.info('[SafeBulk] Timebox reached after ' + processed + ' records.'); + break; + } + } + + if (lastId) _propSet(c.property_name, lastId); + + if (processed === c.chunk_size || gr.hasNext()) { + _scheduleNext('SafeBulkUpdateRunner', 'runSliceScheduled', { cfg: c }); + } else { + gs.info('[SafeBulk] All done. Clearing checkpoint.'); + _propSet(c.property_name, ''); + } + }, + + /** + * Entry point for ScheduleOnce (stringified args). + */ + runSliceScheduled: function (jsonArgs) { + var args = (typeof jsonArgs === 'string') ? JSON.parse(jsonArgs) : (jsonArgs || {}); + var cfg = args.cfg || {}; + // Example logic; replace with your own: + this.runSlice(cfg, function (gr) { + gr.setValue('u_backfilled', true); + gr.update(); + }); + } + }; +})();