Cutting CRM Analytics Query Consumption Before You Hit the Limit
If you have landed here, something is probably already wrong. Maybe a dashboard started timing out. Maybe your Salesforce AE forwarded you a usage report and the query numbers look alarming. Maybe you are just doing the math and realizing a new embedded-dashboard rollout will chew through your allocation faster than expected.
CRM Analytics, known at various points as Wave Analytics, Einstein Analytics, Tableau CRM, and now CRM Analytics (CRMA), tracks every query that executes against a dataset. That count matters whether you are watching a per-org limit, managing a shared allocation across a large deployment, or trying to keep page-load times tolerable for end users. The platform does not volunteer much visibility into where that count comes from. You have to go find it.
This article walks through where the query load actually originates, which problems are worth fixing first, and roughly what the fix involves.
Why the count is higher than you expect
Every widget on a CRMA dashboard is wired to one or more steps. A step is the unit of query execution: it issues a SAQL query against a dataset and returns results. When a user opens a dashboard page, every visible step fires. When a user changes a filter or clicks a selector widget, any step configured to respond to that interaction fires again.
That last part is the trap. A standard CRMA dashboard has faceting turned on by default, meaning a change to almost any widget can cascade to every other widget on the page. On a page with 40 steps, one filter click can produce 39 additional queries. Multiply that by your user count and your numbers get large quickly.
The other thing that surprises people is how many steps accumulate in a mature dashboard. Development tends to go like this: someone adds a KPI card, then wants a second version with slightly different filters, then a third for a different region cut. Three steps are born where one was possible. This happens across every page, across every team that has editor access, and nobody ever cleans it up because the dashboard "works."
Where to start the audit
Open the dashboard JSON. Every CRMA dashboard has one: it is the state file that describes every step, every widget, every binding. You can export it from Analytics Studio. It is not especially readable, but it is complete.
The first thing to look for is dead steps: steps that exist in the JSON but are not referenced by any widget, any binding, or any layout entry anywhere in the file. They are pure waste. They will not appear on screen, but they can still execute depending on how the dashboard initializes. More importantly, they are invisible to anyone working in the visual editor, so they accumulate unnoticed.
The second thing to look for is no-op filters: filter statements that evaluate to true for every row (a predicate like 1 == 1, or a case-when expression that returns the same value regardless of which branch executes). These are usually artifacts of copy-paste development or left-over scaffolding from a feature that was partially removed.
Third, look for broadcastFacet on display-only steps. The broadcastFacet property controls whether a step broadcasts facet changes to other steps. For a number widget or a text binding that only consumes data and never drives a selection, broadcastFacet: false is the right setting. Leaving it on means that widget participates in the cascade re-query every time any filter changes anywhere on the page, even though it cannot itself cause a filter change. This is one of the most common sources of unnecessary query volume in large dashboards.
The step-family merge
After the mechanical cleanup above, the next level of reduction is identifying step families: groups of steps that are structurally nearly identical except for which measure they compute. A common pattern is a set of KPI steps that all query the same dataset, apply identical filters, group by the same dimensions, but each compute a different aggregate (current pipeline, change vs. prior period, win rate, and so on). If those filter blocks are byte-for-byte identical, all of those steps can be collapsed into a single step that computes all measures at once, then each widget reads a different column out of the single result row.
This requires changing the step's SAQL to return multiple measure columns and then updating each consuming widget to point to the right column via its measureField property. It also requires updating any {{cell(...)}} binding expressions in text widgets or conditional-formatting bindings. It is not hard work per step, but it is meticulous: every reference to every removed step has to be found and redirected. Miss one and the widget shows a broken value or a reference error in production.
One real-world result from this two-round approach (mechanical cleanup first, then step-family merges): a 7-page dashboard went from 2063 KB of JSON down to 966 KB, a 53% reduction, and page-one query load dropped from 64 steps to 40, a 37% reduction. The rendered output was visually identical before and after. Those numbers are achievable on a dashboard that has seen typical growth without systematic maintenance.
A note on the merge precondition
Not every near-duplicate family is safe to merge. The precondition is that the filter blocks across the family members must be identical. If two steps look similar but one filters to a specific category value and the other does not, they are not a merge candidate. Merging them requires a pre-group projection step in the SAQL, and that pattern has a failure mode where a field referenced after the group-by is no longer in scope. The CRMA developer edition org tends to be more lenient about malformed SAQL than production orgs: a query that renders cleanly in dev can fail with an "undefined identifier" error in production on the exact same data shape. Static analysis of the query structure is the only reliable check.
Recipe-layer query costs
Dashboard query counts are the most visible problem, but Recipe runs contribute to the overall platform load separately. A nightly full-refresh Recipe that reprocesses an entire dataset when only a subset of records changed is the most common example. Using incremental-mode digests on sfdcDigest nodes, scoped to records modified after a known watermark, is the standard fix. The operational cost of Recipe runs versus dashboard queries flows through different meters depending on your contract, but both matter if you are hitting limits or watching for cost growth.
What makes this genuinely annoying to fix
The diagnosis is not the hard part. Once you know what to look for, reading the JSON and identifying the problems takes a few hours on a complex dashboard.
The hard part is the remediation without breaking things. Every widget binding is an implicit contract between a step name, a column alias, and a widget property. When you remove or rename a step, every downstream reference breaks silently in the JSON editor. The visual editor does not warn you. The dashboard might still load and show something, just not the right thing.
Production-org SAQL strictness, as noted above, also means a fix that looked good in dev needs an independent structural review before it goes to users. The check is not "does it render," it is "does the SAQL structure hold up under the stricter parse rules."
For a team that does not live in CRMA dashboard JSON daily, this work tends to either not get done or get done once and break something. Both outcomes are common.
If you want to understand the scope of the problem on a specific dashboard before committing time to it, CRMA Labs offers a $249 teardown on crmalabs.com. You export the dashboard JSON from Analytics Studio (no org access needed on our end), we do a step-level audit of dead steps, no-op filters, broadcastFacet candidates, and merge-eligible families, and you get back a prioritized fix list with expected query reduction estimates within 48 hours. No commitment to further work required.