Skip to content

Improved Windows MDM performance - #43912

Merged
getvictor merged 4 commits into
mainfrom
victor/43875-win-mdm-perf
Apr 21, 2026
Merged

Improved Windows MDM performance#43912
getvictor merged 4 commits into
mainfrom
victor/43875-win-mdm-perf

Conversation

@getvictor

@getvictor getvictor commented Apr 21, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #43875

POST /api/mdm/microsoft/management is the hot endpoint for any Windows-MDM-enrolled
fleet. Every enrolled host hits it twice per check-in interval. At 40k hosts that's a
four-figure sustained queries-per-second rate on the database reader pool, dominated
by one expensive query plus a handful of redundant MDMWindowsGetEnrolledDeviceWithDeviceID
lookups on the same row.

This PR cuts that load by:

  1. Short-circuiting the pending-commands query when the device's queue is empty (the
    overwhelming common case). Replaces a 3-table join plus anti-join with a cheap
    primary-key probe.
  2. Loading the enrolled device exactly once in isTrustedRequest and threading it
    through to every downstream consumer instead of re-fetching it three times.

No behavior change to the protocol, no schema change. Also filed a related issue: #43897

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

  • Alerted the release DRI if additional load testing is needed

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows MDM server performance at scale by reducing database queries during device check-ins.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request refactors Windows MDM pending command retrieval and device response handling to reduce database query overhead during device check-ins. The changes modify the datastore layer to accept enrollment identifiers instead of device identifiers, thread the already-resolved enrolled device through the service layer call chain to avoid redundant lookups, add deterministic ordering to enrollment queries, and introduce a fast-path existence check before executing the full pending-command query. Interface signatures, mock implementations, and test cases are updated accordingly.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improved Windows MDM performance' is concise and directly related to the main objective of reducing database load and improving performance in Windows MDM operations.
Linked Issues check ✅ Passed The code changes implement both proposed fixes from issue #43875: short-circuiting on empty queue via SELECT EXISTS probe and removing the mdm_windows_enrollments join by plumbing enrollment_id through the call stack.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the two performance fixes described in issue #43875: datastore query refactoring, signature updates, and corresponding service layer changes.
Description check ✅ Passed The pull request description clearly explains the problem, the solution, and includes a related issue reference with all key checklist items marked complete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch victor/43875-win-mdm-perf

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/service/microsoft_mdm.go (1)

1722-1760: ⚠️ Potential issue | 🟠 Major

Don’t persist command responses before authentication succeeds.

For RequestAuthStateChallenge and RequestAuthStateUnauthorized, saveResponse can still insert command results, update profile state, and dequeue commands. Keep these states challenge-only, or persist only a raw audit response that cannot mutate command state.

Proposed fix
 		responseCmds = append(responseCmds, ackMsg)
-		err = saveResponse([]string{})
-		if err != nil {
-			return nil, err
-		}
 		return responseCmds, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/microsoft_mdm.go` around lines 1722 - 1760, The current flow
for RequestAuthStateChallenge/RequestAuthStateUnauthorized builds the ackMsg
(NewSyncMLCmdStatus) and then calls saveResponse([]string{}) which may persist
command results and mutate device/profile state; change this so that for
requestAuthState == RequestAuthStateChallenge || RequestAuthStateUnauthorized
you do NOT call saveResponse or you call a non-mutating audit-only persistence
path instead (e.g., add a new saveAuditResponse or a saveResponse flag) so only
the nonce is stored via svc.keyValueStore.Set and the ackMsg is returned without
dequeuing or marking commands done; update places referencing saveResponse,
responseCmds, and ackMsg to use the non-persisting behavior.
server/datastore/mysql/microsoft_mdm.go (1)

3379-3397: ⚠️ Potential issue | 🟡 Minor

Avoid leaving a trailing OR when skipped command IDs appear at the end.

If failedCommandIds ends with "", the loop skips the final element and never trims the previous " OR ", producing invalid SQL before ORDER BY.

Proposed fix
 	args := []any{deviceID}
-	for idx, commandId := range failedCommandIds {
+	clauses := make([]string, 0, len(failedCommandIds))
+	for _, commandId := range failedCommandIds {
 		if commandId == "" {
 			continue
 		}
 
-		stmt += " wmc.raw_command LIKE ? OR "
+		clauses = append(clauses, "wmc.raw_command LIKE ?")
 		args = append(args, "%<CmdID>"+commandId+"</CmdID>%")
-		if idx == len(failedCommandIds)-1 {
-			stmt = strings.TrimSuffix(stmt, " OR ")
-		}
 	}
 
 	if len(args) == 1 {
 		// No valid command IDs were provided, return empty result to avoid returning all commands for the device.
 		return []*fleet.MDMWindowsCommand{}, nil
 	}
 
-	stmt += fmt.Sprintf(" ORDER BY created_at DESC LIMIT %d", len(failedCommandIds))
+	stmt += " " + strings.Join(clauses, " OR ")
+	stmt += fmt.Sprintf(" ORDER BY created_at DESC LIMIT %d", len(clauses))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/microsoft_mdm.go` around lines 3379 - 3397, The SQL
builder loop can leave a trailing " OR " when the last failedCommandIds entries
are empty because the TrimSuffix is only applied inside the loop; fix by
removing the per-iteration TrimSuffix and instead trim the trailing " OR " once
after the loop (or build the WHERE fragments into a slice and strings.Join with
" OR "). Ensure you append args with "%<CmdID>"+commandId+"</CmdID>%" only for
non-empty commandId, keep the initial deviceID in args, and update the LIMIT to
use the count of added command IDs (len(args)-1) rather than
len(failedCommandIds); references: stmt, args, failedCommandIds, deviceID.
🧹 Nitpick comments (1)
server/datastore/mysql/microsoft_mdm_test.go (1)

1753-1756: Minor: the "non-existent" assertion is a bit weaker than before.

Passing 0 always misses because auto-increment enrollment ids start at 1, so this validates the cheap "no rows" path but can't catch a bug where the query accidentally drops the enrollment_id filter and returns any queued row. Consider also asserting the empty result for a plausibly-valid-but-unused id (e.g. dID + 1000) so a regression that widens the filter would be caught.

Suggested tweak
-	// non-existent enrollment
-	cmds, err = ds.MDMWindowsGetPendingCommands(ctx, 0)
-	require.NoError(t, err)
-	require.Empty(t, cmds)
+	// non-existent enrollment (sentinel id 0 and an unused id well past any real row)
+	for _, missing := range []uint{0, dID + 1000} {
+		cmds, err = ds.MDMWindowsGetPendingCommands(ctx, missing)
+		require.NoError(t, err)
+		require.Empty(t, cmds)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/microsoft_mdm_test.go` around lines 1753 - 1756, The
test currently calls ds.MDMWindowsGetPendingCommands(ctx, 0) which only verifies
the "no rows" path; add a second assertion using a plausibly-valid-but-unused
enrollment id (e.g. use the existing dID variable and call
ds.MDMWindowsGetPendingCommands(ctx, dID+1000)) to ensure the query still
filters by enrollment_id (call require.NoError and require.Empty for that call).
This targets the MDMWindowsGetPendingCommands code path and will catch
regressions that drop or widen the enrollment_id filter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@server/datastore/mysql/microsoft_mdm.go`:
- Around line 3379-3397: The SQL builder loop can leave a trailing " OR " when
the last failedCommandIds entries are empty because the TrimSuffix is only
applied inside the loop; fix by removing the per-iteration TrimSuffix and
instead trim the trailing " OR " once after the loop (or build the WHERE
fragments into a slice and strings.Join with " OR "). Ensure you append args
with "%<CmdID>"+commandId+"</CmdID>%" only for non-empty commandId, keep the
initial deviceID in args, and update the LIMIT to use the count of added command
IDs (len(args)-1) rather than len(failedCommandIds); references: stmt, args,
failedCommandIds, deviceID.

In `@server/service/microsoft_mdm.go`:
- Around line 1722-1760: The current flow for
RequestAuthStateChallenge/RequestAuthStateUnauthorized builds the ackMsg
(NewSyncMLCmdStatus) and then calls saveResponse([]string{}) which may persist
command results and mutate device/profile state; change this so that for
requestAuthState == RequestAuthStateChallenge || RequestAuthStateUnauthorized
you do NOT call saveResponse or you call a non-mutating audit-only persistence
path instead (e.g., add a new saveAuditResponse or a saveResponse flag) so only
the nonce is stored via svc.keyValueStore.Set and the ackMsg is returned without
dequeuing or marking commands done; update places referencing saveResponse,
responseCmds, and ackMsg to use the non-persisting behavior.

---

Nitpick comments:
In `@server/datastore/mysql/microsoft_mdm_test.go`:
- Around line 1753-1756: The test currently calls
ds.MDMWindowsGetPendingCommands(ctx, 0) which only verifies the "no rows" path;
add a second assertion using a plausibly-valid-but-unused enrollment id (e.g.
use the existing dID variable and call ds.MDMWindowsGetPendingCommands(ctx,
dID+1000)) to ensure the query still filters by enrollment_id (call
require.NoError and require.Empty for that call). This targets the
MDMWindowsGetPendingCommands code path and will catch regressions that drop or
widen the enrollment_id filter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 45b37307-9e01-4529-9181-5293d07d785f

📥 Commits

Reviewing files that changed from the base of the PR and between c2379ea and 03577d2.

📒 Files selected for processing (8)
  • changes/43875-windows-mdm-pending-commands-flood
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/fleet/datastore.go
  • server/mock/datastore_mock.go
  • server/service/microsoft_mdm.go
  • server/service/microsoft_mdm_test.go

@codecov

codecov Bot commented Apr 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.25397% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.09%. Comparing base (c2379ea) to head (03577d2).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
server/service/microsoft_mdm.go 61.76% 12 Missing and 1 partial ⚠️
server/datastore/mysql/microsoft_mdm.go 75.86% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #43912   +/-   ##
=======================================
  Coverage   65.08%   65.09%           
=======================================
  Files        2603     2603           
  Lines      253269   253273    +4     
  Branches     9356     9356           
=======================================
+ Hits       164846   164862   +16     
+ Misses      75656    75647    -9     
+ Partials    12767    12764    -3     
Flag Coverage Δ
backend 66.29% <68.25%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@getvictor
getvictor marked this pull request as ready for review April 21, 2026 21:14
@getvictor
getvictor requested a review from a team as a code owner April 21, 2026 21:14
Copilot AI review requested due to automatic review settings April 21, 2026 21:14

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the hot Windows MDM management check-in path (POST /api/mdm/microsoft/management) by reducing redundant datastore lookups and avoiding the expensive pending-commands query when the per-enrollment queue is empty.

Changes:

  • Add a fast-path probe in MDMWindowsGetPendingCommands to short-circuit when no commands are queued, and query pending commands by enrollment_id (dropping the enrollments join).
  • Resolve the Windows enrollment once during request trust evaluation and thread the resulting enrolledDevice through downstream service logic to avoid repeated lookups.
  • Update mocks/tests and datastore interfaces accordingly; add a release note entry.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
server/service/microsoft_mdm.go Threads enrolledDevice from trust check through management response flow; switches pending-commands lookup to enrollment ID.
server/datastore/mysql/microsoft_mdm.go Adds EXISTS probe fast-path and changes pending-commands query to use enrollment_id; makes enrollment selection deterministic with created_at, id ordering; updates save-response signature to accept the already-resolved enrollment.
server/fleet/datastore.go Updates datastore interface signatures for Windows pending commands and save response.
server/mock/datastore_mock.go Updates mock signatures to match datastore interface changes.
server/datastore/mysql/microsoft_mdm_test.go Updates MySQL datastore tests to use enrollment IDs and to reflect per-enrollment pending command semantics.
server/datastore/mysql/mdm_test.go Updates Windows MDM command test to pass full enrollment into MDMWindowsSaveResponse.
server/service/microsoft_mdm_test.go Updates service test mocking to validate enrollment ID is threaded through pending-commands lookup.
changes/43875-windows-mdm-pending-commands-flood Adds release note for Windows MDM performance improvement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1325 to 1346
// isTrustedRequest checks if the incoming request was sent from an MDM-enrolled
// device. It returns the matched enrollment (when the device was found), the
// auth state, and an error only when the request is malformed or otherwise
// cannot be processed. Expected non-trusted outcomes (for example
// RequestAuthStateChallenge or RequestAuthStateUnauthorized) are reported via
// the returned auth state and may return a nil error. The returned enrolled
// device may be nil when the state is RequestAuthStateUntrusted.
func (svc *Service) isTrustedRequest(ctx context.Context, reqSyncML *fleet.SyncML, reqCerts []*x509.Certificate) (*fleet.MDMWindowsEnrolledDevice, requestAuthState, error) {
if reqSyncML == nil {
return RequestAuthStateUntrusted, fleet.NewInvalidArgumentError("syncml req message", "message is not present")
return nil, RequestAuthStateUntrusted, fleet.NewInvalidArgumentError("syncml req message", "message is not present")
}

// Checking if calling request is coming from an already MDM enrolled device
deviceID, err := reqSyncML.GetSource()
if err != nil || deviceID == "" {
return RequestAuthStateUntrusted, fmt.Errorf("invalid SyncML message %w", err)
return nil, RequestAuthStateUntrusted, fmt.Errorf("invalid SyncML message %w", err)
}

enrolledDevice, err := svc.ds.MDMWindowsGetEnrolledDeviceWithDeviceID(ctx, deviceID)
if err != nil || enrolledDevice == nil {
return RequestAuthStateUntrusted, errors.New("device was not MDM enrolled")
return nil, RequestAuthStateUntrusted, errors.New("device was not MDM enrolled")
}

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment for isTrustedRequest says it returns an error only for malformed/unprocessable requests and that expected non-trusted outcomes are communicated via the auth state, but the implementation still returns a non-nil error when the device isn't enrolled (RequestAuthStateUntrusted). Please update the comment to match the actual error semantics (or adjust the function/caller behavior so that 'not enrolled' is treated as a non-error auth state).

Copilot uses AI. Check for mistakes.

@ksykulev ksykulev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

@getvictor
getvictor merged commit 1f6e556 into main Apr 21, 2026
55 checks passed
@getvictor
getvictor deleted the victor/43875-win-mdm-perf branch April 21, 2026 21:52
getvictor added a commit that referenced this pull request Apr 21, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #43875 

Cherry pick of #43912
@coderabbitai coderabbitai Bot mentioned this pull request Jun 5, 2026
5 tasks
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.

Windows MDM pending commands query floods the reader pool on every check-in

3 participants