Repository navigation
refactor(observability): one home for the task annotations telemetry reads - #407
yuechao-qin wants to merge 1 commit into
Conversation
94bbbe8 to
70e6786
Compare
…reads The cloud_provider key was declared twice in instrumentation/, and the three resource keys were reached by importing a launcher purely to borrow its constants. Collects every read into instrumentation/execution_annotations.py: the resource keys are aliased from kubernetes_launchers rather than respelled, and cloud_provider -- which no launcher owns, since routing sets it -- is declared once. Also adds Target and register_targets(), the lookup that turns whatever a task's provider annotation says into a provider and a cluster. A provider that declares no clusters of its own registers the annotation key its tasks use, so no deployment-specific key appears in this repository. Pure readers: task_spec is a loaded column, never a relationship, so these are safe inside before_commit where a lazy load would put a SELECT in the commit path. No behaviour change.
70e6786 to
179ab82
Compare
morgan-wowk
left a comment
There was a problem hiding this comment.
🤖 AI-assisted review at 179ab82.
This review looks at two questions: does the new telemetry add slow queries or slow work to hot paths, and could it break anything?
Performance: fine. Every reader in execution_annotations is a dict lookup on the already-loaded task_spec column. Nothing lazy-loads or queries the database.
Robustness: two small points are inline. Neither causes a failure on its own in this PR. They matter once #409 calls these readers from before_commit outside the try block. That review covers the crash path.
The full suite passes at this head (599 passed).
Posted as a comment, not an approval. Any approval decision is separate from this review.
| raw = _annotations(execution=execution).get(RESOURCES_ACCELERATORS_KEY) | ||
| if not raw: | ||
| return None | ||
| requested = json.loads(raw) # ValueError on malformed input |
There was a problem hiding this comment.
accelerators() documents only ValueError. Two inputs raise TypeError instead:
json.loads(raw)whenrawis not a string.TaskSpec.annotationsisDict[str, Any], so a client can send the accelerators annotation as a JSON object instead of a JSON-encoded string.int(quantity)on line 141 when the count isnullor a list, for example'{"nvidia-l4": null}'.
The same applies to _TARGETS.get(selected) on line 110: a list-valued cloud_provider is unhashable.
Today the only caller catches ValueError (_accelerator in #409), so these escape. The simplest fix is to have the readers catch (TypeError, ValueError), or to have the metric caller catch Exception.
| from contextlib import contextmanager | ||
| from typing import TYPE_CHECKING, Any, Optional | ||
|
|
||
| from . import execution_annotations |
There was a problem hiding this comment.
Minor, import weight. contextual_logging used to be almost dependency-free. Through execution_annotations it now imports backend_types_sql, SQLAlchemy and launchers.kubernetes_launchers, which pulls in the kubernetes client. In a fresh subprocess, the cumulative import time went from about 4 ms to about 249 ms. There's no import cycle; the structured-logging, bugsnag and api-tracing import orders all still work.
This module is imported early by oasis-backend's logging setup. If startup time matters there, a lazy import inside execution_logging_context, or keeping just the one key constant here, would avoid it.
What
Four annotation keys were read from three different modules, one of which imported a
Kubernetes launcher purely to borrow its constants. This puts every read in one place
and adds the lookup that turns whatever a task's provider annotation says into a
provider and a cluster.
No behaviour changes. Every value emitted today is still emitted, identically.
Why
The metric emitter is about to become a fourth reader, and it runs inside
before_commit:That last line is the problem. The cluster a task ran on is not in its annotations —
the annotation holds a key or an alias, and only the deployment's launcher config says
which cluster each resolves to. Reading that config per status change would put a file
read inside a database commit.
So the mapping is registered once at startup and looked up in memory.
Changes
CLOUD_PROVIDER_KEYkubernetes_launchers, not respelled, so a rename there cannot silently empty these readerscloud_provider()target()Target(provider, cluster)through the registered mapaccelerators(){"X":"1"}and{"X": "1"}parse to one requestregister_targets()Target.cluster_from_annotationcovers a provider that declares no clusters of its ownand lets each task name one. The deployment registers that annotation key, so no
deployment-specific key appears in this repository.
Every reader is pure —
task_specis a loaded column, never a relationship — so all ofthem are safe inside
before_commit, where a lazy load would put a SELECT in thecommit path.
The caller arrives separately:
targeting/routing.pyin Shopify's oasis-backend buildsthe map from its launcher config at
create_launcher(). Until something callsregister_targets, the map is empty andtarget()returnsNone, which is the sameanswer it gives for an unrecognised value.
Tests