Commit 6669b2a
[Security Solution] Randomize task schedules when bulk scheduling (#269991)
**Resolves: #195136**
**Related to: #264893**
**Related to: #269340**
## Summary
`TaskScheduling.bulkSchedule` previously sent every task to the store
with no `runAt`, which caused the store to default them all to "now".
When a caller bulk-scheduled many recurring tasks at once, the polling
queue was flooded with [simultaneous
claims](https://en.wikipedia.org/wiki/Thundering_herd_problem).
This PR brings `bulkSchedule` in line with `bulkEnable` (see #172742):
the first task in the batch still runs immediately, but subsequent
enabled recurring tasks are scheduled with a randomized `runAt`, evenly
distributed up to 5 minutes in the future. Ad-hoc tasks (no
`schedule.interval`) and disabled tasks are left untouched and run
immediately as before.
This also helps unblock upcoming work on `RulesClient.bulkCreate`
(#264893), where a single API call may schedule a large number of
detection-rule tasks at once.
## Whats included
- The existing `randomlyOffsetRunTimestamp()` helper is replaced by a
smaller pure helper `addJitter()` that returns `{ runAt, scheduledAt }`
- or `undefined` when no interval is supplied - so callers control the
spread.
- `bulkSchedule` map callback now receives the index `i` and uses
`addJitter()` when `enabled && i > 0`.
- `bulkEnable`'s behavior continues exactly the same with the `i > 0`
branch using the shared `addJitter()` helper.
## How to test
> [!IMPORTANT]
> There is no easy way to test TM `bulkSchedule()` randomizing without
`v2` alerting. Because of this, the test below only covers task
randomizing under `bulkEnable()`. If you apply debugging here, you will
notice that enabling detection rules in bulk uses `bulkSchedule()` under
the hood, but it does so in `enabled: false` state. In other words, no
jitter will get applied until the following pass. This is expected, what
the test below does primarily is to verify that existing behavior is
unaffected. The changes to behavior in `bulkSchedule(enabled:true)` will
become more meaningful with upcoming work on alerting `v2` and
`RulesClient.bulkCreate`.
1. Start ES + Kibana from this branch. Make sure you have a clean ES
with no rules.
2. In Kibana, navigate to **Security → Rules → Detection rules (SIEM)**
and click **Add Elastic Rules** to install the prebuilt detection rule
set (~1850 rules). Leave them disabled.
> Note: This is a good time to place some breakpoints if you're
debugging locally.
3. Go back to the rules management screen. Under "Installed Rules" click
the checkbox to select first 20 rules then `Bulk actions` > `Enable`.
You should see a message saying "Successfully enabled 20 rules"
4. Verify the `runAt` / `scheduledAt` distribution using
[`check-task-runtime.sh`](https://github.com/sdesalas/kibana-knowledge/blob/main/scripts/check-task-runtime.sh):
```bash
$ ./check-task-runtime.sh
```
Or if you are using ports different to the standard `5601` and `9200`
```bash
$ KIBANA_DEV_PORT=5606 ES_DEV_PORT=9205 ./check-task-runtime.sh
```
5. Expected output: counts match, and the first-20 task timestamps are
**spread across several minutes** rather than all stamped with the same
"now":
```
starting..
KIBANA_URL=http://localhost:5601/kbn
ES_URL=http://localhost:9200
1. 2. 3. 4. 5. 6.
rules: 1850
rules_enabled: 20
tasks: 20
tasks_enabled: 20
api_key_owner: 20
apiKey present: 20
first 20 tasks:
taskType status enabled runAt scheduledAt
alerting:siem.queryRule idle true 2026-05-19T16:20:08.323Z 2026-05-19T16:20:08.323Z
alerting:siem.queryRule idle true 2026-05-19T16:21:28.689Z 2026-05-19T16:21:28.689Z
alerting:siem.eqlRule idle true 2026-05-19T16:21:05.927Z 2026-05-19T16:21:05.927Z
alerting:siem.queryRule idle true 2026-05-19T16:20:53.163Z 2026-05-19T16:20:53.163Z
alerting:siem.queryRule idle true 2026-05-19T16:23:30.562Z 2026-05-19T16:23:30.562Z
alerting:siem.esqlRule idle true 2026-05-19T16:23:45.295Z 2026-05-19T16:23:45.295Z
...
```
For every task, `runAt` should equal its matching `scheduledAt`. The
timestamps should be distributed across the configured jitter window
(`min(rule interval, 5m)`) - confirming jitter is applied per-task. On
`main` without this PR, every task's `runAt` collapses to the same
value.
## Callers of `bulkSchedule`
For reference, the production callers of `TaskScheduling.bulkSchedule`
and whether they exercise the new jitter:
| Caller | What it schedules | Hits the new jitter? |
|---|---|---|
|
[`alerting_v2/.../rules_client.bulkEnableRules`](https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/alerting_v2/server/lib/rules_client/rules_client.ts)
| enabled, recurring (`schedule.interval`, `enabled: true`) | **Yes** —
primary path exercised by the manual test above |
|
[`alerting/.../bulk_enable_rules.ts`](https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/alerting/server/application/rule/methods/bulk_enable/bulk_enable_rules.ts)
(legacy) | recurring but `enabled: false` — the comment in that file
explicitly says "we create the task as disabled, taskManager.bulkEnable
will enable them by randomising their schedule datetime" | No (jitter
applied later by `bulkEnable`) |
|
[`workflows_execution_engine/server/plugin.ts`](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/workflows_execution_engine/server/plugin.ts)
| enabled but ad-hoc (no `schedule`) | No (ad-hoc — runs immediately,
correct) |
|
[`actions/create_execute_function.ts`](https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/actions/server/create_execute_function.ts)
| ad-hoc action tasks | No |
|
[`actions/create_unsecured_execute_function.ts`](https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/actions/server/create_unsecured_execute_function.ts)
| ad-hoc action tasks | No |
|
[`alerting/.../backfill_client.ts`](https://github.com/elastic/kibana/blob/main/x-pack/platform/plugins/shared/alerting/server/backfill_client/backfill_client.ts)
| variable is literally `adHocTasksToSchedule` | No |
Of these, only the alerting_v2 `bulkEnableRules` path schedules enabled
recurring tasks in bulk, so it is the only caller whose runtime behavior
changes with this PR.
## Release note
skip
## Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
### Identify risks
- Behavior change is scoped to recurring tasks at `i > 0`; single-task
`bulkSchedule` calls and ad-hoc tasks retain the existing "run now"
semantics.
- The `bulkEnable` path is unchanged in semantics; only the helper
signature changed.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent d29ae68 commit 6669b2a
2 files changed
Lines changed: 146 additions & 14 deletions
File tree
Lines changed: 124 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1337 | 1337 | | |
1338 | 1338 | | |
1339 | 1339 | | |
| 1340 | + | |
| 1341 | + | |
1340 | 1342 | | |
1341 | 1343 | | |
1342 | 1344 | | |
| |||
1366 | 1368 | | |
1367 | 1369 | | |
1368 | 1370 | | |
| 1371 | + | |
| 1372 | + | |
1369 | 1373 | | |
1370 | 1374 | | |
1371 | 1375 | | |
| |||
1379 | 1383 | | |
1380 | 1384 | | |
1381 | 1385 | | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
| 1495 | + | |
| 1496 | + | |
| 1497 | + | |
| 1498 | + | |
| 1499 | + | |
| 1500 | + | |
| 1501 | + | |
| 1502 | + | |
| 1503 | + | |
| 1504 | + | |
| 1505 | + | |
1382 | 1506 | | |
1383 | 1507 | | |
1384 | 1508 | | |
| |||
Lines changed: 22 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
148 | 148 | | |
149 | 149 | | |
150 | 150 | | |
151 | | - | |
| 151 | + | |
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
156 | 166 | | |
157 | 167 | | |
158 | 168 | | |
159 | | - | |
| 169 | + | |
| 170 | + | |
160 | 171 | | |
161 | 172 | | |
162 | 173 | | |
| |||
200 | 211 | | |
201 | 212 | | |
202 | 213 | | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
208 | 217 | | |
209 | 218 | | |
210 | 219 | | |
| |||
375 | 384 | | |
376 | 385 | | |
377 | 386 | | |
378 | | - | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
379 | 391 | | |
380 | 392 | | |
381 | | - | |
| 393 | + | |
382 | 394 | | |
383 | 395 | | |
384 | 396 | | |
385 | 397 | | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | | - | |
| 398 | + | |
391 | 399 | | |
0 commit comments