Skip to content

Performance challenges with large tenants #1236

Open
1 of 7 issues completed
Open

Description

Creating this issue as initial placeholder to address several known challenges with very large tenants.
The issues are sourced from real world large tenant experiences. This work item must not be addressed directly, but rather used as base for creating actual work items to address problematic areas.

The analysis showed several areas worth applying new approaches for:

$skiptoken expiration

Microsoft Graph documentation explicitly makes a reference to dedicated error when paging through vast amount of data: https://learn.microsoft.com/en-us/graph/paging?tabs=http#error-handling
The DirectoryPageTokenNotFoundException must be accounted for, handled and the whole process of grabbing data restarted.
Additionally, MS Graph can, without specifying any concrete reason, expire $skipToken - especially if there was too long time between calling the individual pages.
Any retries that happen, must account for the $skipToken change, and as per documentation, use the value from last successful response, instead of the original one, if that failed.

retry back-off

The analysis shows an exponential retry back off of 3s, 6s, 12s, 24s, 48s - come from the default start setting of 3 seconds x 5 retries.
We must keep track of whether this is a repeatable pattern with large tenants, or an isolated case. If repeatable, it is worth considering making the default larger - e.g. 5 or 6 seconds. Because if there are some real intermittent issues, better to wait 2 seconds more for the first retry, then risking entering throttling loop because of too short base wait.

Keeping this for deeper analysis.


Here is a comprehensive analysis of one particular failure:

Support Package Analysis

Time Range: 2026-05-12 23:12:24 → 2026-05-22 00:24:49 (9 days, 1 hour, 12 minutes)
Environment: PS 7.5.5 on Microsoft Windows 10.0.26200
Module Version: ZeroTrustAssessment 2.2.50

Summary

  • Total log messages: 1,831
  • Errors: 2 | Warnings: 37 | Critical: 0
Error Category Count
DNS Resolution Failure 13
HTTP Timeout (300s) 10
Stream Copy Error 5
Authentication Failure 4
Null Reference 2
Sign-In Log Size Limit 1
Skip Token Expiration 1
Non-Retryable (400) 1
Other 2

Issues Found

Issue 1: Authentication Failures (Initial)

  • Severity: Error + Warning (Issues 1–4)
  • Time: Session start
  • Function: Connect-ZtAssessment
  • Source: Connect-ZtAssessment.ps1
  • Message: InteractiveBrowserCredential authentication failed: User canceled authentication
  • Root Cause: The user closed the browser window on the first authentication attempt. They subsequently retried and connected successfully (the assessment proceeded).
  • Impact: None — transient user action, assessment continued.

Issue 2: Sign-In Log Size Limit Hit

  • Severity: Warning (Issue 5)
  • Time: During export phase
  • Function: Export-ZtGraphEntity
  • Source: Export-ZtGraphEntity.ps1 (~line 226)
  • Message: Sign-in log export reached size limit of 1024 MB (current: 1025.71 MB)
  • Root Cause: Large tenant with a very high volume of sign-in logs. The 1 GB cap was reached.
  • Impact: Low — sign-in log export was truncated but assessment continues. This is expected behavior.
  • Fix: If more sign-in data is needed, increase ZeroTrustAssessment.Export.SignInLog.MaxSizeBytes.

Issue 3: Skip Token Expiration — ServicePrincipalSignIn Export Failed

  • Severity: Warning (Issues 9–12)
  • Time: ~52 minutes into export
  • Function: Invoke-ZtRetry → Export-ZtGraphEntity → Invoke-ZtTenantDataExport
  • Source: Export-ZtGraphEntity.ps1 (~line 190), Invoke-ZtRetry.ps1
  • Message: Skip token is null. It may have a typo or it has expired. (HTTP 400 on page 53)
  • Root Cause: Graph API skip tokens are time-bound. Repeated stream errors and timeouts during pagination slowed the export enough for the token to expire server-side. The 400 was correctly marked as non-retryable by Test-ZtRetryableError — retrying the same expired token would fail again.
  • Impact: ServicePrincipalSignIn export failed (52m57s wasted).

Issue 4: ServicePrincipal Null Reference

  • Severity: Warning (Issues 14–15)
  • Time: After ~1h25m of export
  • Function: Invoke-ZtTenantDataExport → Wait-ZtTenantDataExport
  • Source: Invoke-ZtTenantDataExport.ps1, Wait-ZtTenantDataExport.ps1
  • Message: You cannot call a method on a null-valued expression
  • Root Cause: Likely a cascading failure — a variable was null due to a prior failed network operation. The sustained network instability (stream errors + DNS failures) caused an upstream operation to return null, and the ServicePrincipal export called a method on it.
  • Impact: ServicePrincipal export failed (1h25m42s wasted).

