SaaS · Performance

Acumatica Performance — Redis Caching Strategy

Acumatica Performance — Redis Caching Strategy is the Acumatica performance topic that nobody asks about until they have to.

John Kihiu12 min read

Redis comes up in Acumatica performance conversations for a specific, narrow reason: PXCache is per-graph-instance and per-request, not a shared distributed cache across app servers, so anyone running a multi-server deployment who wants a value computed once and reused across servers — a rate limit counter, an external API response cache, a computed lookup table refreshed hourly — has nowhere native to put it. Acumatica ships no built-in Redis integration; this is entirely custom code you write and operate.

The actual problem Redis solves in this stack

PXCache solves "don't re-query the same row twice within one request." It does not solve "don't call this slow external API from every app server independently" or "share a computed value across all three app servers without hitting SQL Server every time." Those are the legitimate reasons to add Redis: a shared, fast, cross-process cache that outlives a single request and is visible to every app server in the farm.

What is worth putting in Redis, and what is not

The pattern I actually implement

Cache-aside, from custom graph or webhook-handler code, using StackExchange.Redis as the client — nothing exotic:

C# — cache-aside for an external rate lookup
public static class ExchangeRateCache
{
    private static readonly ConnectionMultiplexer _redis =
        ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisConnection"]);

    public static decimal GetRate(string fromCcy, string toCcy)
    {
        var db = _redis.GetDatabase();
        var key = $"fx:{fromCcy}:{toCcy}";
        var cached = db.StringGet(key);
        if (cached.HasValue)
            return decimal.Parse(cached);

        var rate = ExternalFxProvider.FetchRate(fromCcy, toCcy); // slow call
        db.StringSet(key, rate.ToString(), TimeSpan.FromMinutes(15));
        return rate;
    }
}

Note the static ConnectionMultiplexer — same principle as the HttpClient guidance in the memory-leak post: create one connection multiplexer per process and reuse it, never one per call, since it is designed to be a long-lived, thread-safe, multiplexed connection.

Redis is one more thing that can be down

Wrap every Redis call in a try/catch that falls through to the live data source on failure. A Redis outage should degrade your integration to "always fetches live, a bit slower" — never to "throws an exception and blocks the user's transaction." I have seen a poorly wrapped Redis dependency turn a genuinely optional performance optimization into a hard outage dependency, which defeats the entire point of adding a cache in the first place.

TTL beats explicit invalidation for this use case

For the external-lookup and scheduled-aggregate use cases above, a short TTL (minutes, not hours) is simpler and safer than building explicit cache invalidation hooks into every place the underlying data changes. Explicit invalidation is worth the complexity only when staleness has real business cost and the write paths are few and well-known — for most Acumatica-adjacent caching needs, a TTL that is short enough to bound staleness to an acceptable window is the pragmatic choice.

Wrapping up

Redis has no native place in Acumatica — it is infrastructure you add for a specific, narrow job: sharing expensive, tolerably-stale computed or externally-fetched values across app servers. Never cache genuinely transactional data in it, reuse one connection multiplexer per process, and always fail open to the live data source if Redis itself is unavailable.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.