fleet/server
Victor Lyuboslavsky 2ddc2ae90a
Optimized PolicyQueriesForHost and ListPoliciesForHost SQL queries (#43035)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #43034

## Before (correlated subqueries):

The old query scans the policies table and for each policy row, MySQL
executes up to 3 separate subqueries against policy_labels +
label_membership:

```sql
  -- For EACH policy row p:

  -- Subquery 1: Does this policy have any include labels?
  NOT EXISTS (
      SELECT 1 FROM policy_labels pl
      WHERE pl.policy_id = p.id AND pl.exclude = 0
  )

  -- Subquery 2: Is the host in at least one include label?
  OR EXISTS (
      SELECT 1 FROM policy_labels pl
      INNER JOIN label_membership lm ON (lm.host_id = ? AND lm.label_id = pl.label_id)
      WHERE pl.policy_id = p.id AND pl.exclude = 0
  )

  -- Subquery 3: Is the host in any exclude label?
  AND NOT EXISTS (
      SELECT 1 FROM policy_labels pl
      INNER JOIN label_membership lm ON (lm.host_id = ? AND lm.label_id = pl.label_id)
      WHERE pl.policy_id = p.id AND pl.exclude = 1
  )
  ```

  With 200 policies, MySQL executes up to 600 subquery probes into policy_labels and label_membership.

## After (single aggregated LEFT JOIN):

The new query first builds one aggregated result set from policy_labels + label_membership for this host, grouped by policy_id, then joins it once:

```sql
  LEFT JOIN (
      SELECT pl.policy_id,
MAX(CASE WHEN pl.exclude = 0 THEN 1 ELSE 0 END) AS has_include_labels,
MAX(CASE WHEN pl.exclude = 0 AND lm.host_id IS NOT NULL THEN 1 ELSE 0
END) AS host_in_include,
MAX(CASE WHEN pl.exclude = 1 AND lm.host_id IS NOT NULL THEN 1 ELSE 0
END) AS host_in_exclude
      FROM policy_labels pl
LEFT JOIN label_membership lm ON lm.label_id = pl.label_id AND
lm.host_id = ?
      GROUP BY pl.policy_id
  ) pl_agg ON pl_agg.policy_id = p.id
```

  The subquery scans policy_labels once, LEFT JOINs to label_membership for the specific host, and aggregates per policy. Each policy gets three booleans:
  - has_include_labels: 1 if any policy_labels row with exclude=0 exists
  - host_in_include: 1 if any include label row matched a label_membership row for this host
  - host_in_exclude: 1 if any exclude label row matched a label_membership row for this host

  Then the WHERE clause uses these:
```sql
(COALESCE(pl_agg.has_include_labels, 0) = 0 OR pl_agg.host_in_include =
1)
  AND COALESCE(pl_agg.host_in_exclude, 0) = 0
```

The COALESCE handles policies with no policy_labels rows at all (the LEFT JOIN produces NULL).

# 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] QA'd all new/changed functionality manually


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

## Summary by CodeRabbit

## Release Notes

* **Refactor**
  * Optimized database query efficiency for policy operations, delivering approximately 77% faster query execution at scale while improving support for label-based policy scoping.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 08:50:18 -05:00
..
acl ACME MDM -> main (#42926) 2026-04-02 15:56:31 -05:00
activity Removed the ptr helper package from Activity bounded context (#42161) 2026-03-23 14:10:07 -05:00
archtest Refactor endpoint_utils for modularization (#36484) 2025-12-31 09:12:00 -06:00
authz Fixed team maintainers, admins, and GitOps users being unable to add certificate templates (#41740) 2026-03-16 12:24:31 -05:00
aws_common Feat 1817 add iam auth to mysql and redis (#32488) 2025-09-04 10:08:47 -05:00
bindata Allow users to be readded if they were ever removed (#1945) 2021-09-07 13:33:40 -03:00
config Skip startup library for primo mode (#43019) 2026-04-05 21:36:56 -05:00
contexts Move PostJSONWithTimeout to platform/http package and activity cleanup (#40561) 2026-02-26 17:39:10 -06:00
cron slog migration: initLogger + serve.go + cron + schedule (#40699) 2026-02-27 14:29:27 -06:00
crypto Crypto package for db encryption (#41139) 2026-03-11 16:45:59 -06:00
datastore Optimized PolicyQueriesForHost and ListPoliciesForHost SQL queries (#43035) 2026-04-06 08:50:18 -05:00
dev_mode Add lock semantics around dev_mode.IsEnabled to avoid data races (#42646) 2026-03-31 07:49:45 -04:00
errorstore Incremental migration to slog (#40120) 2026-02-19 15:35:35 -06:00
fleet Use OSV for ubuntu vulnerability scanning (#42063) 2026-04-03 15:59:32 -05:00
goose Use UTC timestamps for DB migrations (#36228) 2025-11-24 15:49:10 -06:00
health slog migration: service layer + subsystem libraries (#40661) 2026-02-26 17:40:46 -06:00
launcher Final slog migration PR: test infrastructure + tools + remaining standalone files (#40727) 2026-02-28 05:52:21 -06:00
live_query Incremental migration to slog (#40120) 2026-02-19 15:35:35 -06:00
logging Migrated logging and google calendar files to use slog (#40541) 2026-02-26 12:48:54 -06:00
mail Run multiple independent Fleet dev servers in parallel (#41865) 2026-03-18 13:58:58 -05:00
mdm Switch FMA manifest retrieval to use Cloudflare R2 bucket (#43012) 2026-04-03 19:08:45 -05:00
mock ACME MDM -> main (#42926) 2026-04-02 15:56:31 -05:00
platform ACME MDM -> main (#42926) 2026-04-02 15:56:31 -05:00
policies Migrating maintained apps, failing policies, and webhooks to slog. (#40149) 2026-02-23 08:50:40 -06:00
ptr speed up macOS profile delivery for initial enrollments (#41960) 2026-03-19 14:58:10 -05:00
pubsub Incremental migration to slog (#40120) 2026-02-19 15:35:35 -06:00
service Switch FMA manifest retrieval to use Cloudflare R2 bucket (#43012) 2026-04-03 19:08:45 -05:00
shellquote Updating golangci-lint to 1.61.0 (#22973) 2024-10-18 12:38:26 -05:00
sso End-user authentication for Window/Linux setup experience: backend (#34835) 2025-10-31 11:16:42 -05:00
test Move NewActivity to activity bounded context (#39521) 2026-02-25 14:11:03 -06:00
variables DCSW: Support all IDP variables in Windows config profiles (#34707) 2025-10-24 10:10:58 -03:00
version Move external dependency fleetdm/kolide-kit to monorepo (#15861) 2024-01-02 18:22:52 -03:00
vulnerabilities Use OSV for ubuntu vulnerability scanning (#42063) 2026-04-03 15:59:32 -05:00
webhooks Feat/31914 patch policy (#41518) 2026-03-13 16:47:09 -04:00
websocket Enable errcheck linter for golangci-lint (#8899) 2022-12-05 16:50:49 -06:00
worker use redis to block double profile work for apple devices setting up (#42421) 2026-03-30 16:37:18 -05:00
utils.go Move PostJSONWithTimeout to platform/http package and activity cleanup (#40561) 2026-02-26 17:39:10 -06:00
utils_test.go feature: target profiles by labels (#16202) 2024-01-26 11:00:58 -05:00