Skip to content

Commit 172880e

Browse files
authored
Merge pull request #311472 from daphnemamsft/daphneMa_arg-paging-res
Adding new paging results page
2 parents 9260a40 + 3e40c0c commit 172880e

2 files changed

Lines changed: 315 additions & 0 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
---
2+
title: Guidance for pagination
3+
description: Learn about paging results and pagination limitations.
4+
ms.date: 02/06/2026
5+
ms.topic: reference
6+
ms.custom: devx-track-csharp
7+
---
8+
9+
# Guidance for pagination
10+
11+
When it's necessary to break a result set into smaller sets of records for processing or because a result set would exceed the maximum allowed value of _1000_ returned records, use paging. The [REST API](/rest/api/azureresourcegraph/resourcegraph(2021-03-01)/resources/resources) `QueryResponse` provides values that indicate a results set was broken up: `resultTruncated` and `$skipToken`. `resultTruncated` is a Boolean value that informs the consumer if there are more records not returned in the response. This condition can also be identified when the `count` property is less than the `totalRecords` property. `totalRecords` defines how many records that match the query.
12+
13+
`resultTruncated` is `true` when there are less resources available than a query is requesting or when paging is disabled or when paging isn't possible because:
14+
15+
- The query contains a `limit` or `sample`/`take` operator.
16+
- All output columns are either `dynamic` or `null` type.
17+
18+
When `resultTruncated` is `true`, the `$skipToken` property isn't set.
19+
20+
The following examples show how to skip the first 3,000 records and return the `first` 1,000 records after those records skipped with Azure CLI and Azure PowerShell:
21+
22+
```azurecli
23+
az graph query -q "Resources | project id, name | order by id asc" --first 1000 --skip 3000
24+
```
25+
26+
```azurepowershell
27+
Search-AzGraph -Query "Resources | project id, name | order by id asc" -First 1000 -Skip 3000
28+
```
29+
30+
> [!IMPORTANT]
31+
> The response won't contain `$skipToken` if:
32+
> - The query contains a `limit` or `sample`/`take` operator.
33+
> - All output columns are either `dynamic` or `null` type.
34+
35+
For an example, go to [Next page query](/rest/api/azureresourcegraph/resourcegraph(2021-03-01)/resources/resources#next-page-query) in the REST API docs.
36+
37+
## Pagination limitations
38+
39+
Azure Resource Graph provides powerful capabilities for querying resources across your Azure environment. When working with large result sets that require pagination, understanding how pagination behaves in different scenarios helps you retrieve consistent and complete results.
40+
41+
This article explains pagination considerations and provides strategies for scenarios where you might observe duplicate or missing records in your paginated results.
42+
43+
### Pagination limitations scenario: Sorting by non-unique columns
44+
45+
When paginating results sorted by a non-unique column, you might encounter duplicate or missing records even in static environments where resources aren't changing. This occurs because records with identical sort values have no guaranteed order, and their positions can shift between pagination calls.
46+
47+
> [!NOTE]
48+
> When using `skip or first`, it's recommended to order results by at least one column with asc or `desc`. Without sorting, results are random and not repeatable.
49+
50+
#### Why this scenario happens
51+
52+
Consider an example scenario, a query that retrieves virtual machines sorted by location:
53+
54+
```kusto
55+
Resources
56+
| where type =~ 'microsoft.compute/virtualmachines'
57+
| order by location asc
58+
| project name, location, resourceGroup
59+
```
60+
61+
If multiple VMs share the same location value (for example, eastus), their relative order isn't deterministic. When paginating:
62+
63+
**Page 1 request:** Retrieve the first five virtual machines.
64+
65+
| Position | Name| Location|
66+
|----|-----|-----|
67+
|1|vm-web-01 | eastus|
68+
|2|vm-db-01 | eastus |
69+
|3|vm-app-01 | eastus |
70+
|4| vm-cache-01 | eastus |
71+
|5| vm-api-01 | eastus |
72+
73+
**Page 2 request:** Skip five records and retrieve the next 5.
74+
75+
Because all these VMs share the same location value (eastus), their relative order isn't guaranteed to remain consistent across pagination calls. The second page might return:
76+
77+
| Position | Name| Location|
78+
|----|-----|-----|
79+
|1|vm-app-01 | eastus|
80+
|2|vm-queue-01 | eastus |
81+
|3|vm-monitor-01 | westus |
82+
|4| vm-backup-01 | westus |
83+
|5| vm-test-01 | westus |
84+
85+
Notice that *vm-app-01* appears in both pages (duplicate). Due to the same reordering when there's lack of sorting, a record for e.g *vm-db-01* might never appear in any subsequent page (missing).
86+
87+
#### Solution: sort by a unique column
88+
89+
Always include a unique column such as id in your sort order to ensure deterministic results:
90+
91+
```kusto
92+
Resources
93+
| where type =~ 'microsoft.compute/virtualmachines'
94+
| order by location asc, id asc
95+
| project name, location, resourceGroup
96+
```
97+
98+
By adding id (which is unique for every resource) as a secondary sort column, you establish a stable, deterministic order that remains consistent across pagination calls.
99+
100+
> [!NOTE]
101+
> Sorting by a unique column resolves pagination inconsistencies in static environments where resources aren't frequently changing. If you're still experiencing duplicate or missing records after adding a unique sort column, your environment is likely dynamic with resources being created or deleted during pagination. See Scenario 2 for strategies to handle dynamic environments.
102+
103+
### Pagination limitations scenario: pagination in dynamic environments
104+
105+
If you're experiencing duplicate or missing records despite sorting by a unique column, the cause is likely changes occurring in your Azure environment during pagination. When paginating through large result sets, changes to your Azure environment between requests can affect which records appear on each page.
106+
107+
#### Why this scenario happens
108+
109+
When resources change between pagination requests, the underlying data shifts. Consider the following example scenario:
110+
111+
**Page 1 request:** Retrieve the first 100 virtual machines sorted by id.
112+
113+
```kusto
114+
Resources
115+
| where type =~ 'microsoft.compute/virtualmachines'
116+
| order by id asc
117+
| take 100
118+
```
119+
120+
This request returns VMs with IDs from vm-001 through vm-100. <br>
121+
**Between requests:** Resource vm-050 is deleted from your environment. <br>
122+
**Page 2 request:** Skip the first 100 records and retrieve the next 100. <br>
123+
124+
```kusto
125+
Resources
126+
| where type =~ 'microsoft.compute/virtualmachines'
127+
| order by id asc
128+
| skip 100
129+
| take 100
130+
```
131+
132+
Because vm-050 was deleted, all subsequent resources shifted up by one position. The resource vm-101 is now at position 100, so when you skip 100 records, vm-101 isn't included in the results.
133+
134+
Similarly, if a new resource is created between requests, you might see duplicate records in your results.
135+
136+
### Client-side strategies for dynamic environments
137+
138+
If your scenario requires a more consistent retrieval of resources, consider one of the following approaches. These strategies partition your data in a way that's resilient to changes and can also improve performance through parallel execution.
139+
140+
> [!NOTE]
141+
> These client-side strategies move the pagination logic to your application, which helps avoid the pagination inconsistencies described previously. However, they don't guarantee complete consistency across calls. Resources might be added or deleted between your initial query (for counting or retrieving IDs) and subsequent data fetches. This can result in discrepancies such as a mismatch between expected count and total resources fetched, or missing results if a resource was deleted during the operation. For scenarios requiring strict consistency, consider whether point-in-time accuracy is critical for your use case.
142+
143+
#### Option 1: Hash-based data partitioning
144+
145+
This approach partitions your data using a hash function to ensure consistent and non-overlapping results across multiple queries. Each resource belongs to exactly one partition based on its unique identifier.
146+
147+
##### Step 1: Get the total record count
148+
149+
First, determine how many records match your query:
150+
151+
```kusto
152+
Resources
153+
| where type =~ 'microsoft.compute/virtualmachines'
154+
| count
155+
```
156+
157+
Use the count to determine the number of partitions needed. For example, if your count returns 7,712 records and since Azure Resource Graph returns a maximum of 1,000 records per query, you would need at least eight partitions.
158+
159+
##### Step 2: Query each position
160+
161+
Use the hash() function to partition data based on the resource ID. Query each partition separately:
162+
163+
**Partition 0**
164+
165+
```kusto
166+
Resources
167+
| where type =~ 'microsoft.compute/virtualmachines'
168+
| where hash(tolower(id)) % 8 == 0
169+
```
170+
171+
**Partition 1**
172+
173+
```kusto
174+
Resources
175+
| where type =~ 'microsoft.compute/virtualmachines'
176+
| where hash(tolower(id)) % 8 == 1
177+
```
178+
179+
Continue for each partition through partition 7:
180+
181+
**Partition 7**
182+
183+
```kusto
184+
Resources
185+
| where type =~ 'microsoft.compute/virtualmachines'
186+
| where hash(tolower(id)) % 8 == 7
187+
```
188+
189+
#### Pseudo code
190+
191+
##### Step 1: Get total count and calculate partitions
192+
193+
```kusto
194+
totalCount = executeQuery("Resources | where type =~ 'microsoft.compute/virtualmachines' | count")
195+
196+
numPartitions = ceiling(totalCount / 1000)
197+
```
198+
199+
##### Step 2: Build queries for each partition
200+
201+
```kusto
202+
queries = []
203+
204+
for i = 0 to numPartitions - 1:
205+
206+
queries.append("Resources
207+
208+
| where type =~ 'microsoft.compute/virtualmachines'
209+
210+
| where hash(tolower(id)) % {numPartitions} == {i}")
211+
212+
213+
```
214+
215+
##### Step 3: Execute all queries in parallel and combine results
216+
217+
```kusto
218+
allResults = executeInParallel(queries)
219+
```
220+
221+
##### Benefits
222+
223+
- **No duplicates or missed records:** Each resource ID hashes to exactly one partition.
224+
- **Parallel execution:** All partition queries can run simultaneously, reducing total query time.
225+
226+
#### Option 2: Batch processing with resource IDs
227+
228+
This approach retrieves all resource IDs first, then queries for complete records in smaller batches. This ensures you have a consistent set of identifiers before retrieving the full resource data.
229+
230+
##### Step 1: Retrieve all resource IDs
231+
232+
Use summarize with make_set() to retrieve all resource IDs:
233+
234+
```kusto
235+
Resources
236+
| where type =~ 'microsoft.compute/virtualmachines'
237+
| summarize make_set(id)
238+
```
239+
240+
##### Step 2: Query in batches
241+
242+
Once you have the list of resource IDs, query for full records in batches of 1,000 or fewer:
243+
244+
**Batch 1**
245+
246+
```kusto
247+
Resources
248+
| where type =~ 'microsoft.compute/virtualmachines'
249+
| where id in~ ('id1', 'id2', ... , 'id1000')
250+
```
251+
252+
**Batch 2**
253+
254+
```kusto
255+
Resources
256+
| where type =~ 'microsoft.compute/virtualmachines'
257+
| where id in~ ('id1001', 'id1002', ... , 'id2000')
258+
```
259+
260+
Continue until all IDs are covered.
261+
262+
##### Benefits
263+
264+
- **Guaranteed completeness:** You have a fixed set of IDs before querying for details.
265+
- **Parallel execution:** Batch queries can run simultaneously.
266+
267+
268+
If the response of the query is huge (> 16 MB) and doesn’t fit in a single call, it's suggested to use the previously mentioned partitioning technique to fetch all the data in multiple calls.
269+
270+
The following is an example of a query that might exceed response size limit of 16 MB:
271+
272+
```kusto
273+
Resources
274+
| where type =~ 'microsoft.compute/virtualmachines'
275+
| summarize make_set(id)
276+
```
277+
In this case, use partitioning:
278+
279+
**Partition 0**
280+
281+
```kusto
282+
Resources
283+
| where type =~ 'microsoft.compute/virtualmachines'
284+
| where hash(tolower(id))%10 == 0
285+
| summarize make_set(id)
286+
```
287+
288+
**Partition 1**
289+
290+
```kusto
291+
Resources
292+
| where type =~ 'microsoft.compute/virtualmachines'
293+
| where hash(tolower(id))%10 == 1
294+
| summarize make_set(id)
295+
```
296+
....
297+
298+
**Partition 9**
299+
300+
```kusto
301+
Resources
302+
| where type =~ 'microsoft.compute/virtualmachines'
303+
| where hash(tolower(id))%10 == 9
304+
| summarize make_set(id)
305+
```
306+
307+
## Summary
308+
309+
From this article, you were able to learn:
310+
311+
- How sorting by non-unique columns can cause duplicate or missing records during pagination
312+
- How dynamic environments with changing resources can affect paginated results
313+
- Client-side strategies including hash-based partitioning and batch processing with resource IDs

articles/governance/resource-graph/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
- name: Work with large data sets
7777
displayName: page, first, skip, token
7878
href: ./concepts/work-with-data.md
79+
- name: Guidance for paging results
80+
href: ./concepts/paging-results.md
7981
- name: Guidance for throttled requests
8082
displayName: pagination, batching, throttling, parallel
8183
href: ./concepts/guidance-for-throttled-requests.md

0 commit comments

Comments
 (0)