Commit 3db1ddd
# Backport
This will backport the following commits from `main` to `9.4`:
- [[ResponseOps] Skip unchanged alerts-as-data index templates on
install (#280154)](#280154)
<!--- Backport version: 12.0.4 -->
### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)
<!--BACKPORT [{"author":{"name":"Ersin
Erdal","email":"92688503+ersin-erdal@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-09-10T15:06:27Z","message":"[ResponseOps]
Skip unchanged alerts-as-data index templates on install (#280154)\n\n##
Summary\n\nExtends the skip-unchanged (`_meta` content-hash)
installation work from\n#278126 to **index templates**, completing that
behavior across the\nresource types the alerts service installs per
context.\n\n#278126 is merged. Previously `createOrUpdateIndexTemplate`
always PUT\nthe template on every install. Now it stamps a content hash
into the\ntemplate's `_meta`, GET-checks the currently-installed
template, and\n**skips the write when the hash matches**.\n\n##
Why\n\nThis is the remaining slice of Phase 2 of #246016. On its own,
#278126\nmakes a repeated context install a no-op for the ILM policy
and\ncomponent templates, but the **index template still re-PUT** every
time.\n\nElasticsearch already recognises an identical PUT and leaves
the cluster\nstate alone, so these writes were not publishing new
cluster states —\nbut the body still crosses the wire from every node,
and the master\nstill queues and runs a cluster-state update task that
parses the\ntemplate before concluding nothing changed. That matters
most in\ncombination with the per-node install lock (#280126): when one
node\ninstalls and the others follow, a follower's install should be
cheap\nGETs, not redundant cluster-state writes. With this change, a
follower's\nrepeated install is a no-op across component *and* index
templates.\n\n## What changed\n\n-
`create_or_update_index_template.ts`:\n- a single
`getExistingIndexTemplate` GET now returns both the
existing\n`total_fields.limit` (for the Phase-1 preservation) and the
existing\n`_meta.content_hash`.\n- the content hash is computed
**after** the Phase-1 limit preservation,\nso the stamped hash matches
the body actually installed.\n- skip the `putIndexTemplate` (and the
preceding `simulateTemplate`)\nwhen the installed hash matches.\n\n###
Fail-safe\n\nSkipping happens only on a positive hash match. A missing
stamp\n(templates installed before this change), a 404, or any GET
failure —\npermissions, an exhausted retry — leaves the installed
content unknown\nand falls through to the normal PUT, so the check can
never block an\ninstall that would otherwise have succeeded. The worst
case is today's\nbehavior: a redundant write.\n\nOn that fall-through
path we also cannot preserve a higher existing\n`total_fields.limit`
(same as a 404). The skip still keys off the stamp,\nnot the live body:
a hand-edited template with an intact `content_hash`\nstays un-repaired,
where every install used to rewrite it.\n\n### The `total_fields.limit`
is tracked apart from the hash\n\nThe limit is deliberately left out of
the content hash. Kibana raises it\noutside this path while a mapping
install crawls the limit up\n(`updateIndexTemplateFieldsLimit`), and
operators raise it by hand\nthrough DevTools or the fields-limit API —
both leaving `_meta` intact.\nHashing it would make every such raise
read as a changed template and\nrewrite byte-identical content on the
next startup, which is exactly the\nwrite this PR exists to
remove.\n\nIt is compared as a number instead, and skipping requires
*both* an\nunchanged hash and an installed limit that already satisfies
the\nconfigured one, so a genuinely raised configured limit still
installs\nand a template missing `ignore_dynamic_beyond_limit` still
gets\nrepaired:\n\n| change | before | now |\n| --- | --- | --- |\n|
limit raised out of band (crawl / DevTools) | redundant install
|\nskipped |\n| configured limit raised | install | install |\n|
configured limit lowered | install (preserving the higher limit)
|\nskipped |\n| anything else in the template changed | install |
install |\n\n### Logging\n\nThe install is logged at `info` with the
reason it went ahead (`not\ninstalled`, `installed template carries no
content hash`, `content\nchanged (<old> -> <new>)`, or an unsatisfied
limit). Installs are the\nrare event after this change, so a resurgence
of template writes is\nvisible without enabling debug logging. The skip
stays at `debug`, since\nat `info` it would add a line per template per
(context x space) on\nevery startup.\n\n## Scope / not included\n\n-
**Concrete write index / mapping** hash-skip and
the\n**monotonic-version rule** are intentionally *not* here —
they're\nseparate, more involved changes (the mapping path interacts
with the\nauto-increase retry, and version-monotonicity needs semver
comparison).\nThis PR is the index-template slice.\n- No change to
*when* install runs or to auto-increase behavior.\n\n## Testing\n\n-
`node
scripts/jest\nx-pack/platform/plugins/shared/alerting/server/alerts_service`
— 333\npass, incl. index-template tests (stamped PUT / skip-on-match
/\nPUT-on-stale-hash / PUT-when-unreadable) and updated
integration\nassertions.\n- `node scripts/jest
.../rule_registry/server/rule_data_plugin_service`\n— pass.\n- `node
scripts/jest_integration
--config\nx-pack/platform/plugins/shared/alerting/jest.integration.config.js\nx-pack/platform/plugins/shared/alerting/server/integration_tests/skip_unchanged_index_templates.test.ts`\n—
5 pass against a real Elasticsearch: the stamp/skip round trip,
the\nre-PUT on change, the unstamped upgrade path, and both limit
directions\n(the out-of-band raise driven through the
real\n`updateIndexTemplateFieldsLimit`).\n- scoped `type_check` and
`eslint` clean.\n\nContributes to
#246016\n\n---------\n\nCo-authored-by: Claude Opus 4.8
<noreply@anthropic.com>\nCo-authored-by: Cursor
<cursoragent@cursor.com>","sha":"49e091ed8e8e898933b8a65467b28fdb72d218bd","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Alerting","release_note:skip","backport:skip","Team:ResponseOps","v9.6.0"],"title":"[ResponseOps]
Skip unchanged alerts-as-data index templates on
install","number":280154,"url":"https://github.com/elastic/kibana/pull/280154","mergeCommit":{"message":"[ResponseOps]
Skip unchanged alerts-as-data index templates on install (#280154)\n\n##
Summary\n\nExtends the skip-unchanged (`_meta` content-hash)
installation work from\n#278126 to **index templates**, completing that
behavior across the\nresource types the alerts service installs per
context.\n\n#278126 is merged. Previously `createOrUpdateIndexTemplate`
always PUT\nthe template on every install. Now it stamps a content hash
into the\ntemplate's `_meta`, GET-checks the currently-installed
template, and\n**skips the write when the hash matches**.\n\n##
Why\n\nThis is the remaining slice of Phase 2 of #246016. On its own,
#278126\nmakes a repeated context install a no-op for the ILM policy
and\ncomponent templates, but the **index template still re-PUT** every
time.\n\nElasticsearch already recognises an identical PUT and leaves
the cluster\nstate alone, so these writes were not publishing new
cluster states —\nbut the body still crosses the wire from every node,
and the master\nstill queues and runs a cluster-state update task that
parses the\ntemplate before concluding nothing changed. That matters
most in\ncombination with the per-node install lock (#280126): when one
node\ninstalls and the others follow, a follower's install should be
cheap\nGETs, not redundant cluster-state writes. With this change, a
follower's\nrepeated install is a no-op across component *and* index
templates.\n\n## What changed\n\n-
`create_or_update_index_template.ts`:\n- a single
`getExistingIndexTemplate` GET now returns both the
existing\n`total_fields.limit` (for the Phase-1 preservation) and the
existing\n`_meta.content_hash`.\n- the content hash is computed
**after** the Phase-1 limit preservation,\nso the stamped hash matches
the body actually installed.\n- skip the `putIndexTemplate` (and the
preceding `simulateTemplate`)\nwhen the installed hash matches.\n\n###
Fail-safe\n\nSkipping happens only on a positive hash match. A missing
stamp\n(templates installed before this change), a 404, or any GET
failure —\npermissions, an exhausted retry — leaves the installed
content unknown\nand falls through to the normal PUT, so the check can
never block an\ninstall that would otherwise have succeeded. The worst
case is today's\nbehavior: a redundant write.\n\nOn that fall-through
path we also cannot preserve a higher existing\n`total_fields.limit`
(same as a 404). The skip still keys off the stamp,\nnot the live body:
a hand-edited template with an intact `content_hash`\nstays un-repaired,
where every install used to rewrite it.\n\n### The `total_fields.limit`
is tracked apart from the hash\n\nThe limit is deliberately left out of
the content hash. Kibana raises it\noutside this path while a mapping
install crawls the limit up\n(`updateIndexTemplateFieldsLimit`), and
operators raise it by hand\nthrough DevTools or the fields-limit API —
both leaving `_meta` intact.\nHashing it would make every such raise
read as a changed template and\nrewrite byte-identical content on the
next startup, which is exactly the\nwrite this PR exists to
remove.\n\nIt is compared as a number instead, and skipping requires
*both* an\nunchanged hash and an installed limit that already satisfies
the\nconfigured one, so a genuinely raised configured limit still
installs\nand a template missing `ignore_dynamic_beyond_limit` still
gets\nrepaired:\n\n| change | before | now |\n| --- | --- | --- |\n|
limit raised out of band (crawl / DevTools) | redundant install
|\nskipped |\n| configured limit raised | install | install |\n|
configured limit lowered | install (preserving the higher limit)
|\nskipped |\n| anything else in the template changed | install |
install |\n\n### Logging\n\nThe install is logged at `info` with the
reason it went ahead (`not\ninstalled`, `installed template carries no
content hash`, `content\nchanged (<old> -> <new>)`, or an unsatisfied
limit). Installs are the\nrare event after this change, so a resurgence
of template writes is\nvisible without enabling debug logging. The skip
stays at `debug`, since\nat `info` it would add a line per template per
(context x space) on\nevery startup.\n\n## Scope / not included\n\n-
**Concrete write index / mapping** hash-skip and
the\n**monotonic-version rule** are intentionally *not* here —
they're\nseparate, more involved changes (the mapping path interacts
with the\nauto-increase retry, and version-monotonicity needs semver
comparison).\nThis PR is the index-template slice.\n- No change to
*when* install runs or to auto-increase behavior.\n\n## Testing\n\n-
`node
scripts/jest\nx-pack/platform/plugins/shared/alerting/server/alerts_service`
— 333\npass, incl. index-template tests (stamped PUT / skip-on-match
/\nPUT-on-stale-hash / PUT-when-unreadable) and updated
integration\nassertions.\n- `node scripts/jest
.../rule_registry/server/rule_data_plugin_service`\n— pass.\n- `node
scripts/jest_integration
--config\nx-pack/platform/plugins/shared/alerting/jest.integration.config.js\nx-pack/platform/plugins/shared/alerting/server/integration_tests/skip_unchanged_index_templates.test.ts`\n—
5 pass against a real Elasticsearch: the stamp/skip round trip,
the\nre-PUT on change, the unstamped upgrade path, and both limit
directions\n(the out-of-band raise driven through the
real\n`updateIndexTemplateFieldsLimit`).\n- scoped `type_check` and
`eslint` clean.\n\nContributes to
#246016\n\n---------\n\nCo-authored-by: Claude Opus 4.8
<noreply@anthropic.com>\nCo-authored-by: Cursor
<cursoragent@cursor.com>","sha":"49e091ed8e8e898933b8a65467b28fdb72d218bd"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/280154","number":280154,"mergeCommit":{"message":"[ResponseOps]
Skip unchanged alerts-as-data index templates on install (#280154)\n\n##
Summary\n\nExtends the skip-unchanged (`_meta` content-hash)
installation work from\n#278126 to **index templates**, completing that
behavior across the\nresource types the alerts service installs per
context.\n\n#278126 is merged. Previously `createOrUpdateIndexTemplate`
always PUT\nthe template on every install. Now it stamps a content hash
into the\ntemplate's `_meta`, GET-checks the currently-installed
template, and\n**skips the write when the hash matches**.\n\n##
Why\n\nThis is the remaining slice of Phase 2 of #246016. On its own,
#278126\nmakes a repeated context install a no-op for the ILM policy
and\ncomponent templates, but the **index template still re-PUT** every
time.\n\nElasticsearch already recognises an identical PUT and leaves
the cluster\nstate alone, so these writes were not publishing new
cluster states —\nbut the body still crosses the wire from every node,
and the master\nstill queues and runs a cluster-state update task that
parses the\ntemplate before concluding nothing changed. That matters
most in\ncombination with the per-node install lock (#280126): when one
node\ninstalls and the others follow, a follower's install should be
cheap\nGETs, not redundant cluster-state writes. With this change, a
follower's\nrepeated install is a no-op across component *and* index
templates.\n\n## What changed\n\n-
`create_or_update_index_template.ts`:\n- a single
`getExistingIndexTemplate` GET now returns both the
existing\n`total_fields.limit` (for the Phase-1 preservation) and the
existing\n`_meta.content_hash`.\n- the content hash is computed
**after** the Phase-1 limit preservation,\nso the stamped hash matches
the body actually installed.\n- skip the `putIndexTemplate` (and the
preceding `simulateTemplate`)\nwhen the installed hash matches.\n\n###
Fail-safe\n\nSkipping happens only on a positive hash match. A missing
stamp\n(templates installed before this change), a 404, or any GET
failure —\npermissions, an exhausted retry — leaves the installed
content unknown\nand falls through to the normal PUT, so the check can
never block an\ninstall that would otherwise have succeeded. The worst
case is today's\nbehavior: a redundant write.\n\nOn that fall-through
path we also cannot preserve a higher existing\n`total_fields.limit`
(same as a 404). The skip still keys off the stamp,\nnot the live body:
a hand-edited template with an intact `content_hash`\nstays un-repaired,
where every install used to rewrite it.\n\n### The `total_fields.limit`
is tracked apart from the hash\n\nThe limit is deliberately left out of
the content hash. Kibana raises it\noutside this path while a mapping
install crawls the limit up\n(`updateIndexTemplateFieldsLimit`), and
operators raise it by hand\nthrough DevTools or the fields-limit API —
both leaving `_meta` intact.\nHashing it would make every such raise
read as a changed template and\nrewrite byte-identical content on the
next startup, which is exactly the\nwrite this PR exists to
remove.\n\nIt is compared as a number instead, and skipping requires
*both* an\nunchanged hash and an installed limit that already satisfies
the\nconfigured one, so a genuinely raised configured limit still
installs\nand a template missing `ignore_dynamic_beyond_limit` still
gets\nrepaired:\n\n| change | before | now |\n| --- | --- | --- |\n|
limit raised out of band (crawl / DevTools) | redundant install
|\nskipped |\n| configured limit raised | install | install |\n|
configured limit lowered | install (preserving the higher limit)
|\nskipped |\n| anything else in the template changed | install |
install |\n\n### Logging\n\nThe install is logged at `info` with the
reason it went ahead (`not\ninstalled`, `installed template carries no
content hash`, `content\nchanged (<old> -> <new>)`, or an unsatisfied
limit). Installs are the\nrare event after this change, so a resurgence
of template writes is\nvisible without enabling debug logging. The skip
stays at `debug`, since\nat `info` it would add a line per template per
(context x space) on\nevery startup.\n\n## Scope / not included\n\n-
**Concrete write index / mapping** hash-skip and
the\n**monotonic-version rule** are intentionally *not* here —
they're\nseparate, more involved changes (the mapping path interacts
with the\nauto-increase retry, and version-monotonicity needs semver
comparison).\nThis PR is the index-template slice.\n- No change to
*when* install runs or to auto-increase behavior.\n\n## Testing\n\n-
`node
scripts/jest\nx-pack/platform/plugins/shared/alerting/server/alerts_service`
— 333\npass, incl. index-template tests (stamped PUT / skip-on-match
/\nPUT-on-stale-hash / PUT-when-unreadable) and updated
integration\nassertions.\n- `node scripts/jest
.../rule_registry/server/rule_data_plugin_service`\n— pass.\n- `node
scripts/jest_integration
--config\nx-pack/platform/plugins/shared/alerting/jest.integration.config.js\nx-pack/platform/plugins/shared/alerting/server/integration_tests/skip_unchanged_index_templates.test.ts`\n—
5 pass against a real Elasticsearch: the stamp/skip round trip,
the\nre-PUT on change, the unstamped upgrade path, and both limit
directions\n(the out-of-band raise driven through the
real\n`updateIndexTemplateFieldsLimit`).\n- scoped `type_check` and
`eslint` clean.\n\nContributes to
#246016\n\n---------\n\nCo-authored-by: Claude Opus 4.8
<noreply@anthropic.com>\nCo-authored-by: Cursor
<cursoragent@cursor.com>","sha":"49e091ed8e8e898933b8a65467b28fdb72d218bd"}}]}]
BACKPORT-->
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 554922f commit 3db1ddd
4 files changed
Lines changed: 574 additions & 50 deletions
File tree
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
| 200 | + | |
200 | 201 | | |
201 | 202 | | |
202 | 203 | | |
| |||
889 | 890 | | |
890 | 891 | | |
891 | 892 | | |
| 893 | + | |
892 | 894 | | |
893 | 895 | | |
894 | 896 | | |
| |||
Lines changed: 218 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
134 | 204 | | |
135 | 205 | | |
136 | 206 | | |
137 | 207 | | |
138 | 208 | | |
139 | | - | |
| 209 | + | |
140 | 210 | | |
141 | 211 | | |
142 | 212 | | |
143 | 213 | | |
144 | 214 | | |
145 | 215 | | |
146 | 216 | | |
147 | | - | |
148 | | - | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
149 | 351 | | |
150 | 352 | | |
151 | 353 | | |
| |||
199 | 401 | | |
200 | 402 | | |
201 | 403 | | |
202 | | - | |
| 404 | + | |
203 | 405 | | |
204 | 406 | | |
205 | 407 | | |
| |||
214 | 416 | | |
215 | 417 | | |
216 | 418 | | |
217 | | - | |
| 419 | + | |
218 | 420 | | |
219 | 421 | | |
220 | | - | |
221 | | - | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
222 | 425 | | |
223 | | - | |
224 | | - | |
225 | | - | |
226 | | - | |
227 | | - | |
228 | | - | |
229 | | - | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
230 | 431 | | |
231 | | - | |
232 | | - | |
233 | | - | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
234 | 435 | | |
235 | | - | |
236 | 436 | | |
237 | 437 | | |
238 | 438 | | |
| |||
0 commit comments