Acumatica · Performance

Acumatica Performance — Diagnosing a Slow GI

Acumatica Performance — Diagnosing a Slow GI is the Acumatica performance topic that nobody asks about until they have to.

John Kihiu12 min read

A Generic Inquiry that took two seconds at UAT and takes forty seconds six months into production is the single most common performance ticket I get on established Acumatica instances, and it is almost never a GI designer bug — it is data growth exposing a query shape that was never going to scale, hiding behind a UI that made building it trivially easy.

Step one: get the actual SQL, not a guess

Every GI compiles down to a BQL query and ultimately a T-SQL statement. Guessing which join or condition is expensive wastes time; get the real query from SQL Server's Query Store or from a Profiler trace while running the GI, and work from that, not from reading the GI designer and imagining what it probably generates.

SQL — find the GI's query in Query Store by duration
SELECT TOP 20
    qt.query_sql_text,
    rs.avg_duration/1000.0 AS avg_duration_ms,
    rs.count_executions,
    rs.last_execution_time
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan p ON p.query_id = q.query_id
JOIN sys.query_store_runtime_stats rs ON rs.plan_id = p.plan_id
WHERE qt.query_sql_text LIKE '%ARInvoice%'  -- narrow to the GI's tables
ORDER BY rs.avg_duration DESC;

The four things I check, in order

The GI ran fine in UAT because UAT has 2,000 rows

Query plans that look identical between environments behave completely differently at different table sizes — a table scan on 2,000 rows is instant; the same scan on 4 million rows is not. Never sign off a GI's performance against a UAT or sandbox copy with a fraction of production's data volume. Restore a recent production backup to a test environment (or ask Acumatica support for one on SaaS) before calling a GI production-ready.

Fixing each cause

Missing index: add one covering the join/filter columns, respecting the existing convention of leading with CompanyID for multi-tenant tables — coordinate with SQL Server indexing practices covered in the indexing post in this series, and script custom indexes since upgrades can silently drop unmanaged ones. Fan-out: restructure the GI to aggregate the many-side before joining (a subquery relation or a supporting projection), or split into a master GI and a detail GI rather than one flattened result. Missing default filter: add one on the Parameters tab with a sane default value, not just an optional blank parameter — optional-but-blank still lets a user run the worst case by accident.

Wrapping up

Diagnose a slow GI from the actual SQL Server execution plan, not from staring at the GI designer, and always test against production-scale data before trusting the result. Index the real join/filter columns, catch join fan-out before it inflates the row count, and default every date-range parameter on large tables so nobody accidentally triggers a full-table sort.

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.