Ch. 17 · ServiceNow

ServiceNow Caching and Invalidation

What a ServiceNow instance caches on each node, why a system property change triggers a costly flush, and where changing settings belong.

~7 min readadvancedupdated Oct 19, 2026

A production ServiceNow instance isn’t one server. Requests are spread across several application nodes, and each node keeps its own in-memory copies of things it would otherwise read from the database on every transaction: system property values, dictionary metadata, compiled scripts, ACL definitions and much more. That’s why the platform is fast, and it’s also why “I changed it, but some users still see the old behaviour” is a real support ticket.

Interview questions on this topic usually come in two forms. One is operational: “a change didn’t take effect, what do you check?” The other is design: “where would you store a setting that changes every hour?” Both have the same answer underneath, which is understanding what gets cached, how invalidation reaches every node, and what that invalidation costs.

What’s cached, and where

You don’t get a documented list of every server-side cache, and you shouldn’t need one. The useful mental model is three layers.

Platform metadata on each node. Table and dictionary definitions, system properties, script includes, business rules, ACLs, UI policies and similar records are read constantly, so nodes keep them in memory. When one of these records changes through the normal UI, the platform invalidates the cached copy and tells the other nodes in the cluster to do the same. Most of the time you never notice this happening.

Session state. A logged-in user’s session holds things like their roles and preferences. Changes that affect the user (a new role through a group, for example) are a common source of “it works for me but not for her”, and the usual first step is asking the user to log out and back in so a fresh session picks up the change.

The browser. Client-side assets such as UI scripts, style sheets and form layouts are cached by the browser and by the platform’s own client caching. A user with a stale tab can be running last week’s client script. A hard refresh, or simply a new tab after the change, rules that out.

When someone reports that a change hasn’t taken effect, walk through those layers in order instead of reaching for a flush straight away. Many “cache” problems are actually the change sitting in an update set that was never committed, or a different record (a per-table dictionary override, a second ACL) winning.

What a system property change costs

System properties are where people get burned, because they look like a cheap key-value store. They aren’t. ServiceNow’s documentation on adding a system property is direct about it: every time you add or change a property, the system flushes caches so that every node in the cluster stays consistent, and that flush has a high performance cost lasting roughly one to ten minutes. The same page advises against using properties for configuration that changes more than once or twice a month and suggests a custom table instead.

To see why a flush hurts, here’s a small Node model of three nodes, each with a warm cache of 2,000 metadata keys where a few keys are very hot. After warming up, an admin changes one value. I compared doing nothing, clearing every cache, and invalidating only the changed key, then counted database reads over the next 3,000 requests:

for (const n of nodes) {
  if (mode === 'full flush') n.cache.clear();
  else if (mode === 'targeted') n.cache.delete('key7');
}
JavaScript
no change: db reads in next 3000 requests = 92
full flush: db reads in next 3000 requests = 1773
targeted: db reads in next 3000 requests = 95
after change: app1=new app2=old
Text

Removing one key is almost free: 95 reads against a baseline of 92. Clearing everything makes nearly 60% of the next 3,000 requests go to the database while the caches refill. On a real instance the refill is spread over many more keys and users, which is where the minutes of slowness come from. The last line shows the other failure mode: a node that never receives the invalidation keeps serving the old value indefinitely. The platform’s cluster synchronisation exists to prevent exactly that, which is why you change configuration through supported paths rather than by editing data in ways that skip it.

The property form has an “Ignore cache” check box that changes how much gets flushed. Leave it cleared and the platform flushes server-side caches broadly so every cache sees the new value. Select it and only the sys_properties cache is cleared, which is cheaper, but it’s only correct when nothing else has cached that property’s value. The documentation recommends selecting it only for properties that change more often than monthly and live only in that table. In an interview, the better answer is usually not “tick Ignore cache” but “this shouldn’t be a property”.

Gotcha

A scheduled job that writes a timestamp into a system property every five minutes is a cache flush every five minutes. It’s one of the first things to look for when an instance has unexplained periodic slowdowns.

Designing settings that change often

Suppose a team wants a “maintenance banner” message the service desk can edit several times a day, and a rate limit for an integration that ops tune during incidents. Neither belongs in sys_properties.

Create a small custom table, say u_runtime_setting with u_key, u_value and u_active, protect it with ACLs and give the service desk a form. Read it through one script include so every caller goes through the same code:

// Script include: RuntimeSettings (illustrative; no instance was used to run it)
var RuntimeSettings = Class.create();
RuntimeSettings.prototype = {
  initialize: function () { this._memo = {}; },

  get: function (key, fallback) {
    if (key in this._memo) return this._memo[key];
    var gr = new GlideRecord('u_runtime_setting');
    gr.addQuery('u_key', key);
    gr.addQuery('u_active', true);
    gr.setLimit(1);
    gr.query();
    var value = gr.next() ? gr.getValue('u_value') : fallback;
    this._memo[key] = value;
    return value;
  },

  type: 'RuntimeSettings'
};
JavaScript

The _memo object lives only as long as the script include instance, which in practice means one transaction. That’s deliberate. Variables in server-side scripts don’t persist from one request to the next, so you can’t build a long-lived cache in your own code by stashing values in a global, and you wouldn’t want to: it would need its own cross-node invalidation. Memoising within a transaction removes repeated reads in one business rule chain, and an indexed single-row query per request is cheap. Editing a row in this table is an ordinary record update, so no cache flush happens at all.

Compare that with the property approach and the trade is clear. A property is right for values set at deployment and rarely touched, like an integration endpoint URL or a feature flag flipped per release. A table is right for anything humans or jobs change routinely.

Flushing by hand

Administrators can trigger a manual cache flush (the cache.do page is the familiar route). It has a place: after an unusual change that bypassed normal invalidation, during troubleshooting on a sub-production instance, or when ServiceNow support asks for it. It’s not a deployment step. Running it in production during business hours puts every node through the cold-cache refill shown above, and if the actual problem was an uncommitted update set or a stale browser tab, you’ve slowed everyone down and fixed nothing.

A reasonable order when a change seems not to apply: confirm the change exists in this instance and is the record that wins, open a new browser tab, have the affected user log in again, check whether the issue is specific to one node (if you can see which node served the request), and only then consider a flush, ideally out of hours.

Practice question: the property that changes every ten minutes

A developer stores the timestamp of the last successful import in a system property, updated by a scheduled job every ten minutes. Users have started reporting that forms freeze for a minute or two at irregular times. What’s going on, and what would you change?

Answer

Each update to a system property flushes server-side caches across the cluster, and the documentation warns the cost can last minutes. A job updating one every ten minutes means the instance spends a large share of each hour refilling caches, which matches short, recurring freezes. The irregularity comes from the job’s run time drifting and from users only noticing when they happen to be mid-action. I’d confirm by lining up the job’s run history against the slowness reports or slow transaction logs. The fix is to move the timestamp into a custom table (or onto the import set or job record itself) and read it with an ordinary query. I wouldn’t just select “Ignore cache” on the property, because that keeps a misused property in place and only helps if nothing else caches its value. After the change, I’d check that nothing else in the codebase writes properties on a schedule.

More in ServiceNow

read ✓ServiceNow · mid

ServiceNow GlideAggregate for Summary Queries

Count and summarise ServiceNow records in the database with GlideAggregate instead of looping over every row, and avoid its string-typed results.

~6 min readread →
read ✓ServiceNow · hard

ServiceNow ACL Scripting and Access

When an ACL really needs a script, how to write one that is cheap and null-safe on every row, and how to prove it works by testing as the user.

~7 min readread →
esc