Skip to content

Fix model serving telemetry_config drift and dropped updates - #6106

Draft
radakam wants to merge 12 commits into
mainfrom
fix-serving-telemetry-config
Draft

Fix model serving telemetry_config drift and dropped updates#6106
radakam wants to merge 12 commits into
mainfrom
fix-serving-telemetry-config

Conversation

@radakam

@radakam radakam commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Changes

Classify telemetry_config as a backend default and apply changes to it through ServingEndpoints.PatchTelemetryConfig.

Why

The backend assigns a telemetry_profile_id and echoes it back on GET. Nothing suppressed it, so every bundle plan after a deploy showed a perpetual update. Separately, editing telemetry_config planned an update that DoUpdate had no branch for, so the deploy silently did nothing.

The rule covers the whole subtree, not just telemetry_profile_id: with no local telemetry_config, the remote-only value arrives as a single change on the parent path, which a child rule doesn't match. That planned an update and sent a PATCH with no telemetry_config, deleting telemetry from an endpoint the bundle never configured. Genuine removals still apply, since they carry a non-nil old value.

Behavior change

The backend rejects telemetry on endpoints without custom served models. Such an endpoint with telemetry_config in the bundle used to deploy fine, because the backend dropped the field; now every deploy fails with Telemetry configuration is not supported for endpoint type 'NO_CONFIG'. Erroring beats silently ignoring the field, but it surfaces as a raw 400 rather than a plan-time validation. This is also why telemetry_config left the invariant configs.

Tests

drift/telemetry_config covers convergence and that a sampling_fraction edit is applied. drift/telemetry_config_unmanaged covers out-of-band telemetry on an endpoint that doesn't declare it; with the rule at the child path it fails, with telemetry_config reading back as null. Direct engine only: the terraform provider types sampling_fraction as an integer and truncates 0.5 to 0.

radakam added 2 commits July 30, 2026 12:58
…ping updates

telemetry_config is only carried by CreateServingEndpoint. The serving
endpoints GET API never echoes it back, and no update API accepts it, so
classify it as input_only for remote changes and no_update_api for local
ones.
@radakam
radakam marked this pull request as ready for review July 30, 2026 13:18
@github-actions

Copy link
Copy Markdown
Contributor

Approval status: pending

/acceptance/bundle/ - needs approval

12 files changed
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

/bundle/ - needs approval

Files: bundle/direct/dresources/resources.yml
Suggested: @denik
Also eligible: @janniklasrose, @pietern, @andrewnester, @shreyas-goenka, @lennartkats-db, @anton-107

General files (require maintainer)

Files: .nextchanges/bundles/model-serving-telemetry-config-converges.md
Based on git history:

  • @denik -- recent work in bundle/direct/dresources/, .nextchanges/bundles/, acceptance/bundle/invariant/configs/

Any maintainer (@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka, @simonfaltum, @renaudhartert-db, @janniklasrose, @lennartkats-db) can approve all areas.
See OWNERS for ownership rules.

@radakam
radakam marked this pull request as draft July 30, 2026 13:41
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 155748b

Run: 31005269357

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🔄​ aws linux 1 4 4 290 1099 13:11
💚​ aws windows 4 4 293 1097 10:08
💚​ azure linux 4 4 290 1099 12:18
💚​ azure windows 4 4 292 1097 10:36
🔄​ gcp linux 1 1 5 290 1099 11:02
💚​ gcp windows 1 5 293 1097 11:42
10 interesting tests: 4 RECOVERED, 4 SKIP, 2 flaky
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestSyncIncrementalFileSync 🔄​f ✅​p ✅​p ✅​p ✅​p ✅​p
🔄​ TestSyncNestedFolderSync ✅​p ✅​p ✅​p ✅​p 🔄​f ✅​p
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 30 slowest tests (at least 2 minutes):
duration env testname
6:45 gcp windows TestAccept
6:11 aws windows TestAccept
5:52 azure windows TestAccept
4:02 azure linux TestFilerWorkspaceFilesExtensionsReadDir
3:36 gcp windows TestFilerWorkspaceFilesExtensionsReadDir
3:14 gcp windows TestFilerWorkspaceFilesExtensionsDelete
3:11 gcp linux TestFilerWorkspaceFilesExtensionsDelete
3:10 azure windows TestFilerWorkspaceFilesExtensionsReadDir
3:04 gcp windows TestFilerRecursiveDelete/workspace_files
3:03 azure windows TestFilerWorkspaceFilesExtensionsDelete
2:59 gcp windows TestFilerWorkspaceFilesExtensionsRead
2:59 azure linux TestAccept
2:55 gcp windows TestFilerWorkspaceFilesExtensionsStat
2:49 aws windows TestFilerWorkspaceFilesExtensionsRead
2:49 gcp linux TestAccept
2:48 aws linux TestAccept
2:44 aws windows TestImportDirWithOverwriteFlag
2:43 azure windows TestFilerWorkspaceNotebook/scalaNb.scala
2:39 gcp windows TestFilerWorkspaceNotebook/pyNb.py
2:34 aws windows TestFilerRecursiveDelete/workspace_files
2:30 aws windows TestFilerReadWrite/workspace_files_extensions
2:28 azure linux TestFilerWorkspaceFilesExtensionsStat
2:19 aws windows TestFilerRecursiveDelete/workspace_files_extensions
2:19 gcp linux TestSyncIncrementalFileSync
2:18 azure windows TestImportDir
2:16 aws windows TestFilerWorkspaceFilesExtensionsReadDir
2:15 aws linux TestFilerWorkspaceFilesExtensionsStat
2:06 gcp linux TestFilerWorkspaceFilesExtensionsReadDir
2:06 azure linux TestFilerWorkspaceFilesExtensionsRead
2:01 gcp linux TestFilerWorkspaceFilesExtensionsRead

