Skip to content

release: v1.3.4 — SPSyncUserProfile performance: build UserProfileManager once (#24)#25

Merged
luigilink merged 2 commits into
mainfrom
release/1.3.4
Jul 10, 2026
Merged

release: v1.3.4 — SPSyncUserProfile performance: build UserProfileManager once (#24)#25
luigilink merged 2 commits into
mainfrom
release/1.3.4

Conversation

@luigilink

Copy link
Copy Markdown
Owner

Closes #24. Performance release for SPSyncUserProfile.ps1. No behaviour change — same profiles created/updated, same JSON and HTML outputs.

Why

A field run on a large (~100k-user) farm took ~1h11. Transcript analysis of that run:

Signal Value
Wall time ~71 min
Users in the write loop ~76,000 (~1.5k CREATE / rest UPDATE-INFO)
Not Added ~25,000 — filtered before the loop, ~0 cost
Per user ~56 ms, 95% sub-second, no outliers
Throughput 1,270/min → 880/min (−30% over the run)
Test-SPSADUser (LDAP) CREATE path only (~2%) — not the bottleneck

Root cause: Add-SPSUserProfile rebuilt Get-SPServiceContext + UserProfileManager on every user (~76k constructions, each loading UPA config) — the dominant per-user cost and the driver of the throughput decay (object churn / GC pressure).

What

  • Build the service context + UserProfileManager once in Main and pass the manager into Add-SPSUserProfile (new -UserProfileManager parameter, replacing -MySiteUrl). Fail fast (Exit 1) if it can't be created.
  • Condense the per-user transcript from a ~14-line before/after dump to a single status line — [UPDATE] DOMAIN\user (changed: WorkEmail, Country), [CREATE], [INFO], [UNKNOWN_USER]. On a ~100k-user run this turns a multi-million-line, tens-of-MB transcript into one line per user and removes the matching synchronous I/O.
  • End-of-run timing summary: users processed, wall time, average ms/user, and the CREATE/UPDATE/INFO/UNKNOWN_USER breakdown — so each run is measurable from its log.

Not in this PR (deferred)

Parallelising the profile write with a RunspacePool. It is not a copy-paste of the InfoList AD path: UserProfileManager is not thread-safe, each runspace needs its own context + SharePoint command surface, and the mutating calls (CreateUserProfile/Commit) need isolation and on-farm SQL-contention testing. To be decided with fresh numbers from the new timing summary, only if the single-threaded win here is not enough.

Tests

  • Full suite green locally: 101 passed, 0 failed; PSScriptAnalyzer clean.
  • New tests lock the refactored contract: Add-SPSUserProfile now has a mandatory UserProfileManager parameter and no longer exposes MySiteUrl.
  • Behaviour paths (CREATE/UPDATE/INFO/UNKNOWN_USER, Split-SPSProfileUser, account-status, report) unchanged and still covered.

Upgrade note

No configuration change; output files unchanged. Deploy and run — the next SPSyncUserProfile.ps1 run is faster, its log far smaller, and ends with the timing summary.

…ng, add timing summary

Field run on a large (~100k-user) farm took ~1h11. Transcript analysis showed the
time was spent almost entirely in the per-user loop, which rebuilt the User Profile
service context and UserProfileManager on EVERY user (~76k times) — the dominant
cost and the cause of a ~30% throughput decay over the run (object churn / GC).

- Build Get-SPServiceContext + UserProfileManager ONCE in Main and pass the manager
  into Add-SPSUserProfile (new -UserProfileManager parameter, replacing -MySiteUrl),
  instead of reconstructing them per user. Fail fast (Exit 1) if the manager cannot
  be created.
- Condense the per-user transcript from a ~14-line before/after dump to a single
  status line ([UPDATE] login (changed: ...), [CREATE], [INFO], [UNKNOWN_USER]),
  slashing synchronous I/O and transcript size on large runs.
- Add an end-of-run timing summary (users, wall time, avg ms/user, and the
  CREATE/UPDATE/INFO/UNKNOWN_USER breakdown) so runs are measurable from the log.

No behaviour change: same profiles created/updated, same JSON and HTML outputs.
Tests lock the new parameter contract (UserProfileManager mandatory, MySiteUrl gone).

Fixes #24
Bump module to 1.3.4; changelog and release notes for the SPSyncUserProfile
performance work (#24). RELEASE-NOTES.md holds the current version only.
@github-actions

Copy link
Copy Markdown

Pester Test Results

101 tests   101 ✅  3s ⏱️
 24 suites    0 💤
  1 files      0 ❌

Results for commit 1be20b8.

@luigilink luigilink merged commit f091a8e into main Jul 10, 2026
4 checks passed
@luigilink luigilink deleted the release/1.3.4 branch July 10, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: SPSyncUserProfile spends ~1h on a ~100k-user farm — hoist UserProfileManager out of the per-user loop

1 participant