artigo

performance e diagnóstico: o preço do código lento

GlideRecord sem filtro, getRowCount() e query em loop respondem pela maioria dos incidentes de performance — como reconhecer e medir antes de otimizar.

6 min de leitura avançado servicenow desenvolvimento performance

#o script que passou no seu teste e travou a produção

Você escreve um Business Rule, testa numa instância de dev com 40 registros na tabela, funciona instantaneamente, sobe pro Update Set e segue pra próxima tarefa. Três meses depois a mesma tabela tem 300 mil registros, o mesmo script continua rodando, e agora ele é a explicação para "o sistema ficou lento hoje de manhã" no canal de incidentes.

O detalhe que muda a forma de pensar sobre isso: ServiceNow é multi-tenant dentro da própria instância. Um script pesado num formulário não afeta só quem clicou em salvar — afeta os outros 200 usuários que estão no mesmo processo em paralelo. Performance não é polimento de fim de projeto. É o tipo de coisa que, quando ignorada, vira reunião de RCA com o nome de alguém na ata.

#os três hábitos que parecem inofensivos até o volume chegar

Tem uma característica em comum nos três anti-padrões abaixo: nenhum deles dá erro. O código roda, devolve o resultado certo, passa em code review — porque quem revisa também está olhando pra uma tabela de dev com poucos registros. O problema não é lógico, é de escala.

1. Query sem addQuery(). Um GlideRecord que chama .query() sem nenhum filtro varre a tabela inteira. Em incident ou task, isso pode significar milhões de linhas.

javascript
// ❌ varre a tabela inteira
var gr = new GlideRecord("task");
gr.query();

// �
 filtro + limite explícito quando você sabe o teto esperado
var gr = new GlideRecord("task");
gr.addQuery("active", true);
gr.addQuery("assigned_to", gs.getUserID());
gr.setLimit(500);
gr.query();

2. getRowCount() só para contar. Esse método carrega cada registro na memória, um por um, e só depois entrega o total — é a diferença entre pedir "quantas mesas estão ocupadas" e ter que andar pelo salão inteiro contando cabeça por cabeça antes de responder.

javascript
// ❌ carrega tudo pra saber quantos são
var gr = new GlideRecord("incident");
gr.addQuery("state", 1);
gr.query();
var total = gr.getRowCount();

// �
 o banco já devolve o número pronto
var ga = new GlideAggregate("incident");
ga.addQuery("state", 1);
ga.addAggregate("COUNT");
ga.query();
ga.next();
var total = parseInt(ga.getAggregate("COUNT"));

3. GlideRecord dentro de loop — o padrão N+1. Uma query pra buscar a lista, mais uma query nova a cada iteração do loop. Para 500 registros, isso é 501 idas ao banco. Para 5.000, são 5.001.

javascript
// ❌ 1 query na lista + 1 por registro = N+1
var gr = new GlideRecord("incident");
gr.addQuery("state", 1);
gr.query();
while (gr.next()) {
    var grCI = new GlideRecord("cmdb_ci");
    grCI.get(gr.getValue("cmdb_ci")); // query nova a cada volta
}

// �
 junta os IDs primeiro, resolve tudo numa segunda query
var gr = new GlideRecord("incident");
gr.addQuery("state", 1);
gr.query();

var ciIds = [];
while (gr.next()) {
    var ciId = gr.getValue("cmdb_ci");
    if (ciId && ciIds.indexOf(ciId) === -1) ciIds.push(ciId);
}

var ciMap = {};
if (ciIds.length > 0) {
    var grCI = new GlideRecord("cmdb_ci");
    grCI.addQuery("sys_id", "IN", ciIds.join(","));
    grCI.query();
    while (grCI.next()) ciMap[grCI.getUniqueValue()] = grCI.getValue("name");
}

#GlideAggregate merece um parágrafo próprio

GlideAggregate não serve só para substituir getRowCount(). É a API certa sempre que a pergunta é "quanto", "quantos" ou "qual o total" — COUNT, SUM, MIN, MAX, AVG, GROUP BY. A diferença prática é que o cálculo acontece no banco, não no Rhino/servidor de aplicação depois de carregar tudo.

javascript
// Quantos incidentes ativos por grupo de atribuição
var ga = new GlideAggregate("incident");
ga.addQuery("active", true);
ga.addAggregate("COUNT");
ga.groupBy("assignment_group");
ga.orderByAggregate("COUNT");
ga.setLimit(20);
ga.query();

