@@ -27,6 +27,7 @@ import (
27
27
// Since many hosts may have issues, we need to batch the inserts of host issues.
28
28
// This is a variable, so it can be adjusted during unit testing.
29
29
var hostIssuesInsertBatchSize = 10000
30
+ var hostIssuesUpdateFailingPoliciesBatchSize = 10000
30
31
31
32
// A large number of hosts could be changing teams at once, so we need to batch this operation to prevent excessive locks
32
33
var addHostsToTeamBatchSize = 10000
@@ -5269,37 +5270,71 @@ func updateHostIssuesFailingPolicies(ctx context.Context, tx sqlx.ExecerContext,
5269
5270
return nil
5270
5271
}
5271
5272
5272
- masterStmt := `
5273
+ // For 1 host, we use a single statement to update the host_issues entry.
5274
+ if len (hostIDs ) == 1 {
5275
+ stmt := `
5276
+ INSERT INTO host_issues (host_id, failing_policies_count, total_issues_count)
5277
+ SELECT host_id.id, COALESCE(SUM(!pm.passes), 0), COALESCE(SUM(!pm.passes), 0)
5278
+ FROM policy_membership pm
5279
+ RIGHT JOIN (SELECT ? as id) as host_id
5280
+ ON pm.host_id = host_id.id
5281
+ GROUP BY host_id.id
5282
+ ON DUPLICATE KEY UPDATE
5283
+ failing_policies_count = VALUES(failing_policies_count),
5284
+ total_issues_count = VALUES(failing_policies_count) + critical_vulnerabilities_count`
5285
+ if _ , err := tx .ExecContext (ctx , stmt , hostIDs [0 ]); err != nil {
5286
+ return ctxerr .Wrap (ctx , err , "updating failing policies in host issues for one host" )
5287
+ }
5288
+ return nil
5289
+ }
5290
+
5291
+ // Clear host_issues entries for hosts that are not in policy_membership
5292
+ clearStmt := `
5293
+ UPDATE host_issues
5294
+ SET failing_policies_count = 0, total_issues_count = critical_vulnerabilities_count
5295
+ WHERE host_id NOT IN (
5296
+ SELECT host_id
5297
+ FROM policy_membership
5298
+ WHERE host_id IN (?)
5299
+ ) AND host_id IN (?)`
5300
+
5301
+ // Insert/update host_issues entries for hosts that are in policy_membership.
5302
+ // Initially, these two statements were combined into one statement using `SELECT ? AS id UNION ALL` approach to include the host IDs that
5303
+ // were not in policy_membership (similar how the above query for 1 host works). However, in load testing we saw an error: Thread stack overrun: 242191 bytes used of a 262144 byte stack
5304
+ insertStmt := `
5273
5305
INSERT INTO host_issues (host_id, failing_policies_count, total_issues_count)
5274
- SELECT host_ids.id , COALESCE(SUM(!pm.passes), 0), COALESCE(SUM(!pm.passes), 0)
5306
+ SELECT pm.host_id , COALESCE(SUM(!pm.passes), 0), COALESCE(SUM(!pm.passes), 0)
5275
5307
FROM policy_membership pm
5276
- RIGHT JOIN (%s) as host_ids
5277
- ON pm.host_id = host_ids.id
5278
- GROUP BY host_ids.id
5308
+ WHERE pm.host_id IN (?)
5309
+ GROUP BY pm.host_id
5279
5310
ON DUPLICATE KEY UPDATE
5280
5311
failing_policies_count = VALUES(failing_policies_count),
5281
5312
total_issues_count = VALUES(failing_policies_count) + critical_vulnerabilities_count`
5282
5313
5283
5314
// Large number of hosts could be impacted, so we update their host issues entries in batches to reduce lock time.
5284
- for i := 0 ; i < len (hostIDs ); i += hostIssuesInsertBatchSize {
5315
+ for i := 0 ; i < len (hostIDs ); i += hostIssuesUpdateFailingPoliciesBatchSize {
5285
5316
start := i
5286
- end := i + hostIssuesInsertBatchSize
5317
+ end := i + hostIssuesUpdateFailingPoliciesBatchSize
5287
5318
if end > len (hostIDs ) {
5288
5319
end = len (hostIDs )
5289
5320
}
5290
- totalToProcess := end - start
5291
5321
hostIDsBatch := hostIDs [start :end ]
5292
5322
5293
- inlineTable := strings .TrimSuffix (
5294
- strings .Repeat ("SELECT ? AS id UNION ALL " , totalToProcess ), " UNION ALL " ,
5295
- )
5296
-
5297
- args := make ([]interface {}, totalToProcess )
5298
- for j := range hostIDsBatch {
5299
- args [j ] = hostIDsBatch [j ]
5323
+ // Zero out failing policies count for hosts that are not in policy_membership
5324
+ stmt , args , err := sqlx .In (clearStmt , hostIDsBatch , hostIDsBatch )
5325
+ if err != nil {
5326
+ return ctxerr .Wrap (ctx , err , "building IN statement for clearing host failing policy issues" )
5327
+ }
5328
+ if _ , err := tx .ExecContext (ctx , stmt , args ... ); err != nil {
5329
+ return ctxerr .Wrap (ctx , err , "clearing failing policies in host issues" )
5330
+ }
5331
+ // Update failing policies count for hosts that are in policy_membership
5332
+ stmt , args , err = sqlx .In (insertStmt , hostIDsBatch )
5333
+ if err != nil {
5334
+ return ctxerr .Wrap (ctx , err , "building IN statement for updating host failing policy issues" )
5300
5335
}
5301
- if _ , err := tx .ExecContext (ctx , fmt . Sprintf ( masterStmt , inlineTable ) , args ... ); err != nil {
5302
- return ctxerr .Wrap (ctx , err , "update failing policies in host issues" )
5336
+ if _ , err := tx .ExecContext (ctx , stmt , args ... ); err != nil {
5337
+ return ctxerr .Wrap (ctx , err , "updating failing policies in host issues" )
5303
5338
}
5304
5339
}
5305
5340
return nil
@@ -5322,28 +5357,50 @@ func (ds *Datastore) UpdateHostIssuesVulnerabilities(ctx context.Context) error
5322
5357
return clearAllFn ()
5323
5358
}
5324
5359
5360
+ var allHostIDs []uint
5361
+ if err := sqlx .SelectContext (ctx , ds .reader (ctx ), & allHostIDs , `SELECT id FROM hosts ORDER BY id` ); err != nil {
5362
+ return ctxerr .Wrap (ctx , err , "get all host IDs" )
5363
+ }
5364
+
5325
5365
type issuesCount struct {
5326
5366
HostID uint64 `db:"host_id"`
5327
5367
Count uint64 `db:"count"`
5328
5368
}
5329
-
5330
5369
var criticalCounts []issuesCount
5331
- criticalVulnerabilitiesCountStmt := `
5370
+ // We must batch the query extracting the critical vulnerabilities count because the query is too complex for MySQL to handle in one go.
5371
+ // We saw MySQL error 1114 (HY000), where the temporary table reached its max capacity.
5372
+ for i := 0 ; i < len (allHostIDs ); i += hostIssuesInsertBatchSize {
5373
+ start := i
5374
+ end := i + hostIssuesInsertBatchSize
5375
+ if end > len (allHostIDs ) {
5376
+ end = len (allHostIDs )
5377
+ }
5378
+ criticalVulnerabilitiesCountStmt := `
5332
5379
SELECT combined.host_id, COUNT(*) as count
5333
5380
FROM (SELECT host_id, cve
5334
5381
FROM host_software hs
5335
5382
INNER JOIN software_cve sc ON sc.software_id = hs.software_id
5383
+ WHERE host_id IN (?)
5336
5384
UNION
5337
5385
SELECT host_id, cve
5338
5386
FROM host_operating_system hos
5339
- INNER JOIN operating_system_vulnerabilities osv ON osv.operating_system_id = hos.os_id) combined
5387
+ INNER JOIN operating_system_vulnerabilities osv ON osv.operating_system_id = hos.os_id
5388
+ WHERE host_id IN (?)
5389
+ ) combined
5340
5390
INNER JOIN cve_meta cm ON cm.cve = combined.cve
5341
5391
WHERE cm.cvss_score > ?
5342
5392
GROUP BY combined.host_id
5343
5393
ORDER BY combined.host_id`
5344
- err := sqlx .SelectContext (ctx , ds .reader (ctx ), & criticalCounts , criticalVulnerabilitiesCountStmt , criticalCVSSScoreCutoff )
5345
- if err != nil {
5346
- return ctxerr .Wrap (ctx , err , "get critical vulnerabilities count" )
5394
+ stmt , args , err := sqlx .In (criticalVulnerabilitiesCountStmt , allHostIDs [start :end ], allHostIDs [start :end ], criticalCVSSScoreCutoff )
5395
+ if err != nil {
5396
+ return ctxerr .Wrap (ctx , err , "building IN statement for getting critical vulnerabilities count" )
5397
+ }
5398
+ var batchCriticalCounts []issuesCount
5399
+ err = sqlx .SelectContext (ctx , ds .reader (ctx ), & batchCriticalCounts , stmt , args ... )
5400
+ if err != nil {
5401
+ return ctxerr .Wrap (ctx , err , "get critical vulnerabilities count" )
5402
+ }
5403
+ criticalCounts = append (criticalCounts , batchCriticalCounts ... )
5347
5404
}
5348
5405
5349
5406
// Update the host_issues table, including deleting items with no issues
@@ -5382,7 +5439,7 @@ func (ds *Datastore) UpdateHostIssuesVulnerabilities(ctx context.Context) error
5382
5439
for j := start ; j < end ; j ++ {
5383
5440
hostIDs = append (hostIDs , criticalCounts [j ].HostID )
5384
5441
}
5385
- stmt , args , err = sqlx .In (
5442
+ stmt , args , err : = sqlx .In (
5386
5443
"UPDATE host_issues SET critical_vulnerabilities_count = 0, total_issues_count = failing_policies_count WHERE host_id NOT IN (?)" ,
5387
5444
hostIDs ,
5388
5445
)
0 commit comments