Issue 5: Sustained HTTP Timeouts During Application Export

  • Severity: Warning (Issues 16–26)
  • Time: ~03:00–04:00 UTC on May 13
  • Function: Invoke-ZtRetry
  • Source: Invoke-ZtRetry.ps1
  • Message: The request was canceled due to the configured HttpClient.Timeout of 300 seconds elapsing
  • Root Cause: Graph API responses for Application pages (520s–530s of 999-item pages) consistently exceeded the 300s timeout. This is a very large tenant. Each timeout added 5+ minutes of delay, compounding the pagination problem.
  • Impact: Contributed to the Application export eventually failing via DNS failure.

Issue 6: DNS Resolution Failure — Application Export Failed

  • Severity: Warning (Issues 27–34)
  • Time: 04:12–04:23 UTC on May 13
  • Function: Invoke-ZtRetry → Export-ZtGraphEntity → Wait-ZtTenantDataExport
  • Source: Invoke-ZtRetry.ps1, Export-ZtGraphEntity.ps1
  • Message: The requested name is valid, but no data of the requested type was found. (graph.microsoft.com:443) — 5 consecutive retries exhausted
  • Root Cause: DNS could not resolve graph.microsoft.com. This was a sustained outage lasting ~11 minutes. All 5 retry attempts (with exponential backoff: 3s, 6s, 12s, 24s, 48s) failed.
  • Impact: Application export failed on page 532 after 5h7m of export time. All progress lost.

Issue 7: DNS Failure During Test Phase

  • Severity: Warning (Issues 35–39)
  • Time: 04:32 UTC on May 13 (during Stage 2: Running Tests)
  • Function: Invoke-ZtRetry (called from Invoke-ZtGraphRequestCache for /v1.0/organization)
  • Source: Invoke-ZtRetry.ps1
  • Message: Same DNS resolution failure, 5 consecutive attempts failed
  • Root Cause: DNS outage persisted into the test phase. The assessment continued running tests but any test needing live Graph calls would have failed.
  • Impact: Tests dependent on live Graph API calls during this window likely produced incorrect results.

Failed Exports

Export Duration Error Recommendation
Application 5h 7m DNS resolution failure on page 532 Re-run during stable network window; consider off-peak hours
ServicePrincipal 1h 25m Null-valued expression (cascading failure) Re-run; investigate if network instability caused upstream null
ServicePrincipalSignIn 52m 57s Skip token expired (400 Bad Request) on page 53 Re-run with stable network; stream errors slowed pagination causing token expiry

Successful Exports (13 of 16)

Device, SignIn, ConfigurationPolicy, RoleAssignmentScheduleInstance, RoleAssignmentScheduleInstanceGroup, RoleEligibilityScheduleInstance, RoleManagementPolicyAssignment, RoleDefinition, UserRegistrationDetails, RoleAssignment, User, RoleEligibilityScheduleInstanceGroup, RoleAssignmentGroup


Root Cause Analysis

The dominant issue is network instability on the customer's machine. Three distinct network failure modes were observed during the assessment:

  1. Stream copy errors (5 occurrences) — connections dropped mid-transfer, all recovered on retry
  2. HTTP timeouts (10 occurrences) — 300s timeout exceeded, indicating either slow Graph API responses for this large tenant or network latency
  3. DNS resolution failures (13 occurrences) — sustained inability to resolve graph.microsoft.com, lasting ~11+ minutes, which was the fatal blow that killed the Application export after 5 hours of work

The cascade was: stream errors + timeouts slowed pagination → skip token expired for ServicePrincipalSignIn → DNS outage killed Application and affected the test phase. The ServicePrincipal null-reference failure is likely also network-related.

This is a very large tenant (~530K+ applications, 1 GB+ of sign-in logs), which makes exports slow and amplifies the impact of any network disruption.

Recommendations

  1. Re-run the assessment during a stable network window — the DNS outage at ~04:12 UTC was the primary cause of failure. Ensure reliable DNS resolution and network connectivity before starting.
  2. Check DNS and proxy configuration — verify the customer's DNS can reliably resolve graph.microsoft.com. Check for any corporate proxy, VPN, or firewall that might cause intermittent DNS failures.
  3. Run during off-peak hours — the HTTP timeouts on Application pages suggest Graph API is responding slowly for this tenant. Off-peak hours may yield faster responses.
  4. Consider wired network — if running on WiFi, switch to wired to reduce stream copy errors.
  5. The initial auth failures are benign — the user simply cancelled the browser prompt once and retried successfully.
  6. Sign-in log 1 GB limit is expected — if the full sign-in dataset is needed, set ZeroTrustAssessment.Export.SignInLog.MaxSizeBytes to a higher value, but note this will increase export time significantly.