var porGrupo = {};
while (ga.next()) {
    porGrupo[ga.getDisplayValue("assignment_group")] = parseInt(ga.getAggregate("COUNT"));
}

Regra prática: se o retorno esperado passa de uma centena de registros, ou a tabela cresce continuamente, GlideAggregate deixa de ser opcional. Na dúvida, use GlideAggregate — o custo de escrever um pouco mais de código é bem menor que o custo de descobrir o limite na produção.

#scheduled script grande? o relógio não perdoa

Scheduled Script não roda pra sempre — o corte é controlado por Transaction Quota Rules configuráveis (System Definition > Transaction Quota Rules), e é comum instâncias cancelarem jobs longos bem antes de qualquer limite teórico que você tenha em mente. Se o seu job processa 100 mil registros a 20ms cada, a conta fecha em 33 minutos — tempo de sobra pra esbarrar em alguma quota configurada, e quando isso acontece o ServiceNow não espera você terminar: corta no meio, com uma fatia de dados processada e outra não.

A saída é processar em lotes menores e controlar o tempo decorrido dentro do próprio script, parando antes do limite e deixando o próximo agendamento continuar de onde parou:

javascript
(function syncExternalUsers() {
    var CHUNK_SIZE = 200;
    var MAX_EXEC_MINUTES = 25; // margem de segurança — ajuste conforme a quota configurada na sua instância
    var startTime = new GlideDateTime();
    var processed = 0;

    var gr = new GlideRecord("x_acme_sync_queue");
    gr.addQuery("u_status", "pending");
    gr.orderBy("sys_created_on");
    gr.setLimit(CHUNK_SIZE);
    gr.query();

    while (gr.next()) {
        var elapsedMin = GlideDateTime.subtract(startTime, new GlideDateTime())
            .getNumericValue() / 60000;

        if (elapsedMin >= MAX_EXEC_MINUTES) {
            gs.warn("syncExternalUsers: parando em " + processed + " registros — resto fica pro próximo agendamento");
            break;
        }

        // processa o registro, atualiza status
        processed++;
    }
})();

Duas peças fazem esse padrão funcionar: setLimit() garante que cada execução pega só um pedaço, e a verificação de tempo dentro do while garante que você nunca chega perto do corte abrupto — o job para de forma limpa, com log explicando o motivo, e não no meio de uma escrita.

#medir antes de mexer

A tentação depois de ler uma lista de anti-padrões é sair caçando getRowCount() no código inteiro da instância. Não faça isso sem medir primeiro — otimização sem medição é aposta, e às vezes você otimiza a parte que não era o gargalo.

ferramentaonde ficao que mostra
StatsSystem Diagnostics > Statstransações mais lentas, Business Rules mais executados, tempo médio de resposta por tabela
Slow Query LogSystem Diagnostics > Slow QueriesSQL que passou do threshold configurável (confira o valor vigente na sua instância), com o SQL gerado
Transaction LogSystem Logs > Transactionstempo total, memória e número de queries de uma transação específica
Instance ScanSystem Diagnostics > Instance Scanvarredura automática de Business Rules e configurações problemáticas fora do padrão

Vale lembrar também o que ficou pendente do artigo sobre modelo de dados: índice de banco acelera leitura e penaliza escrita. Crie índice em campo usado em filtro de lista com frequência alta e tabela que já cresceu (não existe número mágico oficial — na prática, "grande o bastante para o full scan doer" costuma ser a régua); não crie em tabela pequena ou em campo usado só em relatório batch ocasional — o ganho não paga o custo em toda escrita subsequente.

#e agora?

  • Na sua instância, abra System Diagnostics > Stats uma vez essa semana, mesmo sem estar investigando nada — é a forma mais barata de pegar uma tabela crescendo rápido antes que ela vire incidente.
  • Procure getRowCount() nos Script Includes e Business Rules do seu escopo. É a troca de menor esforço com maior retorno: geralmente é uma substituição direta por GlideAggregate.
  • Combine este artigo com Business Rules: timing e arquitetura — lá vimos changesTo() e saída rápida como regras de performance no nível do Business Rule; aqui vimos o nível da query. Os dois juntos cobrem a maior parte dos incidentes de lentidão que chegam pro time de desenvolvimento.
LinkedIn X/Twitter WhatsApp ☕ me paga um café