SaaS · Gi

Acumatica GI Concurrent Usage — Locking and Caching

Acumatica GI Concurrent Usage — Locking and Caching sits at the intersection of three forces: what the user wants to see, what the database can deliver, and what the platform will.

John Kihiu12 min read

A GI that runs fine for one user in testing and then times out or drags the whole app tier down once fifty people open it during month-end close is not a rare story — it is close to the default outcome for anything built without thinking about concurrency. Generic Inquiries do not have their own connection pool or caching layer; they run as ordinary queries against the shared application database, competing with every posting, every screen load, and every other GI in flight.

What actually contends under load

Three resources get shared across concurrent GI executions, and each fails differently:

Mitigations, roughly in order of effort

The cheapest fix is almost always pushing filters into the schema so the database does less work per execution — covered in depth elsewhere, but it matters double under concurrency because it shrinks both the query time and the lock footprint. Beyond that:

GI CONDITIONS — BOUND THE WORST CASE
GLTran.TranDate  Greater or Equal  =[@FromDate]  (default: first day of current period)
GLTran.TranDate  Less or Equal     =[@ToDate]    (default: today)

A default date range that bounds an unfiltered request matters more for concurrency than for any single user's experience — it caps how expensive the worst-case simultaneous execution can be, even if most users would have typed a narrower range anyway.

Read committed is not free of contention

Acumatica's default isolation generally avoids GI reads blocking on row locks the way old-school "read committed with locking" would, but heavy concurrent reads against tables under active write load still compete for the same buffer pool and I/O, and reporting-style full or large partial scans against GLTran/ARTran during a batch release are a realistic way to slow the release down. If a GI needs to run heavily during close, test it during a close rehearsal, not in isolation.

For genuinely popular dashboard-facing GIs, the real answer is often to stop querying live transactional tables at all — materialize a summary table via a scheduled processing screen or a snapshot job, and point the high-concurrency GI at that instead. This trades some staleness for taking the concurrency problem off the transactional tables entirely; see the realtime-vs-snapshot tradeoff for when that is worth it.

Move the expensive, infrequent case off-hours

Not every concurrency problem needs architecture. A GI that fifty people query interactively but that only needs to be current as of last night can be scheduled to export to a file or email at 6 a.m. via Automation Schedules, taking it out of the interactive concurrency picture entirely. I use this constantly for "give everyone a daily snapshot" style requirements — it is boring, and it works.

Wrapping up

Concurrent GI usage fails through shared database resources, not through anything specific to the GI engine itself: connection pool exhaustion, lock contention with posting processes, and app-tier rendering load. Bound the worst case with default filters, avoid querying hot transactional tables from high-traffic dashboards, and move genuinely expensive, staleness-tolerant reports to a schedule rather than fighting for concurrency headroom during month-end.

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.