Conversation
Co-authored-by: MSBrett <24294904+MSBrett@users.noreply.github.com>
Co-authored-by: MSBrett <24294904+MSBrett@users.noreply.github.com>
flanakin
left a comment
There was a problem hiding this comment.
🤖 [AI][Claude] PR Review
Summary: This documentation-only PR adds helpful mitigation guidance for Azure Resource Graph payload size limit errors. The expanded error documentation is a significant improvement. A couple of items to address before merging.
⚠️ Should fix (1)
- Changelog entries are placed under "Implementing FinOps guide v14" but these are Power BI report documentation changes. They should be in their own section (e.g., a new
### [Power BI reports](power-bi/reports.md) v14section or a standalone### Documentation improvementssection). Entries also missing issue link#1768.
💡 Suggestions (1)
- "small- and medium-sized" uses a suspended hyphen that's grammatically correct but unusual in Microsoft docs — consider "small and medium-sized" for readability.
There was a problem hiding this comment.
Pull request overview
This PR addresses Azure Resource Graph (ARG) response payload size limit failures affecting the Governance and Workload optimization Power BI reports by adding end-user troubleshooting guidance and introducing subscription-batched ARG querying in the shared dataset definitions.
Changes:
- Added
ftk_ARGBatchSize/ftk_QueryARGand updated multiple ARG-backed tables to query in subscription batches. - Documented the 16 MB ARG payload limit and mitigation steps in the error reference and report docs.
- Added a changelog entry under v14 for the Power BI report mitigation.
Reviewed changes
Copilot reviewed 30 out of 43 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/power-bi/storage/Shared.Dataset/definition/tables/VirtualMachines.tmdl | Storage dataset VirtualMachines definition updated/redirected to support subscription-batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/Subscriptions.tmdl | Storage dataset Subscriptions definition updated/redirected (supports new batching approach). |
| src/power-bi/storage/Shared.Dataset/definition/tables/SqlDatabases.tmdl | Storage dataset SqlDatabases definition updated/redirected to use batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/Resources.tmdl | Storage dataset Resources definition updated/redirected to use batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/PublicIPAddresses.tmdl | Storage dataset PublicIPAddresses definition updated/redirected to use batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/PolicyStates.tmdl | Storage dataset PolicyStates definition updated/redirected to support batching (subscriptionId derivation). |
| src/power-bi/storage/Shared.Dataset/definition/tables/PolicyAssignments.tmdl | Storage dataset PolicyAssignments definition updated/redirected to support batching (subscriptionId derivation). |
| src/power-bi/storage/Shared.Dataset/definition/tables/NetworkSecurityGroups.tmdl | Storage dataset NSG definition updated/redirected to use batched querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/NetworkInterfaces.tmdl | Storage dataset NIC definition updated/redirected to use batched querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/ManagementGroups.tmdl | Storage dataset ManagementGroups definition updated/redirected (context for governance reporting). |
| src/power-bi/storage/Shared.Dataset/definition/tables/Disks.tmdl | Storage dataset Disks definition updated/redirected to use batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/tables/AdvisorReservationRecommendations.tmdl | Storage dataset AdvisorReservationRecommendations updated/redirected (aligns with batching refactor). |
| src/power-bi/storage/Shared.Dataset/definition/tables/AdvisorRecommendations.tmdl | Storage dataset AdvisorRecommendations updated/redirected to use batched ARG querying. |
| src/power-bi/storage/Shared.Dataset/definition/expressions.tmdl | Adds ftk_ARGBatchSize and ftk_QueryARG batching helper functions (storage dataset). |
| src/power-bi/kql/Shared.Dataset/definition/tables/VirtualMachines.tmdl | KQL dataset VirtualMachines now uses ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/SqlDatabases.tmdl | KQL dataset SqlDatabases now uses ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/Resources.tmdl | KQL dataset Resources now uses ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/PublicIPAddresses.tmdl | KQL dataset PublicIPAddresses now uses ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/PolicyStates.tmdl | KQL dataset PolicyStates derives subscriptionId and uses ftk_QueryARG. |
| src/power-bi/kql/Shared.Dataset/definition/tables/PolicyAssignments.tmdl | KQL dataset PolicyAssignments derives subscriptionId and uses ftk_QueryARG. |
| src/power-bi/kql/Shared.Dataset/definition/tables/NetworkSecurityGroups.tmdl | KQL dataset NSGs now use ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/NetworkInterfaces.tmdl | KQL dataset NICs now use ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/Disks.tmdl | KQL dataset Disks now use ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/tables/AdvisorReservationRecommendations.tmdl | KQL dataset AdvisorReservationRecommendations updated and moved to batched querying. |
| src/power-bi/kql/Shared.Dataset/definition/tables/AdvisorRecommendations.tmdl | KQL dataset AdvisorRecommendations now uses ftk_QueryARG batching. |
| src/power-bi/kql/Shared.Dataset/definition/expressions.tmdl | Adds ftk_ARGBatchSize and ftk_QueryARG batching helper functions (KQL dataset). |
| docs-mslearn/toolkit/power-bi/workload-optimization.md | Adds a “Known limitations” section referencing ARG payload limits and ftk_ARGBatchSize. |
| docs-mslearn/toolkit/power-bi/governance.md | Adds a “Known limitations” section referencing ARG payload limits and ftk_ARGBatchSize. |
| docs-mslearn/toolkit/help/errors.md | Expands troubleshooting for “Response payload size exceeded…” with mitigation options and batching guidance. |
| docs-mslearn/toolkit/changelog.md | Adds v14 Power BI reports entry documenting the ARG batching mitigation. |
…-report-loading # Conflicts: # docs-mslearn/toolkit/changelog.md # docs-mslearn/toolkit/help/errors.md # docs-mslearn/toolkit/power-bi/governance.md # docs-mslearn/toolkit/power-bi/workload-optimization.md
…; restore RI-specific filter in AdvisorReservationRecommendations - Clamp batchSize to the documented default (100) when ftk_ARGBatchSize() returns null, non-numeric, non-integer, or a value less than 1 (avoids divide-by-zero and type errors) - Wrap the subscription-list ARG query in try/otherwise so transient failures, permission errors, or an unexpected result shape degrade to an empty batch list instead of throwing - Restore the reserved-instance-specific filter on properties.shortDescription.solution that was dropped when the query was refactored from bracket to dot notation, so AdvisorReservationRecommendations no longer duplicates all of AdvisorRecommendations Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The v15 Power BI reports changelog entry and ms.date bump were edited after staging during merge conflict resolution and got left out of the merge commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 43 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
src/power-bi/kql/Shared.Dataset/definition/expressions.tmdl:158
tostring(split(id, '/')[2])only yields a real subscription ID for IDs that start with/subscriptions/{guid}/.... For tenant- or management-group-scoped policy definitions, it produces incorrect values (for example,/providers/Microsoft.Authorization/...becomesMicrosoft.Authorization). Use an extract that returns null when the ID is not subscription-scoped.
subscriptionId = tostring(split(id, '/')[2]),
src/power-bi/storage/Shared.Dataset/definition/expressions.tmdl:565
tostring(split(id, '/')[2])only yields a real subscription ID for IDs that start with/subscriptions/{guid}/.... For tenant- or management-group-scoped policy definitions, it produces incorrect values (for example,/providers/Microsoft.Authorization/...becomesMicrosoft.Authorization). Use an extract that returns null when the ID is not subscription-scoped.
subscriptionId = tostring(split(id, '/')[2]),
src/power-bi/kql/Shared.Dataset/definition/tables/PolicyAssignments.tmdl:372
tostring(split(id, '/')[2])yields incorrect values for non-subscription-scoped IDs (tenant or management group scope). Use an extract that only returns a subscription ID when the resource ID contains/subscriptions/{guid}/...so you don't populate bogus IDs likeMicrosoft.Authorization.
| extend subscriptionId = tostring(split(id, '/')[2])
src/power-bi/kql/Shared.Dataset/definition/tables/PolicyStates.tmdl:347
tostring(split(id, '/')[2])yields incorrect values for non-subscription-scoped IDs (tenant or management group scope). Use an extract that only returns a subscription ID when the resource ID contains/subscriptions/{guid}/...so you don't populate bogus IDs likeMicrosoft.PolicyInsights.
| extend subscriptionId = tostring(split(id, '/')[2])
Confirmed as a real bug in code this PR introduced (this One residual limitation this fix does not solve, disclosed for transparency: |
…urces Copilot's automated review flagged that tostring(split(id, '/')[2]) produces misleading values (e.g. Microsoft.Authorization, Microsoft.PolicyInsights) instead of null/empty for tenant- or management-group-scoped policy definitions, assignments, and states. This subscriptionId derivation was added by this PR to support ARG subscription batching. Guard the split so non-subscription-scoped IDs now yield an empty value instead of a bogus one, in both kql and storage copies of expressions.tmdl and in PolicyAssignments.tmdl / PolicyStates.tmdl (storage copies are symlinks to the kql originals). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
MSBrett
left a comment
There was a problem hiding this comment.
- High — subscription batching drops built-in and management-group Azure Policy data
PolicyDefinitions and PolicyAssignments derive subscriptionId from split(id, '/')[2], then call ftk_QueryARG, which appends where subscriptionId in ('<subscription GUIDs>'). Built-in definition IDs use /providers/Microsoft.Authorization/policyDefinitions/<id>, so the derived value is Microsoft.Authorization; management-group IDs similarly derive Microsoft.Management. Neither can match a subscription GUID, so those rows are removed. Keep tenant/management-group records outside subscription batching and union them with batched subscription-scope records, or apply the filter only to subscription-scoped IDs. Add regression coverage for built-in, management-group, and subscription IDs in both dataset copies.
- Medium — the reported NSG payload case remains insufficiently bounded and unproven
The issue specifically identifies the NSG query and its mv-expand properties.securityRules as exceeding 16 MB, but NetworkSecurityGroups.tmdl retains that expansion and uses ftk_QueryARG(query, 20). The documented default therefore creates 2,000-subscription batches, and the documented integer minimum of 1 still produces 20 subscriptions. No measurement or validation demonstrates that this stays below ARG’s 16 MB limit. Remove or reduce the multiplier, or expose a true per-table batch setting, and validate the selected bound against a large NSG-rule tenant.
- Medium — required validation evidence for the core M change is absent
There is no Power BI test, .tmdl/M CI validation, or reported manual refresh for the new batching behavior. The PR only reports an M-spec trace, while the repository’s build-and-test guidance says Power BI reports require manual build/verification and that manual verification is always expected. Record a Power BI Desktop refresh against a representative large tenant, including NSG expansion and policy scopes, and add a deterministic boundary check for batching and ID scoping if automated Power BI validation remains unavailable.
… size - ftk_QueryARG: add optional includeUnscoped param that unions in one unbatched Azure Resource Graph query for rows whose derived subscriptionId is blank (tenant- and management-group-scoped policy resources), since the per-batch "subscriptionId in (...)" filter can never match them and they were being silently dropped from every batch. - PolicyDefinitions (kql + storage), PolicyAssignments, PolicyStates: opt into includeUnscoped so built-in policy definitions and management-group-scoped assignments/states are no longer missing from the Governance report. - NetworkSecurityGroups: remove the 20x batch multiplier (2000 subs/batch). It was the largest multiplier of any table despite this table's mv-expand of security rules being the highest per-row payload expansion in the dataset and the original motivating case for ARG batching (changelog #1540). Falls back to the 100-subs/batch default. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Thanks for the detailed review, @MSBrett. Addressed in 1. High: subscription batching drops built-in and management-group policy data Confirmed and fixed. I did not add a union of every possible non-subscription scope pattern beyond "blank subscriptionId" - the fix relies on the existing 2. Medium: NSG payload case insufficiently bounded Agreed the I could not validate this bound against a live large tenant. This sandboxed environment has no Azure Resource Graph access, no Power BI Desktop, and (confirmed by checking 3. Medium: validation evidence for the core M change is absent This is a fair gap I can't close from here for the same reason as #2 - there's no automated Power BI/M test harness in this repo ( |
flanakin
left a comment
There was a problem hiding this comment.
🤖 [AI][Claude] PR Review
Summary: The batching approach is sound and the hardening from earlier review rounds (batch-size validation, try...otherwise on the subscription list, restored RI filter) is solid. Three items need attention before merge — most importantly registering the two new functions in the Power BI build script, and the direction of the batchMultiplier argument. A few items are confirmation requests rather than defects.
🚫 Blockers (3)
Build-PowerBI.ps1needsftk_QueryARGandftk_ARGBatchSizeadded to the Governance and WorkloadOptimizationExpressionslists so they survive the build filter.batchMultiplierscales batches up rather than down, so the heaviest tables issue the largest requests.- Confirm the storage
.tmdl→ symlink conversion is intentional and works on Windows + the build scripts.
⚠️ Should fix (3)
AdvisorRecommendationschangedresultTruncatedfromtruetofalse.- In-query
subscriptionIdfiltering trims results but doesn't reduce Resource Graph's scan scope. - Confirm the
AdvisorReservationRecommendationscolumn/typing additions are intended to ship in this PR.
💡 Suggestions (2)
- Reference the default batch size instead of duplicating the
100literal. - Apply the same
try...otherwiseguard to theUnscopedSourcequery.
…entries, batch size inversion, and Advisor sort/type regressions - Revert 13 storage/*.tmdl symlinks to real duplicated files; git checks out symlinks as literal path-text on Windows without core.symlinks=true, which corrupted local builds and Power BI Desktop sessions for most Windows contributors. - Add ftk_QueryARG and ftk_ARGBatchSize to the Governance and WorkloadOptimization Expressions allowlists in Build-PowerBI.ps1; both were being stripped from the shipped .pbit even though ~10 tables call ftk_QueryARG, which would break refresh in the built report. - Reduce batchMultiplier on VirtualMachines (3 mv-expand blocks, widest row) from 5 to none, and on AdvisorRecommendations, AdvisorReservationRecommendations, and SqlDatabases from 5 to 2, so batch size roughly tracks per-row payload weight instead of being inverted (heaviest tables previously got the largest batches). - Restore the lookbackPeriod Int64.Type transform in AdvisorReservationRecommendations that this PR had dropped. - Re-sort AdvisorRecommendations/AdvisorReservationRecommendations by SortOrder after Table.Combine, since combining per-batch results only preserves sort order within each batch, not globally. - Wrap ftk_QueryARG's UnscopedSource query in try...otherwise so a transient ARG failure on the optional unscoped-rows query degrades to batched-only results instead of failing the whole table. - Extract defaultBatchSize as a named constant and add a comment explaining the in-query subscription filter vs request-level scope tradeoff. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
RolandKrummenacher
left a comment
There was a problem hiding this comment.
Overview
The batching approach is sound and ftk_QueryARG is correctly written M. My concerns are about failure modes, request volume, and scope rather than the core idea.
I've left inline comments on the specific lines. Summary of what I think blocks merge:
- A failed subscription enumeration silently produces an empty report rather than an error — strictly worse than the error it replaces.
- ~6x more ARG requests with no retry, in exactly the large tenants this targets. Trades a deterministic 16 MB error for a nondeterministic 429.
- Resource-group-scoped readers lose data, because they can't see the subscription containers the batch list is built from.
And a scope request: this is at least three PRs. Beyond batching it rewrites AdvisorReservationRecommendations' schema (+16 columns, lookbackPeriod string→int64, coalesce(…,0) on savings amounts, a new recommendedActions fallback), switches properties['category'] to properties.category, converges the entire storage dataset onto the kql dataset's content, and churns several hundred lineageTag GUIDs. Those may all be wanted, but they're unreviewable next to the batching change and will make bisecting a future refresh regression painful. I'd like the AdvisorReservationRecommendations schema work split out.
Note that every finding on src/power-bi/kql/.../expressions.tmdl applies identically to the duplicated copy in src/power-bi/storage/.../expressions.tmdl — I only commented once per issue to keep this readable.
Verified as fine
So these don't come up again: changelog placement is correct (v15 is still 15.0.0-dev.0; the "Released June 2026" header is a placeholder). The errors.md anchor resolves. Every batched query either uses extend-only against resources/advisorresources/policyResources — which carry subscriptionId natively — or explicitly projects it, so the appended filter always resolves; I checked all twelve. The broken symlinks flagged earlier are gone. ManagementGroups correctly stays unbatched. {0..-1} is a valid empty range in M, so the zero-subscription path doesn't throw.
Testing
@MSBrett's note that none of this ran against a live tenant is the crux. Pester passes but never touches these files, and findings 1–3 are all runtime-only failure modes that no static check would catch. A manual refresh against a genuinely multi-batch tenant (>100 subscriptions, ideally >500) is the real gate here — specifically watching for 429s during concurrent partition refresh, and confirming the tables aren't silently empty.
| requestedBatchSize = try ftk_ARGBatchSize() * (if batchMultiplier <> null then batchMultiplier else 1) otherwise null, | ||
| batchSize = if requestedBatchSize = null or not Value.Is(requestedBatchSize, type number) or requestedBatchSize < 1 then defaultBatchSize else Number.RoundDown(requestedBatchSize), | ||
| subQuery = "resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionId" & ftk_DemoFilter(), | ||
| Subscriptions = try AzureResourceGraph.Query(subQuery, "Tenant", null, null, [resultTruncated = false]) otherwise null, |
There was a problem hiding this comment.
Blocking — this silently turns a failure into an empty report.
Trace the failure path: try … otherwise null → SubscriptionIds = {} (line 110) → BatchCount = 0 → Batches = {} → Filtered = {} → Combined = null (line 127) → every caller's NullHandling hits Source = null and returns the empty #table.
So an auth failure, a throttle, or a transient ARG outage on the subscription list query yields "0 rows, refresh succeeded" across all twelve batched tables. The user gets a blank Governance report with a green checkmark and no indication anything went wrong. Before this change, the error surfaced.
This is worse than the payload error the PR fixes — that one at least told you what happened. Suggest not swallowing here: either let the query throw, or distinguish "tenant genuinely has no readable subscriptions" from "enumeration failed" and raise explicitly with error Error.Record(…).
| // filter within the ARG query itself (rather than the request-level "subscriptions" scope parameter) so batching logic stays entirely in M and behaves the same for every caller regardless of query shape | ||
| subFilter = " | where subscriptionId in (" & Text.Combine(List.Transform(batchIds, each "'" & _ & "'"), ",") & ")", | ||
| fullQuery = query & subFilter, | ||
| Source = AzureResourceGraph.Query(fullQuery, "Tenant", null, null, [resultTruncated = false]) |
There was a problem hiding this comment.
Blocking — request amplification with no retry.
This call has no try and no backoff, so the first 429 fails the entire table.
Request count per table is now 1 + ceil(N / batchSize). For a 1,000-subscription tenant across the twelve adopting tables that's roughly 84 ARG calls versus 14 today:
- 1x multiplier (10 batches):
NetworkSecurityGroups,Resources,VirtualMachines→ 33 - 2x (5 batches):
AdvisorRecommendations,AdvisorReservationRecommendations,Disks,NetworkInterfaces,SqlDatabases,PolicyAssignments,PolicyStates→ 44 (incl. 2 unscoped) - 5x (2 batches):
PublicIPAddresses,PolicyDefinitions→ 7 (incl. 1 unscoped)
ARG's quota is 15 requests per 5 seconds per user, and Power BI evaluates partitions concurrently. This will throttle. The PR body lists it as a known limitation, but I don't think it can be deferred — large tenants are the entire target population, so we'd be swapping a deterministic 16 MB error for a nondeterministic throttling error for the same users.
Two things would address it: a bounded retry with backoff around this call (Function.InvokeAfter), and hoisting the subscription list into one shared expression instead of re-querying it once per table (that alone removes 12 of the 84).
| defaultBatchSize = 100, | ||
| requestedBatchSize = try ftk_ARGBatchSize() * (if batchMultiplier <> null then batchMultiplier else 1) otherwise null, | ||
| batchSize = if requestedBatchSize = null or not Value.Is(requestedBatchSize, type number) or requestedBatchSize < 1 then defaultBatchSize else Number.RoundDown(requestedBatchSize), | ||
| subQuery = "resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionId" & ftk_DemoFilter(), |
There was a problem hiding this comment.
Blocking — resource-group-scoped readers silently lose data.
The batch list is derived from resourcecontainers, but the queries it filters run against resources. Those have different RBAC surfaces: a principal with Reader at resource group scope can read the resources rows but cannot see the subscription container. Those subscriptions never enter the batch list, so their resources are dropped with no error.
The old unfiltered tenant-scope query returned them. RG-scoped Reader is a common delegation pattern, so this isn't hypothetical. At minimum it needs a documented caveat in the "Known limitations" sections this PR adds; better would be a fallback when the derived subscription list looks suspiciously narrow.
| if Table.HasColumns(Source, "Results") and Table.RowCount(Source) = 1 then null else Source | ||
| ), | ||
| // some callers (for example tenant/management-group-scoped policy resources) can have a blank subscriptionId that never matches any subscription batch filter above; fetch those rows once, unbatched, when requested | ||
| UnscopedSource = if includeUnscoped = true then (try AzureResourceGraph.Query(query & " | where isempty(subscriptionId)", "Tenant", null, null, [resultTruncated = false]) otherwise null) else null, |
There was a problem hiding this comment.
This branch bypasses ftk_DemoFilter().
The batched path gets demo scoping implicitly — the filter constrains subQuery on line 108, so the batch IDs are already limited to the seven demo subscriptions. But this unscoped query appends only | where isempty(subscriptionId) to the raw caller query, with no demo filter.
Result: in the demo datasets, PolicyDefinitions, PolicyAssignments and PolicyStates will pull every tenant- and MG-scoped policy row from the demo tenant instead of the curated scope. Worth appending ftk_DemoFilter()'s intent here too, or explicitly deciding that unscoped rows are always in-scope for demos.
| let | ||
| defaultBatchSize = 100, | ||
| requestedBatchSize = try ftk_ARGBatchSize() * (if batchMultiplier <> null then batchMultiplier else 1) otherwise null, | ||
| batchSize = if requestedBatchSize = null or not Value.Is(requestedBatchSize, type number) or requestedBatchSize < 1 then defaultBatchSize else Number.RoundDown(requestedBatchSize), |
There was a problem hiding this comment.
not Value.Is(requestedBatchSize, type number) is dead code. requestedBatchSize is the result of a * b evaluated inside try … otherwise null on the previous line — multiplication in M either yields a number or errors, and the error is already caught. So it's a number or null, never another type, and this clause can never be true.
Same pattern on line 110 with Value.Is(Subscriptions, type table): AzureResourceGraph.Query returns a table or errors, and the error is caught by the try.
Harmless, but it reads as defensive coverage that isn't actually covering anything.
| recommendedActions = strcat(properties.extendedProperties.displaySKU, ' virtual machines in ', properties.extendedProperties.location), | ||
| recommendedActions = iff(isnotempty(properties.extendedProperties.displaySKU), strcat(properties.extendedProperties.displaySKU, ' virtual machines in ', properties.extendedProperties.location), tostring(properties.shortDescription.solution)), | ||
| SortOrder = case(properties.impact == 'High', 1, properties.impact == 'Medium', 2, properties.impact == 'Low', 3, 9) | ||
| | order by SortOrder asc |
There was a problem hiding this comment.
This order by should go now. ftk_QueryARG appends the batch filter after it, so each batch sorts its full result set and then filters — wasted work per batch — and the ordering is discarded anyway when Table.Combine concatenates, which is why the new Table.Sort step exists below.
Same applies to the order by in AdvisorRecommendations.
|
|
||
| " & ftk_DemoFilter(), "Tenant", null, null, [resultTruncated = true]), | ||
| ", | ||
| Source = ftk_QueryARG(query, 2), |
There was a problem hiding this comment.
Unflagged behavior change: this table previously ran with [resultTruncated = true], and ftk_QueryARG hardcodes false. So the one table that was explicitly protected by truncation now returns complete results.
That's probably the outcome you want, but it isn't in the changelog or the PR description, and it cuts against the PR's goal — it increases per-batch payload for the table that had a payload guard. Worth calling out explicitly so it's a decision rather than a side effect.
| > _OLE DB or ODBC error: [Expression.Error] Please provide below info when asking for support: timestamp = {timestamp}, correlationId = {guid}. Details: Response payload size is {number}, and has exceeded the limit of 16777216. Please consider querying less data at a time and make paginated call if needed._ | ||
|
|
||
| This error means that you have more resources than are supported in an unfiltered Resource Graph query. This happens because FinOps toolkit reports are designed to show resource-level details and are not aggregated. They are designed for small- and medium-sized environments and not designed to support organizations with millions of resources. | ||
| Azure Resource Graph enforces a 16 MB response payload limit per query. FinOps toolkit reports automatically paginate queries in batches of subscriptions (default: 100 per batch) to stay within this limit, so most environments should not encounter this error. If you still see it, it means the resources in a single batch of subscriptions exceed the 16 MB limit. |
There was a problem hiding this comment.
"default: 100 per batch" is only true for three of the twelve tables. The effective batch is ftk_ARGBatchSize() × batchMultiplier — 200 for most tables and 500 for PublicIPAddresses and PolicyDefinitions.
So a user who follows Option 1 below and drops the value from 100 to 20 still gets 100-subscription batches on those two tables and 40 on most others — not the 20 the docs imply. Either document the multiplier or state the effective range.
Related: nothing in the code or docs records why each table got 1x, 2x or 5x. Given NSG already had to be walked back from 20x during review, it'd help to write down the reasoning (or drop the multiplier entirely and let one number mean one thing).
|
|
||
| ## Related content | ||
|
|
||
| Related resources: |
There was a problem hiding this comment.
This restructure is unrelated to the fix — ## See also was removed and its two links relocated here under a new "Related resources:" list. governance.md keeps its original structure, so the two report docs now diverge.
Suggest reverting unless @flanakin specifically asked for it; it adds review surface to an already-large PR.
| requestedBatchSize = try ftk_ARGBatchSize() * (if batchMultiplier <> null then batchMultiplier else 1) otherwise null, | ||
| batchSize = if requestedBatchSize = null or not Value.Is(requestedBatchSize, type number) or requestedBatchSize < 1 then defaultBatchSize else Number.RoundDown(requestedBatchSize), | ||
| subQuery = "resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionId" & ftk_DemoFilter(), | ||
| Subscriptions = try AzureResourceGraph.Query(subQuery, "Tenant", null, null, [resultTruncated = false]) otherwise null, |
There was a problem hiding this comment.
Flagging for tracking: ftk_QueryARG is duplicated verbatim between the kql and storage datasets, so every finding I left on src/power-bi/kql/.../expressions.tmdl applies here too — the silent-empty-result path, the missing retry, the resourcecontainers RBAC gap, the includeUnscoped demo-filter bypass, the dead Value.Is guards, and the Table.Combine shape assumption.
Not asking to deduplicate in this PR (the two datasets are separate by design), just make sure fixes land in both copies.
|
@@copilot: you have some new feedback! Please review and resolve all comments and I'll let reviewers know by removing the |
Problem
Users with large Azure environments are encountering payload size limit errors when attempting to load the Governance and Workload Optimization Power BI reports. The error message appears as:
This occurs because both reports use Azure Resource Graph queries with
[resultTruncated = false]to retrieve comprehensive resource details. For organizations with millions of resources, the response payload exceeds Azure Resource Graph's 16 MB limit, preventing the reports from loading.Root Cause
Azure Resource Graph enforces a 16 MB (16,777,216 bytes) response payload limit per query. The Governance and Workload Optimization reports are designed to show detailed resource-level information and are optimized for small to medium-sized environments. When used in large enterprise environments with millions of resources, the unfiltered queries exceed this limit.
Solution
This PR includes both a functional fix and documentation. The functional fix paginates Azure Resource Graph queries by subscription so a single query is split into multiple smaller batches that each stay under the 16 MB limit, then combines the results. The documentation gives users manual mitigation options (filtering by subscription/tags, trimming columns) for cases where the default batching still isn't enough, or where they want more control.
Functional changes
ftk_QueryARGandftk_ARGBatchSize(inShared.Dataset/definition/expressions.tmdl, both thekqlandstoragedatasets) — a shared M function that queries Resource Graph for the tenant's subscriptions, splits them into batches (default 100 subscriptions per batch, user-configurable viaftk_ARGBatchSize), runs the caller's query once per batch with asubscriptionId in (...)filter, and combines all batch results.ftk_QueryARGacross the Resource Graph-backed tables used by the Governance and Workload Optimization reports (Advisor recommendations, disks, network interfaces/security groups, policy assignments/states, public IPs, resources, SQL databases, virtual machines).ftk_QueryARGagainst invalidftk_ARGBatchSize()values (null, non-numeric, non-integer, or less than 1 — falls back to the documented default of 100) and against the subscription-list Resource Graph query failing or returning an unexpected shape (falls back to an empty batch list instead of throwing).Documentation changes
1. Enhanced Error Documentation (
help/errors.md)Expanded the existing "Response payload size exceeded" error section with detailed, step-by-step mitigation strategies for cases where the built-in batching isn't sufficient:
Option 1: Filter by Subscription
Option 2: Filter by Tags
Option 3: Remove Unnecessary Columns
Option 4: Disable Problematic Queries
Each option includes step-by-step instructions, KQL code examples, and links to official Microsoft documentation.
2. Updated Report Documentation
Added "Known limitations" sections to both affected reports (
power-bi/governance.md,power-bi/workload-optimization.md) explaining the 16 MB Azure Resource Graph payload limit and linking to the error reference guide.3. Changelog Entry
Added an entry to the
v15Power BI reports section ofchangelog.md(the next unreleased version).Impact
References
Testing
devand resolved conflicts, restoring a review-flagged regression (a dropped reserved-instance-specific filter inAdvisorReservationRecommendations) in the processKnown limitations / follow-ups
ftk_ARGBatchSize) is user-configurable but has no upper cap; an extremely large custom value could still hit the 16 MB limit in very large tenants.Fixes: #1768
Original prompt
Fixes #1768
💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.