The previous approach classified telemetry_config as input_only/no_update_api
on the premise that GET never returns it and no API updates it. Neither holds:
ServingEndpointDetailed carries the field, and ServingEndpoints.PatchTelemetryConfig
updates it. Suppressing the whole subtree converged only by making genuine
sampling_fraction edits invisible and silently dropping them.

The actual remote-only difference is telemetry_profile_id, which the backend
assigns and echoes back, so suppress just that as a backend default and wire
the PATCH API into DoUpdate. Edits now converge and are applied.

The testserver fake dropped telemetry_config on create, which is why the
earlier suppression looked correct locally; it now echoes the field and
populates the fields the backend assigns.
@radakam radakam changed the title Fix model serving telemetry_config never converging and silently drop… Fix model serving telemetry_config drift and dropped updates Aug 3, 2026
radakam added 9 commits August 3, 2026 14:09
The backend only supports telemetry configuration on endpoints with custom served
models. The invariant suite deploys a config-less endpoint, so the create silently
dropped telemetry_config and every subsequent plan reported an update that the
telemetry-config API then rejected with "Telemetry configuration is not supported
for endpoint type 'NO_CONFIG'". Serving a custom model needs a registered model
version and real serving compute, which is too slow for this suite, and the
drift/telemetry_config test already asserts the same convergence locally.

The terraform truncation note moves to that test, which is where the engine
matrix is now restricted.
The backend default was declared at telemetry_config.telemetry_profile_id, but when
telemetry_config is absent from the bundle, structdiff reports the populated remote
as a single change on the parent telemetry_config path. Neither the rule prefix nor
the map fallback in shouldSkipBackendDefault matches a struct at that path, so the
change was planned as an update and DoUpdate sent a PATCH with no telemetry_config,
deleting telemetry from an endpoint the bundle never configured. Adopting an
endpoint that already has telemetry reaches this.

Declaring the rule on the whole subtree covers the parent path. A genuine removal is
unaffected because it carries a non-nil old value, which shouldSkipBackendDefault
requires to be nil.

The new telemetry_config_unmanaged test covers it. With the rule back at the child
path it fails with the endpoint's telemetry_config reading back as null and a second
PATCH carrying an empty body.
Leftover from adding and then removing the telemetry_config invariant config.
The telemetry_config_unmanaged test reads the endpoint back with `api get` and a
leading-slash path, which MSYS rewrites to C:\Program Files\Git\api\... before the
CLI sees it, so the request missed the testserver stub and the test failed on
Windows only. The out-of-band `api patch` above it already guarded against this.

Setting MSYS_NO_PATHCONV for the whole script via test.toml would instead break
the print_requests.py invocation on the last line, the same reason script.prepare
unsets it around its Python helpers.
Nothing exercised the removal path, which is the least certain part of this
change: dropping telemetry_config from the bundle makes DoUpdate PATCH with the
field omitted, and whether that deletes the configuration or is a no-op is the
backend's call. The drift test now removes it, asserts the endpoint reads back
null, and records the empty PATCH body in the golden file so the assumption is
visible rather than buried in the fake.

The fake also minted a fresh telemetry_profile_id on every PATCH instead of
keeping the endpoint's profile. UUID masking hides that in the output, so it
could only ever mislead a future test; the patch now carries the existing ID
forward.

The changelog gained the user-visible regression: an endpoint the backend types
as NO_CONFIG that declares telemetry_config used to deploy because the field was
dropped, and now fails on every deploy.
Wiring the telemetry-config API into DoUpdate turned a silent no-op into a hard
failure for endpoints the backend types as NO_CONFIG. Create drops telemetry_config
on such an endpoint instead of failing, so the bundle deployed once and then failed
on every later deploy, when the plan applied the field the create had thrown away.

The field can never be honored there, so keep failing, but fail at validation time
naming the field and the fix instead of surfacing a 400 from the second deploy on.
The classification here had been rewritten twice on differing assumptions about
what the telemetry API accepts and returns, with the testserver fake reshaped to
match each one, so the acceptance tests could not tell whether any of them held.
Checked against a workspace instead:

  - A telemetry_config naming neither table_names nor telemetry_profile_id is
    discarded; create and the telemetry API both report success and apply nothing.
  - GET echoes telemetry_profile_id, and inference_table_config once the config
    was accepted. table_names is never echoed.
  - Telemetry is rejected on endpoints typed NO_CONFIG and EXTERNAL_MODELS, and
    create rejects a foundation model outright.
  - An empty PATCH body removes the configuration.
  - The telemetry API refuses to run while the endpoint is still applying an
    earlier update, and every update call starts one.

So table_names is input_only but inference_table_config is not: sampling_fraction
round trips and needs ordinary drift detection. Suppressing the subtree would
silently swallow the edits users actually make.

DoUpdate now waits for the endpoint to settle before patching telemetry when an
earlier call in the same pass ran. WaitAfterUpdate only runs once DoUpdate has
returned, so without this a deploy touching both config and telemetry_config
fails on the telemetry call.

Validation covers both forms the API discards, and the fake models the
endpoint-type rejection so a fixture the backend would refuse cannot pass.

The drift fixture moves off an external model, which the API rejects, onto a
custom served model and gains a sampling_fraction edit. Both drift tests stay
local: telemetry needs a registered UC model and roughly ten minutes to
provision, and each PATCH locks the endpoint for another two to three.
They had accumulated the reasoning behind each revision, which belongs in the
commit messages. Each one now states the API behaviour the code depends on and
stops there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants