[object Object]

The script worked fine for a year and then started timing out as table volume crossed two million rows. The bug was the same pattern in three places — N+1 queries inside iteration, full-table scans masked by small dev datasets, getRowCount() against tables that grew. GlideRecord is forgiving until it isn’t, and the patterns below are the difference between server-side scripts that scale and ones that get a 1 AM page.

Select Only Fields You Need

GlideRecord pulls every field by default and pays the database cost for it. For large tables or tight loops, use setFieldsNeeded() to project only the columns you read. The savings are not just memory — they reduce transit time between the database and the application server, which dominates query latency on wide tables. For pure counting, use GlideAggregate instead of pulling rows.

var gr = new GlideRecord('incident');
gr.setFieldsNeeded(['number', 'short_description', 'priority']);
gr.addQuery('active', true);
gr.setLimit(500);
gr.query();

Use addEncodedQuery for Complex Conditions

Complex where-clauses compose better with encoded queries — the same format used in list-view URL filters. Reusable across scripts, shareable with platform users (paste into a list view to verify), and easier to test than chained addQuery() plus addOr() calls. The string format is auditable in code review; chained method calls hide the logic in the chain order.

var gr = new GlideRecord('incident');
gr.addEncodedQuery('priority<=2^assignment_groupINgroup1,group2^stateNOT IN6,7');
gr.query();

Chunk Large Queries

Never retrieve millions of rows in one shot. Use setLimit() plus a pagination pattern (walk forward with addQuery('sys_id', '>', lastSysId) ordered by sys_id), or use the platform’s batched processing utilities for very large operations. Batch sizes of 1000-5000 are a reasonable default; tune based on how much processing happens per row. The transaction timeout will hit eventually on unbounded queries, and the partial work done is rarely transactional.

function processInBatches(tableName, encodedQuery, processor) {
  var lastSysId = '';
  while (true) {
    var gr = new GlideRecord(tableName);
    gr.addEncodedQuery(encodedQuery);
    if (lastSysId) gr.addQuery('sys_id', '>', lastSysId);
    gr.orderBy('sys_id');
    gr.setLimit(1000);
    gr.query();
    if (!gr.hasNext()) break;
    while (gr.next()) {
      processor(gr);
      lastSysId = gr.sys_id+'';
    }
  }
}

GlideAggregate for Counting

gr.getRowCount() fetches all matching rows and counts them in JavaScript. Wrong approach on a big table — wastes database time and memory for a single integer answer. GlideAggregate pushes the count to the database where it belongs. Always use it for aggregates (count, sum, average, min, max). The same applies to grouping operations; do them in the database.

Never Query in a Loop

Nested GlideRecord queries inside iteration over a parent result set is the classic N+1 trap. A loop over 1000 incidents that queries cmdb_ci for each one issues 1001 database round-trips. Collect IDs first, then issue one query with addQuery('sys_id', 'IN', listOfIds). The N+1 anti-pattern is responsible for most “this script worked in dev and times out in production” stories.

// N+1 anti-pattern (BAD)
var incs = new GlideRecord('incident');
incs.query();
while (incs.next()) {
  var ci = new GlideRecord('cmdb_ci');
  ci.get(incs.cmdb_ci); // N queries
}

// Batch lookup (GOOD)
var incs = new GlideRecord('incident');
incs.query();
var ciIds = [];
while (incs.next()) ciIds.push(incs.cmdb_ci+'');
var cis = new GlideRecord('cmdb_ci');
cis.addQuery('sys_id', 'IN', ciIds);
cis.query(); // one query

Common Failure Modes

Forgetting to call query() after addQuery — the GlideRecord exists but iterates over no data; tests pass against empty result, production fails against real data. update() called inside iteration without setWorkflow(false) when business rules should be skipped — performance collapses as cascading rules fire per row. Using gs.eventQueue() from inside a loop without batching — the event table grows faster than processing can drain.

What Changed in 2026

Newer releases optimize some common GlideRecord patterns at the platform level (better fields-needed inference for simple queries), but the patterns above still apply. The platform optimizations are not a substitute for understanding the cost of each operation; they widen the margin for code that already follows the patterns and do little for code that does not.

What to do this week: pick the longest-running scheduled job in your instance and review its server-side script for the N+1 pattern; that is almost always where the time is going.

[object Object]
Share