Activity

  1. jamesmor commented on May 27, 2026

    @jamesmor

    In my tenant (~1.7M directory objects), I’m encountering both the $skipToken issue described above and a consistent memory exhaustion.

    Observations

    • Full assessment runs have taken up to ~23 hours before becoming unresponsive due to memory exhaustion, on systems with up to 64GB RAM.
    • The issue appears to be concentrated in the Identity/User-related tasks
    • The Device pillar completes successfully when run independently
    • The Identity, Data, and Network pillars consistently fail due to memory exhaustion (system stops responding)
    • I have not yet tested the newer preview pillars
    • Multiple runs on the same system do not fail at the same apparent point or elapsed time

    Environment

    • System 1
      • ZeroTrustAssessment module version: 2.2.0.
      • PowerShell version: 7.6.1
      • 8 core + 32GB RAM,
    • System 2
      • ZeroTrustAssessment module version: 2.2.0.
      • PowerShell version: 7.6.1
      • 12core + 64GB RAM
    • System 3
      • ZeroTrustAssessment module version: 2.3.0.
      • PowerShell version: 7.6.2
      • 8 vCPU + 64GB RAM,
    • System 4 (run currently in progress)
      • ZeroTrustAssessment module version: 2.3.0.
      • PowerShell version: 7.6.2
      • 16 vCPU + 128GB RAM

    Questions

    1. Is a tenant with ~1.7M objects compatible with ZTA?
    2. Is the memory exhaustion behavior part of the known “large tenant” performance limitations, or would this be considered a separate issue?
    3. Would you prefer a separate issue specifically for the memory exhaustion scenario?
    4. Is there additional diagnostic data that would be helpful (logs, support package, specific test IDs, etc.)?
      Happy to provide logs or run targeted tests if that helps isolate the issue.
  2. jamesmor commented on May 27, 2026

    @jamesmor

    An update: the latest run with 128GB of RAM completed all but the RoleAssignmentGroup and RoleAssignmentScheduleInstanceGroup exports.

  3. kodevza commented on May 27, 2026

    @kodevza

    James Morris (@jamesmor) Would you be kind enough to test one more version?

    I prepared a change that should address the memory retention issue during Graph export.

    Before the fix, the export completed successfully, but retained too much managed memory after GC:

    GC memory after GC: ~4.54 GB
    WorkingSet peak: ~8.54 GB
    Private peak: ~8.65 GB
    Elapsed: ~4m 15s

    After the fix, the same benchmark passed with much lower retained memory:

    GC memory after GC: ~124 MB
    WorkingSet peak: ~716 MB
    Private peak: ~791 MB
    Elapsed: ~4m 14s

    The relevant commit is here:
    kodevza@146cf7c

    The change makes Export-ZtGraphEntity capture the Graph @odata.nextLink before writing each page, then clears the exported page’s heavy value payload in a finally block. This prevents retained response wrapper objects from keeping all exported entities alive in memory.

    I also added a regression test to verify that exported page values are released after writing and released:

    https://github.com/kodevza/zerotrustassessment/releases/tag/v2.4.0-memory_gc_test

    The README contains a short 5-line installation guide.

  4. jamesmor commented on May 27, 2026

    @jamesmor

    Thanks, I've got 2.40.0 running now. In most of my tests earlier today, I was able to skate by with 127GB/128GB and finish 14 of 16 tests, but then got hung on the RoleAssignmentGroup and RoleAssignmentScheduleInstanceGroup exports. The whole system has been hung, so I haven't been to collect PSFPackages after I crash the box and bring it back up.

  5. jamesmor commented on May 28, 2026

    @jamesmor
    Image

    Version 2.4.0 started out using considerably less memory than what I had previously seen, but it has crept up over the last couple hours to near 113GB in use. As seen in the image above, the RoleAssignmentGroup and RoleAssignmentScheduleInstanceGroup exports are still not completing.

  6. kodevza commented on May 28, 2026

    @kodevza

    James Morris (@jamesmor) Thx for logs and check but I think we mixed two topics in this thread a bit.

    This issue was originally more of a large-tenant / skipToken / retry placeholder. Your memory exhaustion case looks specific enough to deserve a separate issue. Could you open a new issue for the memory behavior and link this thread there? BTW: I will provide new version soon.

    Anton Staykov (@astaykov) Is this retry issue currently blocking you or users in large tenants?

    If yes: would you accept using the version from my fork for now with the fix, or do you specifically want this fixed upstream in the Microsoft repo?

  7. jamesmor commented on May 28, 2026

    @jamesmor

    I'll open the new Issues shortly; one for the memory exhaustion and one for the RoleAssignmentGroup and RoleAssignmentScheduleInstancegroup exports not completing and blocking progress on anything that relies on them. Thank you! I really appreciate the assistance.

  8. leogasparini commented on Jul 14, 2026

    @leogasparini

    Hi, I also have similar issues as James Morris (@jamesmor) with a huge tenant and was testing some potential fixes if it could be useful:

    leogasparini#1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions