fleet/server/service/async
Victor Lyuboslavsky 8af94af14b
Removed duplicate FlippingPoliciesForHost DB calls (#42845)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42836 

This is another hot path optimization.

## Before

When a host submits policy results via `SubmitDistributedQueryResults`,
the system needed to determine which policies "flipped" (changed from
passing to failing or vice versa). Each consumer computed this
independently:

```
SubmitDistributedQueryResults(policyResults)
  |
  +-- processScriptsForNewlyFailingPolicies
  |     filter to failing policies with scripts
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #1
  |     convert result to set, filter, queue scripts
  |
  +-- processSoftwareForNewlyFailingPolicies
  |     filter to failing policies with installers
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #2
  |     convert result to set, filter, queue installs
  |
  +-- processVPPForNewlyFailingPolicies
  |     filter to failing policies with VPP apps
  |     BUILD SUBSET of results
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #3
  |     convert result to set, filter, queue VPP
  |
  +-- webhook filtering
  |     filter to webhook-enabled policies
  |     CALL FlippingPoliciesForHost(subset)          <-- DB query #4
  |     register flipped policies in Redis
  |
  +-- RecordPolicyQueryExecutions
        CALL FlippingPoliciesForHost(all results)     <-- DB query #5
        reset attempt counters for newly passing
        INSERT/UPDATE policy_membership
```

Each `FlippingPoliciesForHost` call runs `SELECT policy_id, passes FROM
policy_membership WHERE host_id = ? AND policy_id IN (?)`. All 5 queries
hit the same table for the same host before `policy_membership` is
updated, so they all see identical state.

Each consumer also built intermediate maps to narrow down to its subset
before calling `FlippingPoliciesForHost`, then converted the result into
yet another set for filtering. This meant 3-4 temporary maps per
consumer.

## After

```
SubmitDistributedQueryResults(policyResults)
  |
  CALL FlippingPoliciesForHost(all results)           <-- single DB query
  build newFailingSet, normalize newPassing
  |
  +-- processScriptsForNewlyFailingPolicies
  |     filter to failing policies with scripts
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue scripts
  |
  +-- processSoftwareForNewlyFailingPolicies
  |     filter to failing policies with installers
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue installs
  |
  +-- processVPPForNewlyFailingPolicies
  |     filter to failing policies with VPP apps
  |     CHECK newFailingSet (in-memory map lookup)
  |     queue VPP
  |
  +-- webhook filtering
  |     filter to webhook-enabled policies
  |     FILTER newFailing/newPassing by policy IDs (in-memory)
  |     register flipped policies in Redis
  |
  +-- RecordPolicyQueryExecutions
        USE pre-computed newPassing (skip DB query)
        reset attempt counters for newly passing
        INSERT/UPDATE policy_membership
```

The intermediate subset maps and per-consumer set conversions are
removed. Each process function goes directly from "policies with
associated automation" to "is this policy in newFailingSet?" in a single
map lookup.

# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Performance Improvements**
* Reduced redundant database queries during policy result submissions by
computing flipping policies once per host check-in instead of multiple
times.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 10:11:07 -05:00
..
async.go slog migration: service layer + subsystem libraries (#40661) 2026-02-26 17:40:46 -06:00
async_bench_test.go Implement async processing of hosts for label queries (#2288) 2021-11-01 14:13:16 -04:00
async_host_seen.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_host_seen_test.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_label.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_label_test.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_policy.go Removed duplicate FlippingPoliciesForHost DB calls (#42845) 2026-04-06 10:11:07 -05:00
async_policy_test.go Removed duplicate FlippingPoliciesForHost DB calls (#42845) 2026-04-06 10:11:07 -05:00
async_scheduled_query_stats.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_scheduled_query_stats_test.go OpenTelemetry minor improvements (#32324) 2025-08-28 19:32:46 -05:00
async_test.go Removed duplicate FlippingPoliciesForHost DB calls (#42845) 2026-04-06 10:11:07 -05:00
collect.go Update to Go 1.24.1 (#27506) 2025-03-31 11:14:09 -05:00
collect_test.go Set authz checked when rate limiting device endpoints (#6702) 2022-07-18 14:22:49 -03:00
doc.go Support async saving of hosts' last seen time (#5640) 2022-05-10 11:29:17 -04:00